diff --git a/.swiftlint.yml b/.swiftlint.yml index bea1ed3..fc23e7c 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -20,7 +20,7 @@ type_body_length: - 400 # error file_length: - warning: 800 + warning: 1000 error: 1200 colon: diff --git a/.travis.yml b/.travis.yml index 4d13f2c..df9047a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ language: objective-c -osx_image: xcode8 +osx_image: xcode9 env: global: - PROJECT=Framezilla.xcodeproj - IOS_FRAMEWORK_SCHEME="Framezilla iOS" - - IOS_SDK=iphonesimulator10.0 + - IOS_SDK=iphonesimulator11.0 matrix: - - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" + - DESTINATION="OS=10.0,name=iPhone 7 Plus" + - DESTINATION="OS=11.0,name=iPhone 7 Plus" script: - set -o pipefail diff --git a/Example/FramezillaExample.xcodeproj/project.pbxproj b/Example/FramezillaExample.xcodeproj/project.pbxproj index 852087e..658de24 100644 --- a/Example/FramezillaExample.xcodeproj/project.pbxproj +++ b/Example/FramezillaExample.xcodeproj/project.pbxproj @@ -111,7 +111,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0830; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = "Nikita Ermolenko"; TargetAttributes = { 8442F94E1EC75C9C00B72551 = { @@ -193,7 +193,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -201,7 +203,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -244,7 +250,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -252,7 +260,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; diff --git a/Example/FramezillaExample/ViewController.swift b/Example/FramezillaExample/ViewController.swift index d1b1b05..7612c3c 100644 --- a/Example/FramezillaExample/ViewController.swift +++ b/Example/FramezillaExample/ViewController.swift @@ -22,63 +22,29 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + if #available(iOS 11.0, *) { + additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) + } + scrollView.backgroundColor = .yellow scrollView.contentSize = CGSize(width: 500, height: 1000) content1.backgroundColor = .red content2.backgroundColor = .green content3.backgroundColor = .black - - view.addSubview(scrollView) - scrollView.addSubview(content2) -// view.addSubview(content3) + view.backgroundColor = .yellow + view.addSubview(content1) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - -// content1.configureFrame { maker in -// maker.edges(top: 0, left: 0, right: 0) -// maker.height(50) -// } -// -// content2.configureFrame { maker in -// maker.edges(left: 0, bottom: 0, right: 0) -// maker.height(200) -// } -// -// content3.configureFrame { maker in -// maker.size(width: 20, height: 70) -// maker.centerY(between: content2, content1) -// maker.centerX() -// } - - -// content1.configureFrame { maker in -// maker.edges(top: 0, left: 0, bottom: 0) -// maker.width(50) -// } -// -// content2.configureFrame { maker in -// maker.edges(top: 0, bottom: 0, right: 0) -// maker.width(200) -// } -// -// content3.configureFrame { maker in -// maker.size(width: 20, height: 70) -// maker.centerX(between: content1, content2) -// maker.centerY() -// } - - scrollView.configureFrame { maker in - maker.margin(20) - } - content2.configureFrame { maker in - maker.size(width: 100, height: 100) - maker.left() - maker.centerY() + content1.configureFrame { maker in + maker.top(to: nui_safeArea) + maker.bottom(to: nui_safeArea) + maker.right(to: nui_safeArea) + maker.left(to: nui_safeArea) } } } diff --git a/Framezilla.podspec b/Framezilla.podspec index 1a527a9..b1a3c2c 100644 --- a/Framezilla.podspec +++ b/Framezilla.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "Framezilla" - spec.version = "2.2.1" + spec.version = "2.3.0" spec.summary = "Comfortable syntax for working with frames." spec.homepage = "https://github.com/Otbivnoe/Framezilla" diff --git a/Framezilla.xcodeproj/project.pbxproj b/Framezilla.xcodeproj/project.pbxproj index 1f48950..bb4482f 100644 --- a/Framezilla.xcodeproj/project.pbxproj +++ b/Framezilla.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 845108071EE2F5BC006DC1C8 /* ScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845108061EE2F5BC006DC1C8 /* ScrollViewTests.swift */; }; 8497C0111E59EB7700447E2F /* Number.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8497C0101E59EB7700447E2F /* Number.swift */; }; 8497C0131E59FA4B00447E2F /* Array+Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8497C0121E59FA4B00447E2F /* Array+Stack.swift */; }; + 84EDCC241F9B3AB10091FAB9 /* MakerSafeAreaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EDCC231F9B3AB10091FAB9 /* MakerSafeAreaTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,6 +64,7 @@ 845108061EE2F5BC006DC1C8 /* ScrollViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewTests.swift; sourceTree = ""; }; 8497C0101E59EB7700447E2F /* Number.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Number.swift; sourceTree = ""; }; 8497C0121E59FA4B00447E2F /* Array+Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Stack.swift"; sourceTree = ""; }; + 84EDCC231F9B3AB10091FAB9 /* MakerSafeAreaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakerSafeAreaTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -133,6 +135,7 @@ 8442F93A1EC75A8500B72551 /* MakerStackTests.swift */, 8442F93B1EC75A8500B72551 /* MakerTests.swift */, 8442F93C1EC75A8500B72551 /* MakerWidthHeightTests.swift */, + 84EDCC231F9B3AB10091FAB9 /* MakerSafeAreaTests.swift */, 8442F93D1EC75A8500B72551 /* StateTests.swift */, 845108061EE2F5BC006DC1C8 /* ScrollViewTests.swift */, ); @@ -197,16 +200,16 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0800; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0900; TargetAttributes = { 115972131D8450F500BC5C20 = { CreatedOnToolsVersion = 8.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; ProvisioningStyle = Manual; }; 11FB41311D844D2B00700A40 = { CreatedOnToolsVersion = 8.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; }; @@ -276,6 +279,7 @@ 845108071EE2F5BC006DC1C8 /* ScrollViewTests.swift in Sources */, 8442F9431EC75A8500B72551 /* MakerTests.swift in Sources */, 8442F93E1EC75A8500B72551 /* BaseTest.swift in Sources */, + 84EDCC241F9B3AB10091FAB9 /* MakerSafeAreaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -358,7 +362,8 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "Tests/FramezillaTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -407,7 +412,8 @@ SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = "Tests/FramezillaTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -415,12 +421,18 @@ 11FB412B1D844CD800700A40 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -440,12 +452,18 @@ 11FB412C1D844CD800700A40 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -518,7 +536,8 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -575,7 +594,8 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; diff --git a/Framezilla.xcodeproj/xcshareddata/xcschemes/Framezilla iOS.xcscheme b/Framezilla.xcodeproj/xcshareddata/xcschemes/Framezilla iOS.xcscheme index 4e62dcf..c462db0 100644 --- a/Framezilla.xcodeproj/xcshareddata/xcschemes/Framezilla iOS.xcscheme +++ b/Framezilla.xcodeproj/xcshareddata/xcschemes/Framezilla iOS.xcscheme @@ -1,6 +1,6 @@ + language = "" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> @@ -55,6 +57,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/README.md b/README.md index 899c6b2..892278e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Version](https://img.shields.io/cocoapods/v/Framezilla.svg?style=flat)](http://cocoadocs.org/docsets/Framezilla) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](https://img.shields.io/cocoapods/p/Framezilla.svg?style=flat)](http://cocoadocs.org/docsets/Framezilla) -![Swift 3.0.x](https://img.shields.io/badge/Swift-3.0.x-orange.svg) +![Swift 4.0](https://img.shields.io/badge/Swift-4.0-orange.svg) [![License](https://img.shields.io/cocoapods/l/Framezilla.svg?style=flat)](http://cocoadocs.org/docsets/Framezilla) **Everyone wants to see a smooth scrolling, that tableview or collectionview scrolls without any lags and it's a right choice. But the constraints do not give it for us. Therefore, we have to choose manual calculation frames, but sometimes, when cell has a complex structure, code has not an elegant, beautiful structure.** @@ -69,6 +69,7 @@ Run `carthage update` to build the framework and drag the built `Framezilla.fram - [x] Optional semantic - `and` - [x] Side relations: `nui_left`, `nui_bottom`, `nui_width`, `nui_centerX` and so on. - [x] States +- [x] Safe area support 😱 # Usage :rocket: @@ -152,6 +153,21 @@ Also possible to create relations with another view, not a superview: } ``` +In iOS 11 Apple has introduced the safe area, similar to `topLayoutGuide` and `bottomLayoutGuide`. Framezilla supports this new api as well: + +```swift +content.configureFrame { maker in + maker.top(to: nui_safeArea) + maker.bottom(to: nui_safeArea) + maker.right(to: nui_safeArea, inset: 10) + maker.left(to: nui_safeArea, inset: 10) +} +``` + + + +**Note**: In earlier versions of OS than iOS 11, these methods create a relation to a superview, not the safe area. + ## Center relations If you just want to center subview relative superview with constant `width` and `height`, this approach specially for you: diff --git a/Sources/Maker.swift b/Sources/Maker.swift index a278780..ce4bf91 100644 --- a/Sources/Maker.swift +++ b/Sources/Maker.swift @@ -14,6 +14,11 @@ enum HandlerPriority: Int { case low } +public struct SafeArea {} +public var nui_safeArea: SafeArea { + return SafeArea() +} + public final class Maker { typealias HandlerType = () -> Void @@ -244,6 +249,30 @@ public final class Maker { return left(to: RelationView(view: superview, relation: .left), inset: inset) } + /// Creates a left relation to the superview's safe area. + /// + /// Use this method when you want to join a left side of current view with left edge of the superview's safe area. + /// + /// - note: In earlier versions of OS than iOS 11, it creates a left relation to a superview. + /// + /// - parameter safeArea: The safe area of current view. Use a `nui_safeArea` - global property. + /// - parameter inset: The inset for additional space to safe area. Default value: 0. + /// + /// - returns: `Maker` instance for chaining relations. + + @discardableResult public func left(to safeArea: SafeArea, inset: Number = 0.0) -> Maker { + if #available(iOS 11.0, *) { + guard let superview = view.superview else { + assertionFailure("Can not configure a left relation to the safe area without superview.") + return self + } + return left(inset: superview.safeAreaInsets.left + inset.value) + } + else { + return left(inset: inset) + } + } + /// Creates left relation. /// /// Use this method when you want to join left side of current view with some horizontal side of another view. @@ -278,12 +307,36 @@ public final class Maker { @discardableResult public func top(inset: Number = 0.0) -> Maker { guard let superview = view.superview else { - assertionFailure("Can not configure top relation to superview without superview.") + assertionFailure("Can not configure a top relation to superview without superview.") return self } return top(to: RelationView(view: superview, relation: .top), inset: inset.value) } - + + /// Creates a top relation to the superview's safe area. + /// + /// Use this method when you want to join a top side of current view with top edge of the superview's safe area. + /// + /// - note: In earlier versions of OS than iOS 11, it creates a top relation to a superview. + /// + /// - parameter safeArea: The safe area of current view. Use a `nui_safeArea` - global property. + /// - parameter inset: The inset for additional space to safe area. Default value: 0. + /// + /// - returns: `Maker` instance for chaining relations. + + @discardableResult public func top(to safeArea: SafeArea, inset: Number = 0.0) -> Maker { + if #available(iOS 11.0, *) { + guard let superview = view.superview else { + assertionFailure("Can not configure a top relation to the safe area without superview.") + return self + } + return top(inset: superview.safeAreaInsets.top + inset.value) + } + else { + return top(inset: inset) + } + } + /// Creates top relation. /// /// Use this method when you want to join top side of current view with some vertical side of another view. @@ -422,11 +475,35 @@ public final class Maker { @discardableResult public func bottom(inset: Number = 0.0) -> Maker { guard let superview = view.superview else { - assertionFailure("Can not configure bottom relation to superview without superview.") + assertionFailure("Can not configure a bottom relation to superview without superview.") return self } return bottom(to: RelationView(view: superview, relation: .bottom), inset: inset) } + + /// Creates a bottom relation to the superview's safe area. + /// + /// Use this method when you want to join a bottom side of current view with bottom edge of the superview's safe area. + /// + /// - note: In earlier versions of OS than iOS 11, it creates a bottom relation to a superview. + /// + /// - parameter safeArea: The safe area of current view. Use a `nui_safeArea` - global property. + /// - parameter inset: The inset for additional space to safe area. Default value: 0. + /// + /// - returns: `Maker` instance for chaining relations. + + @discardableResult public func bottom(to safeArea: SafeArea, inset: Number = 0.0) -> Maker { + if #available(iOS 11.0, *) { + guard let superview = view.superview else { + assertionFailure("Can not configure a bottom relation to the safe area without superview.") + return self + } + return bottom(inset: superview.safeAreaInsets.bottom + inset.value) + } + else { + return bottom(inset: inset) + } + } /// Creates bottom relation. /// @@ -468,12 +545,36 @@ public final class Maker { @discardableResult public func right(inset: Number = 0.0) -> Maker { guard let superview = view.superview else { - assertionFailure("Can not configure right relation to superview without superview.") + assertionFailure("Can not configure a right relation to superview without superview.") return self } return right(to: RelationView(view: superview, relation: .right), inset: inset.value) } - + + /// Creates a right relation to the superview's safe area. + /// + /// Use this method when you want to join a right side of current view with right edge of the superview's safe area. + /// + /// - note: In earlier versions of OS than iOS 11, it creates a right relation to a superview. + /// + /// - parameter safeArea: The safe area of current view. Use a `nui_safeArea` - global property. + /// - parameter inset: The inset for additional space to safe area. Default value: 0. + /// + /// - returns: `Maker` instance for chaining relations. + + @discardableResult public func right(to safeArea: SafeArea, inset: Number = 0.0) -> Maker { + if #available(iOS 11.0, *) { + guard let superview = view.superview else { + assertionFailure("Can not configure a right relation to the safe area without superview.") + return self + } + return right(inset: superview.safeAreaInsets.right + inset.value) + } + else { + return right(inset: inset) + } + } + /// Creates right relation. /// /// Use this method when you want to join right side of current view with some horizontal side of another view. @@ -514,7 +615,7 @@ public final class Maker { @discardableResult public func center() -> Maker { guard let superview = view.superview else { - assertionFailure("Can not configure center relation to superview without superview.") + assertionFailure("Can not configure a center relation to superview without superview.") return self } return center(to: superview) @@ -543,7 +644,7 @@ public final class Maker { @discardableResult public func centerY(offset: Number = 0.0) -> Maker { guard let superview = view.superview else { - assertionFailure("Can not configure centerY relation to superview without superview.") + assertionFailure("Can not configure a centerY relation to superview without superview.") return self } return centerY(to: RelationView(view: superview, relation: .centerY), offset: offset.value) @@ -606,7 +707,7 @@ public final class Maker { @discardableResult public func centerX(offset: Number = 0.0) -> Maker { guard let superview = view.superview else { - assertionFailure("Can not configure centerX relation to superview without superview.") + assertionFailure("Can not configure a centerX relation to superview without superview.") return self } return centerX(to: RelationView(view: superview, relation: .centerX), offset: offset.value) diff --git a/Sources/MakerHelper.swift b/Sources/MakerHelper.swift index d851bae..849454a 100644 --- a/Sources/MakerHelper.swift +++ b/Sources/MakerHelper.swift @@ -31,7 +31,12 @@ extension Maker { if let superview = self.view.superview, superview === view, superview.superview == nil { return CGRect(origin: .zero, size: superview.frame.size) } - return self.view.superview!.convert(view.frame, from: view.superview) + + if let supervew = self.view.superview { + return supervew.convert(view.frame, from: view.superview) + } + assertionFailure("Can't configure a frame for view: \(self.view) without superview.") + return .zero } var convertedRect = rect diff --git a/Tests/MakerSafeAreaTests.swift b/Tests/MakerSafeAreaTests.swift new file mode 100644 index 0000000..d12fa19 --- /dev/null +++ b/Tests/MakerSafeAreaTests.swift @@ -0,0 +1,248 @@ +// +// MakerSafeAreaTests.swift +// FramezillaTests +// +// Created by Nikita Ermolenko on 21/10/2017. +// + +import XCTest +import Framezilla + +class MakerSafeAreaTests: XCTestCase { + + private let viewController = UIViewController() + + override func setUp() { + super.setUp() + viewController.view.frame = CGRect(x: 0, y: 0, width: 300, height: 500) + + let window = UIWindow() + window.rootViewController = viewController + window.isHidden = false + } + + override func tearDown() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets = .zero + } + super.tearDown() + } + + /* top */ + + func testThatCorrectlyConfigures_top_to_SafeArea() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.top = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + view.configureFrame { maker in + maker.top(to: nui_safeArea) + maker.left() + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: 0, y: viewController.additionalSafeAreaInsets.top, width: 20, height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: 0, y: 0, width: 20, height: 20)) + } + } + + func testThatCorrectlyConfigures_top_to_SafeAreaWithInset() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.top = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + let inset: CGFloat = 5 + view.configureFrame { maker in + maker.top(to: nui_safeArea, inset: inset) + maker.left() + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: 0, y: viewController.additionalSafeAreaInsets.top + inset, width: 20, height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: 0, y: inset, width: 20, height: 20)) + } + } + + /* left */ + + func testThatCorrectlyConfigures_left_to_SafeArea() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.left = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + view.configureFrame { maker in + maker.top() + maker.left(to: nui_safeArea) + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: viewController.additionalSafeAreaInsets.left, y: 0, width: 20, height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: 0, y: 0, width: 20, height: 20)) + } + } + + func testThatCorrectlyConfigures_left_to_SafeAreaWithInset() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.left = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + let inset: CGFloat = 5 + view.configureFrame { maker in + maker.top() + maker.left(to: nui_safeArea, inset: inset) + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: viewController.additionalSafeAreaInsets.left + inset, + y: 0, + width: 20, + height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: inset, + y: 0, + width: 20, + height: 20)) + } + } + + /* bottom */ + + func testThatCorrectlyConfigures_bottom_to_SafeArea() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.bottom = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + view.configureFrame { maker in + maker.bottom(to: nui_safeArea) + maker.left() + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: 0, + y: viewController.view.frame.height - viewController.additionalSafeAreaInsets.bottom - 20, + width: 20, + height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: 0, + y: viewController.view.frame.height - 20, + width: 20, + height: 20)) + } + } + + func testThatCorrectlyConfigures_bottom_to_SafeAreaWithInset() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.bottom = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + let inset: CGFloat = 5 + view.configureFrame { maker in + maker.bottom(to: nui_safeArea, inset: inset) + maker.left() + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: 0, + y: viewController.view.frame.height - viewController.additionalSafeAreaInsets.bottom - 20 - inset, + width: 20, + height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: 0, + y: viewController.view.frame.height - 20 - inset, + width: 20, + height: 20)) + } + } + + /* right */ + + func testThatCorrectlyConfigures_right_to_SafeArea() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.right = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + view.configureFrame { maker in + maker.right(to: nui_safeArea) + maker.bottom() + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: viewController.view.frame.width - viewController.additionalSafeAreaInsets.right - 20, + y: viewController.view.frame.height - 20, + width: 20, + height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: viewController.view.frame.width - 20, + y: viewController.view.frame.height - 20, + width: 20, + height: 20)) + } + } + + func testThatCorrectlyConfigures_right_to_SafeAreaWithInset() { + if #available(iOS 11.0, *) { + viewController.additionalSafeAreaInsets.right = 10 + } + + let view = UIView() + viewController.view.addSubview(view) + + let inset: CGFloat = 5 + view.configureFrame { maker in + maker.right(to: nui_safeArea, inset: inset) + maker.bottom() + maker.size(width: 20, height: 20) + } + + if #available(iOS 11.0, *) { + XCTAssertEqual(view.frame, CGRect(x: viewController.view.frame.width - viewController.additionalSafeAreaInsets.right - 20 - inset, + y: viewController.view.frame.height - 20, + width: 20, + height: 20)) + } + else { + XCTAssertEqual(view.frame, CGRect(x: viewController.view.frame.width - 20 - inset, + y: viewController.view.frame.height - 20, + width: 20, + height: 20)) + } + } +} diff --git a/img/safe_area.png b/img/safe_area.png new file mode 100644 index 0000000..bd5eef4 Binary files /dev/null and b/img/safe_area.png differ