diff --git a/.swift-version b/.swift-version index 8c50098..4d54dad 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.1 +4.0.2 diff --git a/AlertBar.podspec b/AlertBar.podspec index fa1921b..f23031f 100644 --- a/AlertBar.podspec +++ b/AlertBar.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "AlertBar" - s.version = "0.3.3" + s.version = "0.4.0" s.summary = "An easy alert on status bar." s.homepage = "https://github.com/jinSasaki/AlertBar" s.screenshots = "https://github.com/jinSasaki/AlertBar/raw/master/assets/demo.gif" diff --git a/AlertBar.xcodeproj/project.pbxproj b/AlertBar.xcodeproj/project.pbxproj index f56308b..5c0dc40 100644 --- a/AlertBar.xcodeproj/project.pbxproj +++ b/AlertBar.xcodeproj/project.pbxproj @@ -73,8 +73,8 @@ 93A4C46E1E223B0B00C0962F /* AlertBar */ = { isa = PBXGroup; children = ( - 93A4C46F1E223B0B00C0962F /* AlertBar.h */, 93C0F48B1E8AC52800F15353 /* Sources */, + 93A4C46F1E223B0B00C0962F /* AlertBar.h */, 93A4C4701E223B0B00C0962F /* Info.plist */, ); path = AlertBar; @@ -278,6 +278,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -323,6 +324,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -346,7 +348,6 @@ PRODUCT_BUNDLE_IDENTIFIER = jp.sasakky.AlertBar; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -366,7 +367,6 @@ PRODUCT_BUNDLE_IDENTIFIER = jp.sasakky.AlertBar; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -379,7 +379,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = jp.sasakky.AlertBarTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -392,7 +391,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = jp.sasakky.AlertBarTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Example/AlertBarExample.xcodeproj/project.pbxproj b/Example/AlertBarExample.xcodeproj/project.pbxproj index 61dfd8b..ec8cf17 100644 --- a/Example/AlertBarExample.xcodeproj/project.pbxproj +++ b/Example/AlertBarExample.xcodeproj/project.pbxproj @@ -7,15 +7,16 @@ objects = { /* Begin PBXBuildFile section */ + 930B7D7E1F78D0D2005DBCFB /* AlertBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930B7D7D1F78D0C0005DBCFB /* AlertBar.swift */; }; 93A4C4931E223C7B00C0962F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A4C4921E223C7B00C0962F /* AppDelegate.swift */; }; 93A4C4951E223C7B00C0962F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A4C4941E223C7B00C0962F /* ViewController.swift */; }; 93A4C4981E223C7B00C0962F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 93A4C4961E223C7B00C0962F /* Main.storyboard */; }; 93A4C49A1E223C7B00C0962F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93A4C4991E223C7B00C0962F /* Assets.xcassets */; }; 93A4C49D1E223C7B00C0962F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 93A4C49B1E223C7B00C0962F /* LaunchScreen.storyboard */; }; - 93C0F48A1E8AC48500F15353 /* AlertBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C0F4891E8AC48500F15353 /* AlertBar.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 930B7D7D1F78D0C0005DBCFB /* AlertBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBar.swift; sourceTree = ""; }; 93A4C48F1E223C7B00C0962F /* AlertBarExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlertBarExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 93A4C4921E223C7B00C0962F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 93A4C4941E223C7B00C0962F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -23,7 +24,6 @@ 93A4C4991E223C7B00C0962F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 93A4C49C1E223C7B00C0962F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 93A4C49E1E223C7B00C0962F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 93C0F4891E8AC48500F15353 /* AlertBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlertBar.swift; path = ../../../AlertBar/Sources/AlertBar.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -37,6 +37,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 930B7D7C1F78D0C0005DBCFB /* Sources */ = { + isa = PBXGroup; + children = ( + 930B7D7D1F78D0C0005DBCFB /* AlertBar.swift */, + ); + name = Sources; + path = ../../Sources; + sourceTree = ""; + }; 93A4C4861E223C7B00C0962F = { isa = PBXGroup; children = ( @@ -56,25 +65,17 @@ 93A4C4911E223C7B00C0962F /* AlertBarExample */ = { isa = PBXGroup; children = ( + 930B7D7C1F78D0C0005DBCFB /* Sources */, 93A4C4921E223C7B00C0962F /* AppDelegate.swift */, - 93A4C4941E223C7B00C0962F /* ViewController.swift */, - 93A4C4A41E223CDB00C0962F /* Sources */, - 93A4C4961E223C7B00C0962F /* Main.storyboard */, 93A4C4991E223C7B00C0962F /* Assets.xcassets */, - 93A4C49B1E223C7B00C0962F /* LaunchScreen.storyboard */, 93A4C49E1E223C7B00C0962F /* Info.plist */, + 93A4C49B1E223C7B00C0962F /* LaunchScreen.storyboard */, + 93A4C4961E223C7B00C0962F /* Main.storyboard */, + 93A4C4941E223C7B00C0962F /* ViewController.swift */, ); path = AlertBarExample; sourceTree = ""; }; - 93A4C4A41E223CDB00C0962F /* Sources */ = { - isa = PBXGroup; - children = ( - 93C0F4891E8AC48500F15353 /* AlertBar.swift */, - ); - path = Sources; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -134,8 +135,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 93A4C49D1E223C7B00C0962F /* LaunchScreen.storyboard in Resources */, 93A4C49A1E223C7B00C0962F /* Assets.xcassets in Resources */, + 93A4C49D1E223C7B00C0962F /* LaunchScreen.storyboard in Resources */, 93A4C4981E223C7B00C0962F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -147,9 +148,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 93A4C4951E223C7B00C0962F /* ViewController.swift in Sources */, - 93C0F48A1E8AC48500F15353 /* AlertBar.swift in Sources */, + 930B7D7E1F78D0D2005DBCFB /* AlertBar.swift in Sources */, 93A4C4931E223C7B00C0962F /* AppDelegate.swift in Sources */, + 93A4C4951E223C7B00C0962F /* ViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -275,7 +276,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = jp.sasakky.AlertBarExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -288,7 +290,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = jp.sasakky.AlertBarExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/Example/AlertBarExample/ViewController.swift b/Example/AlertBarExample/ViewController.swift index a6bf6b3..cae0060 100644 --- a/Example/AlertBarExample/ViewController.swift +++ b/Example/AlertBarExample/ViewController.swift @@ -11,7 +11,7 @@ import UIKit class ViewController: UIViewController { @IBAction func tapSuccess(_ sender: AnyObject) { - AlertBar.show(.success, message: "This is a Success message.") + AlertBar.show(type: .success, message: "This is a Success message.") } @IBAction func tapError(_ sender: AnyObject) { @@ -19,21 +19,21 @@ class ViewController: UIViewController { } @IBAction func tapNotice(_ sender: AnyObject) { - AlertBar.show(.notice, message: "This is a Notice message.", completion: { () -> Void in + AlertBar.show(type: .notice, message: "This is a Notice message.", completion: { () -> Void in print("Noticed") }) } @IBAction func tapWarning(_ sender: AnyObject) { - AlertBar.show(.warning, message: "This is a Warning message.") + AlertBar.show(type: .warning, message: "This is a Warning message.") } @IBAction func tapInfo(_ sender: AnyObject) { - AlertBar.show(.info, message: "This is an Info message.") + AlertBar.show(type: .info, message: "This is an Info message.", option: .init(shouldConsiderSafeArea: false, isStretchable: true, textAlignment: .right)) } @IBAction func tapCustom(_ sender: AnyObject) { - AlertBar.show(.custom(UIColor.lightGray, UIColor.black), message: "This is a Custom message.", duration: 5) + AlertBar.show(type: .custom(UIColor.lightGray, UIColor.black), message: "This is a Custom message. \nlong \nlong \nlong \nlong \nlong \nlong \nlong message", duration: 5, option: .init(isStretchable: true, textAlignment: .center)) } override var prefersStatusBarHidden : Bool { diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..71effec --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +#!/bin/bash + +sort: sort-src sort-example + +sort-src: + - ./scripts/sort-Xcode-project-file "./AlertBar.xcodeproj" + +sort-example: + - ./scripts/sort-Xcode-project-file "./Example/AlertBarExample.xcodeproj" diff --git a/README.md b/README.md index ad7efad..02a1bdf 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ An easy alert on status bar. -![demo](./assets/demo.gif) +| Demo | +| :---: | +| ![demo](./assets/demo.gif) | ## Usage ### Import @@ -23,36 +25,74 @@ AlertBar has default types: - warning - info -``` -AlertBar.show(.success, message: "This is a Success message.") +```swift +AlertBar.show(type: .success, message: "This is a Success message.") ``` And you can customize the background and text colors of AlertBar. -Select `Custom` type and set background and text colors as UIColor: `.Custom(BackgroundColor, TextColor)` +Select `custom` type and set background and text colors as UIColor: `.custom(BackgroundColor, TextColor)` -``` -AlertBar.show(.custom(.lightGray, .black), message: "This is a Custom message.") +```swift +AlertBar.show(type: .custom(.lightGray, .black), message: "This is a Custom message.") ``` #### Alert duration AlertBar accepts to custom alert duration. -``` -AlertBar.show(.success, message: "This is a Success message.", duration: 10) +```swift +AlertBar.show(type: .success, message: "This is a Success message.", duration: 10) ``` -### Custom Options -#### TextAlignment -AlertBar accepts to custom text alignment. -NOTE: This option is global. +### AlertBar Options + +AlertBar accepts options follows: + +- Consider Safe Area +- Stretch bar +- TextAlignment + +Use `setDefault` method to set default option. +```swift +let option = AlertBar.Option( + shouldConsiderSafeArea: true, + isStretchable: true, + textAlignment: .center +) +AlertBar.setDefault(option: option) ``` -AlertBar.textAlignment = .center + +Or set parameter of `show` method to each AlertBar. +```swift +let option = AlertBar.Option( + shouldConsiderSafeArea: true, + isStretchable: true, + textAlignment: .center +) +AlertBar.show(type: .success, message: "This is AlertBar!", option: option) ``` -## Requirements +### Consider Safe Area +The Safe Area is adopted from iOS 11 and AlertBar can change whether to consider SafeArea or not. -- Swift 3.x -- iOS 8.0+ -- ARC +`AlertBar.Option#shouldConsiderSafeArea: Bool` +is set to `true` by default. + +| `shouldConsiderSafeArea == true` | `shouldConsiderSafeArea == false` | +| :---: | :---: | +| ![true](./assets/shouldConsiderSafeArea_true.png) | ![false](./assets/shouldConsiderSafeArea_false.png) | + +### Stretch bar +AlertBar can stretch the bar if the message needs the multi lines. + +`AlertBar.Option#isStretchable: Bool` +is set to `false` by default. + +| `isStretchable == true` | `isStretchable == false` | +| :---: | :---: | +| ![true](./assets/isStretchable_true.png) | ![false](./assets/isStretchable_false.png) | + + +#### TextAlignment +AlertBar accepts to custom text alignment. ## Installation ### CocoaPods diff --git a/Sources/AlertBar.swift b/Sources/AlertBar.swift index 00389e2..675be34 100644 --- a/Sources/AlertBar.swift +++ b/Sources/AlertBar.swift @@ -16,79 +16,59 @@ public enum AlertBarType { case custom(UIColor, UIColor) var backgroundColor: UIColor { - get { - switch self { - case .success: - return UIColor(0x4CAF50) - case .error: - return UIColor(0xf44336) - case .notice: - return UIColor(0x2196F3) - case .warning: - return UIColor(0xFFC107) - case .info: - return UIColor(0x009688) - case .custom(let backgroundColor, _): - return backgroundColor - } + switch self { + case .success: return UIColor(0x4CAF50) + case .error: return UIColor(0xf44336) + case .notice: return UIColor(0x2196F3) + case .warning: return UIColor(0xFFC107) + case .info: return UIColor(0x009688) + case .custom(let backgroundColor, _): return backgroundColor } } var textColor: UIColor { - get { - switch self { - case .custom(_, let textColor): - return textColor - default: - return UIColor(0xFFFFFF) - } + switch self { + case .custom(_, let textColor): return textColor + default: return UIColor(0xFFFFFF) } } } -open class AlertBar: UIView { - open static var textAlignment: NSTextAlignment = .left - static var alertBars: [AlertBar] = [] - - let messageLabel = UILabel() +public final class AlertBar { + public static let shared = AlertBar() + private static let kWindowLevel: CGFloat = UIWindowLevelStatusBar + 1 + private var alertBarViews: [AlertBarView] = [] + private var option = Option(shouldConsiderSafeArea: true, isStretchable: false, textAlignment: .left) - required public init?(coder aDecoder: NSCoder) { - fatalError("NSCoding not supported") - } - - override init(frame: CGRect) { - super.init(frame: frame) - messageLabel.frame = CGRect(x: 2, y: 2, width: frame.width - 4, height: frame.height - 4) - messageLabel.font = UIFont.systemFont(ofSize: 12) - self.addSubview(messageLabel) - - NotificationCenter.default.addObserver(self, selector: #selector(self.handleRotate(_:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) - } + public struct Option { + let shouldConsiderSafeArea: Bool + let isStretchable: Bool + let textAlignment: NSTextAlignment - deinit { - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) + public init( + shouldConsiderSafeArea: Bool = true, + isStretchable: Bool = false, + textAlignment: NSTextAlignment = .left) { + + self.shouldConsiderSafeArea = shouldConsiderSafeArea + self.isStretchable = isStretchable + self.textAlignment = textAlignment + } } - - @objc fileprivate func handleRotate(_ notification: Notification) { - self.removeFromSuperview() - AlertBar.alertBars = [] + + public func setDefault(option: Option) { + self.option = option } - - open class func show(_ type: AlertBarType, message: String, duration: Double = 2, completion: (() -> Void)? = nil) { - let statusBarHeight = UIApplication.shared.statusBarFrame.height - let alertBar = AlertBar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: statusBarHeight)) - alertBar.messageLabel.text = message - alertBar.messageLabel.textAlignment = AlertBar.textAlignment - alertBar.backgroundColor = type.backgroundColor - alertBar.messageLabel.textColor = type.textColor - AlertBar.alertBars.append(alertBar) - + + public func show(type: AlertBarType, message: String, duration: TimeInterval = 2, option: Option? = nil, completion: (() -> Void)? = nil) { + // Hide all before new one is shown. + alertBarViews.forEach({ $0.hide() }) + + let currentOption = option ?? self.option + let width = UIScreen.main.bounds.width let height = UIScreen.main.bounds.height let baseView = UIView(frame: UIScreen.main.bounds) - baseView.isUserInteractionEnabled = false - baseView.addSubview(alertBar) - let window: UIWindow let orientation = UIApplication.shared.statusBarOrientation if orientation.isLandscape { @@ -99,41 +79,163 @@ open class AlertBar: UIView { } else { window = UIWindow(frame: CGRect(x: 0, y: 0, width: width, height: height)) if orientation == .portraitUpsideDown { - baseView.transform = CGAffineTransform(rotationAngle: CGFloat.pi) + baseView.transform = CGAffineTransform(rotationAngle: .pi) } } window.isUserInteractionEnabled = false - window.windowLevel = UIWindowLevelStatusBar + 1 + CGFloat(AlertBar.alertBars.count) - window.addSubview(baseView) + window.windowLevel = AlertBar.kWindowLevel window.makeKeyAndVisible() - - alertBar.transform = CGAffineTransform(translationX: 0, y: -statusBarHeight) - UIView.animate(withDuration: 0.2, - animations: { () -> Void in - alertBar.transform = CGAffineTransform.identity - }, completion: { _ in - UIView.animate(withDuration: 0.2, - delay: duration, - options: UIViewAnimationOptions(), - animations: { () -> Void in - alertBar.transform = CGAffineTransform(translationX: 0, y: -statusBarHeight) - }, - completion: { (animated: Bool) -> Void in - alertBar.removeFromSuperview() - if let index = AlertBar.alertBars.index(of: alertBar) { - AlertBar.alertBars.remove(at: index) - } - // To hold window instance - window.isHidden = true - completion?() - }) - }) + baseView.isUserInteractionEnabled = false + window.addSubview(baseView) + + let safeArea: UIEdgeInsets + if #available(iOS 11.0, *) { + safeArea = window.safeAreaInsets + } else { + safeArea = .zero + } + let alertBarView = AlertBarView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 0)) + alertBarView.delegate = self + alertBarView.backgroundColor = type.backgroundColor + alertBarView.messageLabel.textColor = type.textColor + alertBarView.messageLabel.text = message + alertBarView.messageLabel.numberOfLines = currentOption.isStretchable ? 0 : 1 + alertBarView.messageLabel.textAlignment = currentOption.textAlignment + alertBarView.fit(safeArea: currentOption.shouldConsiderSafeArea ? safeArea : .zero) + alertBarViews.append(alertBarView) + baseView.addSubview(alertBarView) + + let statusBarHeight: CGFloat = max(UIApplication.shared.statusBarFrame.height, safeArea.top) + let alertBarHeight: CGFloat = max(statusBarHeight, alertBarView.frame.height) + alertBarView.show(duration: 2, translationY: -alertBarHeight) { + if let index = self.alertBarViews.index(of: alertBarView) { + self.alertBarViews.remove(at: index) + } + // To hold window instance + window.isHidden = true + completion?() + } } - - open class func show(error: Error, duration: Double = 2, completion: (() -> Void)? = nil) { + + public func show(error: Error, duration: TimeInterval = 2, option: Option? = nil, completion: (() -> Void)? = nil) { let code = (error as NSError).code let localizedDescription = error.localizedDescription - self.show(.error, message: "(\(code)) " + localizedDescription, duration: duration, completion: completion) + show(type: .error, message: "(\(code)) \(localizedDescription)", duration: duration, option: option, completion: completion) + } +} + +extension AlertBar: AlertBarViewDelegate { + func alertBarViewHandleRotate(_ alertBarView: AlertBarView) { + alertBarView.removeFromSuperview() + alertBarViews.forEach({ $0.hide() }) + alertBarViews = [] + } +} + +// MARK: - Static helpers + +public extension AlertBar { + public static func setDefault(option: Option) { + shared.option = option + } + + public static func show(type: AlertBarType, message: String, duration: TimeInterval = 2, option: Option? = nil, completion: (() -> Void)? = nil) { + shared.show(type: type, message: message, duration: duration, option: option, completion: completion) + } + + public static func show(error: Error, duration: TimeInterval = 2, option: Option? = nil, completion: (() -> Void)? = nil) { + shared.show(error: error, duration: duration, option: option, completion: completion) + } +} + +protocol AlertBarViewDelegate: class { + func alertBarViewHandleRotate(_ alertBarView: AlertBarView) +} + +internal class AlertBarView: UIView { + internal let messageLabel = UILabel() + internal weak var delegate: AlertBarViewDelegate? + + private enum State { + case showing + case shown + case hiding + case hidden + } + + private static let kMargin: CGFloat = 2 + private static let kAnimationDuration: TimeInterval = 0.2 + + private var translationY: CGFloat = 0 + private var completion: (() -> Void)? + private var state: State = .hidden + + required public init?(coder aDecoder: NSCoder) { + fatalError("NSCoding not supported") + } + + override init(frame: CGRect) { + super.init(frame: frame) + let margin = AlertBarView.kMargin + messageLabel.frame = CGRect(x: margin, y: margin, width: frame.width - margin*2, height: frame.height - margin*2) + messageLabel.font = UIFont.systemFont(ofSize: 12) + addSubview(messageLabel) + + NotificationCenter.default.addObserver(self, selector: #selector(self.handleRotate(_:)), name: .UIDeviceOrientationDidChange, object: nil) + } + + func fit(safeArea: UIEdgeInsets) { + let margin = AlertBarView.kMargin + messageLabel.sizeToFit() + messageLabel.frame.origin.x = margin + safeArea.left + messageLabel.frame.origin.y = margin + safeArea.top + messageLabel.frame.size.width = frame.size.width - margin*2 - safeArea.left - safeArea.right + frame.size.height = messageLabel.frame.origin.y + messageLabel.frame.height + margin*2 + } + + deinit { + NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil) + } + + func show(duration: TimeInterval, translationY: CGFloat, completion: (() -> Void)?) { + self.state = .showing + self.translationY = translationY + self.completion = completion + + transform = CGAffineTransform(translationX: 0, y: translationY) + UIView.animate( + withDuration: AlertBarView.kAnimationDuration, + animations: { () -> Void in + self.transform = .identity + }, completion: { _ in + self.state = .shown + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(Int(duration))) { + self.hide() + } + }) + } + + func hide() { + guard state == .showing || state == .shown else { + return + } + self.state = .hiding + // Hide animation + UIView.animate( + withDuration: AlertBarView.kAnimationDuration, + animations: { () -> Void in + self.transform = CGAffineTransform(translationX: 0, y: self.translationY) + }, + completion: { (animated: Bool) -> Void in + self.removeFromSuperview() + self.state = .hidden + self.completion?() + self.completion = nil + }) + } + + @objc private func handleRotate(_ notification: Notification) { + delegate?.alertBarViewHandleRotate(self) } } diff --git a/assets/isStretchable_false.png b/assets/isStretchable_false.png new file mode 100644 index 0000000..a8c65f4 Binary files /dev/null and b/assets/isStretchable_false.png differ diff --git a/assets/isStretchable_true.png b/assets/isStretchable_true.png new file mode 100644 index 0000000..e6f3d84 Binary files /dev/null and b/assets/isStretchable_true.png differ diff --git a/assets/shouldConsiderSafeArea_false.png b/assets/shouldConsiderSafeArea_false.png new file mode 100644 index 0000000..d0d9d0c Binary files /dev/null and b/assets/shouldConsiderSafeArea_false.png differ diff --git a/assets/shouldConsiderSafeArea_true.png b/assets/shouldConsiderSafeArea_true.png new file mode 100644 index 0000000..01e7739 Binary files /dev/null and b/assets/shouldConsiderSafeArea_true.png differ diff --git a/scripts/sort-Xcode-project-file b/scripts/sort-Xcode-project-file new file mode 100755 index 0000000..f87bb82 --- /dev/null +++ b/scripts/sort-Xcode-project-file @@ -0,0 +1,177 @@ +#!/usr/bin/perl -w + +# Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of Apple Inc. ("Apple") nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Script to sort "children" and "files" sections in Xcode project.pbxproj files + +use strict; + +use File::Basename; +use File::Spec; +use File::Temp qw(tempfile); +use File::Compare; +use Getopt::Long; + +sub sortChildrenByFileName($$); +sub sortFilesByFileName($$); + +# Files (or products) without extensions +my %isFile = map { $_ => 1 } qw( + create_hash_table + jsc + minidom + testapi + testjsglue +); + +my $printWarnings = 1; +my $showHelp; + +my $getOptionsResult = GetOptions( + 'h|help' => \$showHelp, + 'w|warnings!' => \$printWarnings, +); + +if (scalar(@ARGV) == 0 && !$showHelp) { + print STDERR "ERROR: No Xcode project files (project.pbxproj) listed on command-line.\n"; + undef $getOptionsResult; +} + +if (!$getOptionsResult || $showHelp) { + print STDERR <<__END__; +Usage: @{[ basename($0) ]} [options] path/to/project.pbxproj [path/to/project.pbxproj ...] + -h|--help show this help message + -w|--[no-]warnings show or suppress warnings (default: show warnings) +__END__ + exit 1; +} + +for my $projectFile (@ARGV) { + if (basename($projectFile) =~ /\.xcodeproj$/) { + $projectFile = File::Spec->catfile($projectFile, "project.pbxproj"); + } + + if (basename($projectFile) ne "project.pbxproj") { + print STDERR "WARNING: Not an Xcode project file: $projectFile\n" if $printWarnings; + next; + } + + # Grab the mainGroup for the project file + my $mainGroup = ""; + open(IN, "< $projectFile") || die "Could not open $projectFile: $!"; + while (my $line = ) { + $mainGroup = $2 if $line =~ m#^(\s*)mainGroup = ([0-9A-F]{24} /\* .+ \*/);$#; + } + close(IN); + + my ($OUT, $tempFileName) = tempfile( + basename($projectFile) . "-XXXXXXXX", + DIR => dirname($projectFile), + UNLINK => 0, + ); + + # Clean up temp file in case of die() + $SIG{__DIE__} = sub { + close(IN); + close($OUT); + unlink($tempFileName); + }; + + my @lastTwo = (); + open(IN, "< $projectFile") || die "Could not open $projectFile: $!"; + while (my $line = ) { + if ($line =~ /^(\s*)files = \(\s*$/) { + print $OUT $line; + my $endMarker = $1 . ");"; + my @files; + while (my $fileLine = ) { + if ($fileLine =~ /^\Q$endMarker\E\s*$/) { + $endMarker = $fileLine; + last; + } + push @files, $fileLine; + } + print $OUT sort sortFilesByFileName @files; + print $OUT $endMarker; + } elsif ($line =~ /^(\s*)children = \(\s*$/) { + print $OUT $line; + my $endMarker = $1 . ");"; + my @children; + while (my $childLine = ) { + if ($childLine =~ /^\Q$endMarker\E\s*$/) { + $endMarker = $childLine; + last; + } + push @children, $childLine; + } + if ($lastTwo[0] =~ m#^\s+\Q$mainGroup\E = \{$#) { + # Don't sort mainGroup + print $OUT @children; + } else { + print $OUT sort sortChildrenByFileName @children; + } + print $OUT $endMarker; + } else { + print $OUT $line; + } + + push @lastTwo, $line; + shift @lastTwo if scalar(@lastTwo) > 2; + } + close(IN); + close($OUT); + + if (compare($projectFile, $tempFileName) == 0) { + unlink($tempFileName); + } else { + unlink($projectFile) || die "Could not delete $projectFile: $!"; + rename($tempFileName, $projectFile) || die "Could not rename $tempFileName to $projectFile: $!"; + } +} + +exit 0; + +sub sortChildrenByFileName($$) +{ + my ($a, $b) = @_; + my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/; + my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/; + my $aSuffix = $1 if $aFileName =~ m/\.([^.]+)$/; + my $bSuffix = $1 if $bFileName =~ m/\.([^.]+)$/; + if ((!$aSuffix && !$isFile{$aFileName} && $bSuffix) || ($aSuffix && !$bSuffix && !$isFile{$bFileName})) { + return !$aSuffix ? -1 : 1; + } + return lc($aFileName) cmp lc($bFileName); +} + +sub sortFilesByFileName($$) +{ + my ($a, $b) = @_; + my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /; + my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /; + return lc($aFileName) cmp lc($bFileName); +} \ No newline at end of file