├── Cartfile ├── Cartfile.resolved ├── Gemfile ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Layoutable.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── LayoutableTests.xcscheme │ │ └── Layoutable.xcscheme └── project.pbxproj ├── Package.resolved ├── Layoutable ├── Layoutable.h ├── Info.plist └── Sources │ ├── LayoutEngine.swift │ ├── LayoutProperty.swift │ ├── LayoutValues.swift │ ├── LayoutManager.swift │ ├── LayoutConstraint.swift │ ├── LayoutAnchor.swift │ ├── CompositAnchor.swift │ └── Layoutable.swift ├── .travis.yml ├── Package.swift ├── LayoutableTests ├── Info.plist ├── TestNode.swift ├── LayoutTest.swift └── PerformanceTest.swift ├── LICENSE ├── SwiftLayoutable.podspec ├── .gitignore └── README.md /Cartfile: -------------------------------------------------------------------------------- 1 | github "https://github.com/nangege/Cassowary" "master" 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "nangege/Cassowary" "fdc596978d9daff5b4da6640aa3666b0519cdc7f" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "fastlane" 6 | gem "jazzy" 7 | gem "cocoapods", "~>1.6.beta" 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Layoutable.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Layoutable.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Cassowary", 6 | "repositoryURL": "git@github.com:nangege/Cassowary.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "c5c93c28853a56bc484fa59696e9d0ee80ec9d0e", 10 | "version": null 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Layoutable/Layoutable.h: -------------------------------------------------------------------------------- 1 | // 2 | // Layoutable.h 3 | // Layoutable 4 | // 5 | // Created by nangezao on 2018/8/31. 6 | // Copyright © 2018 Tang Nan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Layoutable. 12 | FOUNDATION_EXPORT double LayoutableVersionNumber; 13 | 14 | //! Project version string for Layoutable. 15 | FOUNDATION_EXPORT const unsigned char LayoutableVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | os: osx 3 | osx_image: xcode10 4 | 5 | env: 6 | matrix: 7 | - TEST_TYPE=iOS 8 | 9 | -before_install: 10 | - | 11 | gem install xcpretty -N --no-ri --no-rdoc 12 | brew update 13 | brew outdated carthage || brew upgrade carthage 14 | script: 15 | - | 16 | if [ "$TEST_TYPE" = iOS ]; then 17 | set -o pipefail 18 | carthage update 19 | xcodebuild clean test -project Layoutable.xcodeproj -scheme Layoutable -destination 'platform=iOS Simulator,name=iPhone 7,OS=12.0' -enableCodeCoverage YES | bundle exec xcpretty 20 | fi 21 | after_success: 22 | - sleep 5 23 | - if [ "$TEST_TYPE" = iOS ] ; then 24 | bash <(curl -s https://codecov.io/bash) 25 | fi 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Layoutable", 7 | products: [ 8 | .library( 9 | name: "Layoutable", 10 | targets: ["Layoutable"] 11 | ) 12 | ], 13 | dependencies: [.package(url: "git@github.com:nangege/Cassowary.git", branch:"master")], 14 | targets: [ 15 | .target( 16 | name: "Layoutable", 17 | dependencies: ["Cassowary"], 18 | path: "Layoutable/Sources" 19 | ), 20 | .testTarget( 21 | name: "LayoutableTests", 22 | dependencies: ["Layoutable"], 23 | path: "LayoutableTests" 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /LayoutableTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Layoutable/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /LayoutableTests/TestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestNode.swift 3 | // LayoutableTests 4 | // 5 | // Created by nangezao on 2018/9/4. 6 | // Copyright © 2018 Tang Nan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import Layoutable 11 | 12 | class TestNode: Layoutable{ 13 | 14 | public init() {} 15 | 16 | lazy var layoutManager = LayoutManager(self) 17 | 18 | var layoutSize = CGSize(width: InvalidIntrinsicMetric, height: InvalidIntrinsicMetric) 19 | 20 | weak var superItem: Layoutable? = nil 21 | 22 | var subItems = [Layoutable]() 23 | 24 | func addSubnode(_ node: TestNode){ 25 | subItems.append(node) 26 | node.superItem = self 27 | } 28 | 29 | func layoutSubItems() {} 30 | 31 | func updateConstraint() {} 32 | 33 | var layoutRect: CGRect = .zero 34 | 35 | var itemIntrinsicContentSize: CGSize{ 36 | return layoutSize 37 | } 38 | 39 | func contentSizeFor(maxWidth: CGFloat) -> CGSize { 40 | return InvaidIntrinsicSize 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 nangege 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LayoutableTests/LayoutTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutTest.swift 3 | // LayoutableTests 4 | // 5 | // Created by nangezao on 2018/10/10. 6 | // Copyright © 2018 Tang Nan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Layoutable 11 | 12 | class LayoutTest: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | 23 | func testExample() { 24 | let node = TestNode() 25 | let node1 = TestNode() 26 | let node2 = TestNode() 27 | 28 | node.addSubnode(node1) 29 | node.addSubnode(node2) 30 | 31 | node1.size == (30,30) 32 | node2.size == (40,40) 33 | 34 | [node,node1].equal(.centerY,.left) 35 | 36 | [node2,node].equal(.top,.bottom,.centerY,.right) 37 | 38 | [node1,node2].space(10, axis: .horizontal) 39 | 40 | node.layoutIfEnabled() 41 | 42 | XCTAssertEqual(node1.layoutRect, CGRect(x: 0, y: 5, width: 30, height: 30)) 43 | XCTAssertEqual(node2.layoutRect, CGRect(x: 40, y: 0, width: 40, height: 40)) 44 | XCTAssertEqual(node.layoutRect, CGRect(x: 0, y: 0, width: 80, height: 40)) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /SwiftLayoutable.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "SwiftLayoutable" 4 | s.version = "0.1-beta" 5 | s.summary = "Swift reimplement of Autolayout" 6 | 7 | s.description = <<-DESC 8 | Layoutable is a swift reimplement of apple's Autolayout. 9 | * It uses the same Cassowary algorithm as it's core and provides a set of api similar to Autolayout. 10 | * The difference is that Layouable is more flexable and easy to use.Layoutable don't rely on UIView, it can be used in any object that conform to Layoutable protocol such as CALaye or self defined object. 11 | * It can be used in background thread which is the core benefit of Layoutable. Layoutable also provides high level api and syntax sugar to make it easy to use. 12 | DESC 13 | 14 | s.homepage = "https://github.com/nangege/Layoutable" 15 | 16 | s.license = { :type => "MIT", :file => "LICENSE" } 17 | 18 | s.authors = { "nangege" => "1543640002@qq.com" } 19 | 20 | s.swift_version = "4.2" 21 | 22 | s.ios.deployment_target = "8.0" 23 | s.module_name = "Layoutable" 24 | 25 | 26 | s.source = { :git => "https://github.com/nangege/Layoutable.git", :tag => '0.1-beta' } 27 | s.source_files = ["Layoutable/Sources/*.swift", "Layoutable/Layoutable.h"] 28 | s.public_header_files = ["Layoutable/Layoutable.h"] 29 | 30 | 31 | s.requires_arc = true 32 | 33 | s.dependency 'SwiftCassowary', '~> 0.1-beta' 34 | 35 | end 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | *.DS_Store 71 | -------------------------------------------------------------------------------- /LayoutableTests/PerformanceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutableTests.swift 3 | // LayoutableTests 4 | // 5 | // Created by nangezao on 2018/8/31. 6 | // Copyright © 2018 Tang Nan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Layoutable 11 | 12 | class LayoutableTests: XCTestCase { 13 | 14 | func testNestPerformance() { 15 | let testNumber = 100 16 | self.measure { 17 | let node = TestNode() 18 | var nodes = [TestNode]() 19 | node.size == (320.0,640.0) 20 | for index in 0..= 0 57 | newNode.right <= node.right 58 | 59 | newNode.top >= 20 60 | newNode.bottom <= node.bottom - 20 61 | 62 | newNode.left == leftNode.left + CGFloat(arc4random()%20) 63 | node.top == rightNode.top + CGFloat(arc4random()%20) ~ .strong 64 | 65 | nodes.append(newNode) 66 | } 67 | node.layoutIfEnabled() 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Layoutable.xcodeproj/xcshareddata/xcschemes/LayoutableTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Layoutable/Sources/LayoutEngine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutEngine.swift 3 | // Cassowary 4 | // 5 | // Created by Tang,Nan(MAD) on 2018/3/19. 6 | // Copyright © 2018年 nange. All rights reserved. 7 | // 8 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 9 | /// of this software and associated documentation files (the "Software"), to deal 10 | /// in the Software without restriction, including without limitation the rights 11 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | /// copies of the Software, and to permit persons to whom the Software is 13 | /// furnished to do so, subject to the following conditions: 14 | /// 15 | /// The above copyright notice and this permission notice shall be included in 16 | /// all copies or substantial portions of the Software. 17 | /// 18 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 19 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 20 | /// Software in any work that is designed, intended, or marketed for pedagogical or 21 | /// instructional purposes related to programming, coding, application development, 22 | /// or information technology. Permission for such use, copying, modification, 23 | /// merger, publication, distribution, sublicensing, creation of derivative works, 24 | /// or sale is expressly withheld. 25 | /// 26 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | /// THE SOFTWARE. 33 | 34 | import Cassowary 35 | import Foundation 36 | 37 | final class LayoutEngine{ 38 | 39 | static let solverPool = NSMapTable.weakToStrongObjects() 40 | static func solveFor(_ node: Layoutable) -> SimplexSolver{ 41 | if let solver = solverPool.object(forKey: node){ 42 | return solver 43 | }else{ 44 | let solver = SimplexSolver() 45 | solver.autoSolve = false 46 | solverPool.setObject(solver, forKey: node) 47 | return solver 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Layoutable/Sources/LayoutProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Layout.swift 3 | // 4 | // Created by nangezao on 2017/7/19. 5 | // Copyright © 2017年 nange. All rights reserved. 6 | // 7 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 8 | /// of this software and associated documentation files (the "Software"), to deal 9 | /// in the Software without restriction, including without limitation the rights 10 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | /// copies of the Software, and to permit persons to whom the Software is 12 | /// furnished to do so, subject to the following conditions: 13 | /// 14 | /// The above copyright notice and this permission notice shall be included in 15 | /// all copies or substantial portions of the Software. 16 | /// 17 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 18 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 19 | /// Software in any work that is designed, intended, or marketed for pedagogical or 20 | /// instructional purposes related to programming, coding, application development, 21 | /// or information technology. Permission for such use, copying, modification, 22 | /// merger, publication, distribution, sublicensing, creation of derivative works, 23 | /// or sale is expressly withheld. 24 | /// 25 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | /// THE SOFTWARE. 32 | 33 | import Cassowary 34 | import CoreGraphics 35 | 36 | final class LayoutProperty{ 37 | 38 | let x = Variable() 39 | let y = Variable() 40 | let width = Variable.restricted() 41 | let height = Variable.restricted() 42 | 43 | weak var solver: SimplexSolver? 44 | 45 | var frame: CGRect{ 46 | guard let solver = solver else{ 47 | return .zero 48 | } 49 | let minX = solver.valueFor(x) 50 | let minY = solver.valueFor(y) 51 | let w = solver.valueFor(width) 52 | let h = solver.valueFor(height) 53 | return CGRect(x: minX ?? 0, y: minY ?? 0, width: w ?? 0, height: h ?? 0) 54 | } 55 | 56 | func expressionFor(attribue: LayoutAttribute) -> Expression{ 57 | switch attribue { 58 | case .left: 59 | return Expression(x) 60 | case .top: 61 | return Expression(y) 62 | case .right: 63 | return x + width 64 | case .bottom: 65 | return y + height 66 | case .width: 67 | return Expression(width) 68 | case .height: 69 | return Expression(height) 70 | case .centerX: 71 | return width/2 + x 72 | case .centerY: 73 | return height/2 + y 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Layoutable/Sources/LayoutValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutCache.swift 3 | // Cassowary 4 | // 5 | // Created by nangezao on 2017/12/7. 6 | // Copyright © 2017年 nange. All rights reserved. 7 | // 8 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 9 | /// of this software and associated documentation files (the "Software"), to deal 10 | /// in the Software without restriction, including without limitation the rights 11 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | /// copies of the Software, and to permit persons to whom the Software is 13 | /// furnished to do so, subject to the following conditions: 14 | /// 15 | /// The above copyright notice and this permission notice shall be included in 16 | /// all copies or substantial portions of the Software. 17 | /// 18 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 19 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 20 | /// Software in any work that is designed, intended, or marketed for pedagogical or 21 | /// instructional purposes related to programming, coding, application development, 22 | /// or information technology. Permission for such use, copying, modification, 23 | /// merger, publication, distribution, sublicensing, creation of derivative works, 24 | /// or sale is expressly withheld. 25 | /// 26 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | /// THE SOFTWARE. 33 | 34 | import CoreFoundation 35 | 36 | // use tuple as data struct to make it easy to write 37 | // like node.size = (30,30) compare to UIKit node.size = CGSize(width: height: 30) 38 | public typealias Value = CGFloat 39 | public typealias Size = (width: Value, height: Value) 40 | public typealias Point = (x: Value, y: Value) 41 | public typealias Offset = (x: Value, y: Value) 42 | public typealias Insets = (top: Value,left: Value, bottom: Value,right: Value) 43 | public typealias XSideInsets = (left: Value, right: Value) 44 | public typealias YSideInsets = (top: Value, bottom: Value) 45 | public typealias EdgeInsets = (top: Value, left: Value, bottom: Value, right: Value) 46 | 47 | //public typealias Rect = (origin: Point, size: Size) 48 | // 49 | public let InvalidIntrinsicMetric: CGFloat = -1 50 | // 51 | public let InvaidIntrinsicSize = CGSize(width: InvalidIntrinsicMetric, 52 | height: InvalidIntrinsicMetric) 53 | 54 | public let EdgeInsetsZero: EdgeInsets = (0,0,0,0) 55 | 56 | //public let RectZero: Rect = ((0,0),(0,0)) 57 | 58 | public let OffsetZero: Offset = (0,0) 59 | 60 | public let SizeZero: Size = (0,0) 61 | 62 | public struct LayoutValues{ 63 | public var frame = CGRect.zero 64 | public var subLayout = [LayoutValues]() 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Layoutable.xcodeproj/xcshareddata/xcschemes/Layoutable.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Layoutable/Sources/LayoutManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutManager.swift 3 | // Layoutable 4 | // 5 | // Created by nangezao on 2018/9/21. 6 | // Copyright © 2018 Tang Nan. All rights reserved. 7 | // 8 | // 9 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 10 | /// of this software and associated documentation files (the "Software"), to deal 11 | /// in the Software without restriction, including without limitation the rights 12 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | /// copies of the Software, and to permit persons to whom the Software is 14 | /// furnished to do so, subject to the following conditions: 15 | /// 16 | /// The above copyright notice and this permission notice shall be included in 17 | /// all copies or substantial portions of the Software. 18 | /// 19 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 20 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 21 | /// Software in any work that is designed, intended, or marketed for pedagogical or 22 | /// instructional purposes related to programming, coding, application development, 23 | /// or information technology. Permission for such use, copying, modification, 24 | /// merger, publication, distribution, sublicensing, creation of derivative works, 25 | /// or sale is expressly withheld. 26 | /// 27 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 33 | /// THE SOFTWARE. 34 | 35 | import Cassowary 36 | import CoreGraphics 37 | 38 | 39 | /// LayoutManager hold and handle all the properties needed for layout 40 | /// like SimplexSolver, LayoutProperty ... 41 | /// so that the class conform to LayoutItem does not need to provide those properties 42 | final public class LayoutManager{ 43 | 44 | public init(_ item: Layoutable){ 45 | self.item = item 46 | } 47 | 48 | weak var item: Layoutable? 49 | 50 | weak var solver: SimplexSolver? 51 | 52 | var variable = LayoutProperty() 53 | 54 | // This property is used to adjust position after layout pass 55 | // It is useful for simplefy layout for irregular layout 56 | public var offset = OffsetZero 57 | 58 | var fixedWidth = false 59 | 60 | /// just like translateAutoSizingMaskIntoConstraints in autolayout 61 | /// if true, current frame of this item will be added to layout engine 62 | var translateRectIntoConstraints = true 63 | 64 | var enabled = true 65 | 66 | /// to indicator whether contentSize of this item is changed 67 | /// if true, contentSize constraints will be updated 68 | var layoutNeedsUpdate = false 69 | 70 | /// used to track all constraints that secondAnchor.item is self 71 | var pinedConstraints = Set() 72 | 73 | /// constraints for this item that had been added to solver 74 | var installedConstraints = Set() 75 | 76 | /// constraints for this item that need to be added to solver 77 | var newlyAddedConstraints = Set() 78 | 79 | /// size constraints for contentSize 80 | let contentSizeConstraints = ContentSizeConstraints() 81 | 82 | // size constraints used for frame translated constraint 83 | let sizeConstraints = SizeConstraints() 84 | 85 | /// position constraints used for frame translated constraint 86 | let positionConstraints = PositionConstraints() 87 | 88 | /// whether this item should constrainted by rect translated constraints 89 | var isRectConstrainted: Bool{ 90 | return translateRectIntoConstraints && !pinedConstraints.isEmpty 91 | } 92 | 93 | var isConstraintValidRect: Bool{ 94 | return solver != nil && !translateRectIntoConstraints 95 | } 96 | 97 | var sizeNeedsUpdate: Bool{ 98 | return layoutNeedsUpdate && !translateRectIntoConstraints 99 | } 100 | 101 | func addConstraintsTo(_ solver: SimplexSolver){ 102 | 103 | // maybe need optiomize 104 | // find a better way to manager constraint cycle 105 | // when to add ,when to remove 106 | self.solver = solver 107 | variable.solver = solver 108 | installedConstraints.forEach{ $0.addToSolver(solver)} 109 | updateConstraint() 110 | } 111 | 112 | /// add new constraints to current solver 113 | func updateConstraint(){ 114 | if let solver = self.solver{ 115 | newlyAddedConstraints.forEach { 116 | $0.addToSolver(solver) 117 | installedConstraints.insert($0) 118 | } 119 | newlyAddedConstraints.removeAll() 120 | } 121 | } 122 | 123 | func addConstraint(_ constraint: LayoutConstraint){ 124 | newlyAddedConstraints.insert(constraint) 125 | } 126 | 127 | func removeConstraint(_ constraint: LayoutConstraint){ 128 | newlyAddedConstraints.remove(constraint) 129 | installedConstraints.remove(constraint) 130 | } 131 | 132 | func updateSize(_ size: CGSize){ 133 | guard let item = item else { return } 134 | contentSizeConstraints.updateSize(size, node: item) 135 | } 136 | 137 | func updateRect(_ rect: CGRect){ 138 | guard let item = item else { return } 139 | sizeConstraints.updateSize(rect.size, node: item) 140 | positionConstraints.updateOrigin(rect.origin, node: item) 141 | } 142 | 143 | /// final caculated rect for this item 144 | var layoutRect: CGRect{ 145 | return variable.frame 146 | } 147 | } 148 | 149 | final class ContentSizeConstraints{ 150 | 151 | class Axis{ 152 | var huggingPriorty = LayoutPriority.medium{ 153 | didSet{ 154 | if let hugging = hugging{ 155 | hugging.priority = huggingPriorty 156 | } 157 | } 158 | } 159 | 160 | var compressionPriorty = LayoutPriority.strong{ 161 | didSet{ 162 | if let compression = compression{ 163 | compression.priority = compressionPriorty 164 | } 165 | } 166 | } 167 | 168 | var hugging: LayoutConstraint? 169 | var compression: LayoutConstraint? 170 | } 171 | 172 | var xAxis = Axis() 173 | var yAxis = Axis() 174 | 175 | /// update content size Constraint 176 | func updateSize(_ size: CGSize,node: Layoutable){ 177 | 178 | if size.width != InvalidIntrinsicMetric{ 179 | if let width = xAxis.hugging, 180 | let widthCompression = xAxis.compression{ 181 | 182 | widthCompression.constant = size.width 183 | width.constant = size.width 184 | 185 | }else{ 186 | xAxis.compression = node.width >= size.width ~ xAxis.compressionPriorty 187 | xAxis.hugging = node.width <= size.width ~ xAxis.huggingPriorty 188 | } 189 | } 190 | 191 | if size.height != InvalidIntrinsicMetric{ 192 | if let height = yAxis.hugging, 193 | let heightCompression = yAxis.compression{ 194 | 195 | heightCompression.constant = size.height 196 | height.constant = size.height 197 | 198 | }else{ 199 | yAxis.compression = node.height >= size.height ~ yAxis.compressionPriorty 200 | yAxis.hugging = node.height <= size.height ~ yAxis.huggingPriorty 201 | } 202 | } 203 | } 204 | } 205 | 206 | final class SizeConstraints{ 207 | 208 | var width: LayoutConstraint? 209 | var height: LayoutConstraint? 210 | 211 | /// update content size Constraint 212 | func updateSize(_ size: CGSize,node: Layoutable, priority: LayoutPriority = .strong){ 213 | 214 | if size.width != InvalidIntrinsicMetric{ 215 | if let width = width{ 216 | width.constant = size.width 217 | }else{ 218 | width = node.width == size.width ~ priority 219 | } 220 | } 221 | 222 | if size.height != InvalidIntrinsicMetric{ 223 | if let height = height{ 224 | height.constant = size.height 225 | }else{ 226 | height = node.height == size.height ~ priority 227 | } 228 | } 229 | } 230 | } 231 | 232 | final class PositionConstraints{ 233 | 234 | /// used for frame translated constraint 235 | var minX: LayoutConstraint? 236 | var minY: LayoutConstraint? 237 | 238 | /// update content size Constraint 239 | func updateOrigin(_ point: CGPoint,node: Layoutable, priority: LayoutPriority = .required){ 240 | 241 | if let minX = minX{ 242 | minX.constant = point.x 243 | }else{ 244 | minX = node.left == point.x ~ priority 245 | } 246 | 247 | if let minY = minY{ 248 | minY.constant = point.y 249 | }else{ 250 | minY = node.top == point.y ~ priority 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Layoutable 2 | [![Version](https://img.shields.io/cocoapods/v/SwiftLayoutable.svg?style=flat)](http://cocoapods.org/pods/SwiftLayoutable) 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | [![](https://img.shields.io/badge/iOS-8.0%2B-lightgrey.svg)]() 5 | [![Swift 4.0](https://img.shields.io/badge/Swift-4.2-orange.svg)]() 6 | 7 | 8 | 9 | 10 | Layoutable is a swift reimplement of apple's Autolayout. It uses the same [Cassowary ](https://constraints.cs.washington.edu/cassowary/) algorithm as it's core and provides a set of api similar to Autolayout. The difference is that Layouable is more flexable and easy to use.Layoutable don't rely on UIView, it can be used in any object that conform to Layoutable protocol such as CALaye or self defined object.It can be used in background thread which is the core benefit of Layoutable. Layoutable also provides high level api and syntax sugar to make it easy to use. 11 | 12 | ## Requirements 13 | - iOS 8.0+ 14 | - Swift 4.2 15 | - Xcode 10.0 or higher 16 | 17 | ## Installation 18 | 19 | ### CocoaPods 20 | 21 | [CocoaPods](http://cocoapods.org/) is a dependency manager for Cocoa projects. Install it with the following command: 22 | 23 | `$ gem install cocoapods` 24 | 25 | To integrate Layoutable into your Xcode project using CocoaPods, specify it to a target in your Podfile: 26 | 27 | ``` 28 | source 'https://github.com/CocoaPods/Specs.git' 29 | platform :ios, '8.0' 30 | use_frameworks! 31 | 32 | target 'MyApp' do 33 | # your other pod 34 | # ... 35 | pod 'SwiftLayoutable' 36 | end 37 | ``` 38 | Then, run the following command: 39 | 40 | `$ pod install` 41 | 42 | open the `{Project}.xcworkspace` instead of the `{Project}.xcodeproj` after you installed anything from CocoaPods. 43 | 44 | For more information about how to use CocoaPods, [see this tutorial](http://www.raywenderlich.com/64546/introduction-to-cocoapods-2). 45 | 46 | 47 | [Layoutable](https://github.com/nangege/Layoutable) rely on [Cassowary](https://github.com/nangege/Cassowary), you need to add both of them to your projetc. 48 | 49 | 50 | ### Carthage 51 | 52 | 53 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager for Cocoa application. To install the carthage tool, you can use [Homebrew](http://brew.sh). 54 | 55 | ```bash 56 | $ brew update 57 | $ brew install carthage 58 | ``` 59 | 60 | To integrate Panda into your Xcode project using Carthage, specify it in your `Cartfile`: 61 | 62 | ```bash 63 | github "https://github.com/nangege/Layoutable" "master" 64 | ``` 65 | 66 | Then, run the following command to build the Panda framework: 67 | 68 | ```bash 69 | $ carthage update 70 | ``` 71 | 72 | At last, you need to set up your Xcode project manually to add the Panda,Layoutable and Cassowary framework. 73 | 74 | On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop each framework you want to use from the Carthage/Build folder on disk. 75 | 76 | On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script with the following content: 77 | 78 | ```bash 79 | /usr/local/bin/carthage copy-frameworks 80 | ``` 81 | 82 | and add the paths to the frameworks you want to use under “Input Files”: 83 | 84 | ```bash 85 | $(SRCROOT)/Carthage/Build/iOS/Layoutable.framework 86 | $(SRCROOT)/Carthage/Build/iOS/Cassowary.framework 87 | ``` 88 | 89 | For more information about how to use Carthage, please see its [project page](https://github.com/Carthage/Carthage). 90 | 91 | ### Manually 92 | 93 | `git clone git@github.com:nangege/Layoutable.git` , 94 | `git clone git@github.com:nangege/Cassowary.git` 95 | 96 | 97 | then drag `Layoutable.xcodeproj` and `Cassowary.xcodeproj` file to your projrct 98 | 99 | On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section,add `Layoutable.framework` and `Cassowary.framework`. 100 | 101 | 102 | ## Usage 103 | 104 | 1. define your own Layout Object 105 | 106 | ```swift 107 | import Layoutable 108 | 109 | class TestNode: Layoutable{ 110 | 111 | public init() {} 112 | 113 | lazy var layoutManager = LayoutManager(self) 114 | 115 | var layoutSize = CGSize(width: InvalidIntrinsicMetric, height: InvalidIntrinsicMetric) 116 | 117 | weak var superItem: Layoutable? = nil 118 | 119 | var subItems = [Layoutable]() 120 | 121 | func addSubnode(_ node: TestNode){ 122 | subItems.append(node) 123 | node.superItem = self 124 | } 125 | 126 | func layoutSubItems() {} 127 | 128 | func updateConstraint() {} 129 | 130 | var layoutRect: CGRect = .zero 131 | 132 | var itemIntrinsicContentSize: CGSize{ 133 | return layoutSize 134 | } 135 | 136 | func contentSizeFor(maxWidth: CGFloat) -> CGSize { 137 | return InvaidIntrinsicSize 138 | } 139 | 140 | } 141 | 142 | ``` 143 | 144 | 2. use Layout object to Layout 145 | 146 | ```swift 147 | import Layoutable 148 | 149 | // Layout node1 and node2 horizontalally in node,space 10 and equal center in Vertical 150 | 151 | let node = TestNode() 152 | let node1 = TestNode() 153 | let node2 = TestNode() 154 | 155 | node.addSubnode(node1) 156 | node.addSubnode(node2) 157 | 158 | node1.size == (30,30) 159 | node2.size == (40,40) 160 | 161 | [node,node1].equal(.centerY,.left) 162 | [node2,node].equal(.top,.bottom,.centerY,.right) 163 | [node1,node2].space(10, axis: .horizontal) 164 | 165 | node.layoutIfEnabled() 166 | 167 | print(node.frame) // (0.0, 0.0, 80.0, 40.0) 168 | print(node1.frame) // (0.0, 5.0, 30.0, 30.0) 169 | print(node2.frame) // (40.0, 0.0, 40.0, 40.0) 170 | 171 | ``` 172 | 173 | ### Operation 174 | 175 | 1. basic attributes 176 | 177 | Like Autolayout, Layoutable support both Equal, lessThanOrEqual and greatThanOrEqualTo 178 | 179 | ```swift 180 | node1.left.equalTo(node2.left) 181 | node1.top.greatThanOrEqualTo(node2.top) 182 | node1.bottom.lessThanOrEqualTo(node2.bottom) 183 | ``` 184 | or 185 | 186 | ```swift 187 | node1.left == node2.left // can bve write as node1.left == node2 188 | node1.top >= node2.top // can bve write as node1.top >= node2 189 | node1.bottom <= node2.bottom // can bve write as node1.bottom <= node2 190 | 191 | ``` 192 | 2. composit attribute 193 | 194 | beside basic attribute such as left,right, Layoutable also provide some Composit attribute like size ,xSide,ySide,edge 195 | 196 | ```swift 197 | node1.xSide.equalTo(node2,insets:(10,10)) 198 | node1.edge(node2,insets:(5,5,5,5)) 199 | node.topLeft.equalTo(node2, offset: (10,5)) 200 | 201 | ``` 202 | or 203 | 204 | ```swift 205 | node1.xSide == node2.xSide + (10,10) 206 | //node1.xSide == node2.xSide.insets(10) 207 | //node1.xSide == node2.xSide.insets((10,10)) 208 | 209 | node1.edge == node2.insets((5,5,5,5)) 210 | // node1.edge == node2 + (5,5,5,5) 211 | 212 | node.topLeft == node2.topLeft.offset((10,5)) 213 | 214 | ``` 215 | 216 | 3. update Priority 217 | 218 | ```swift 219 | node1.width == 100 ~.strong 220 | node1.height == 200 ~ 760.0 221 | ``` 222 | 4. update constant 223 | 224 | ```swift 225 | let c = node.left == node2.left + 10 226 | c.constant = 100 227 | 228 | ``` 229 | 230 | ## Supported attributes 231 | 232 | 233 | Layoutable | NSLayoutAttribute 234 | ------------------------- | -------------------------- 235 | Layoutable.left | NSLayoutAttributeLeft 236 | Layoutable.right | NSLayoutAttributeRight 237 | Layoutable.top | NSLayoutAttributeTop 238 | Layoutable.bottom | NSLayoutAttributeBottom 239 | Layoutable.width | NSLayoutAttributeWidth 240 | Layoutable.height | NSLayoutAttributeHeight 241 | Layoutable.centerX | NSLayoutAttributeCenterX 242 | Layoutable.centerY | NSLayoutAttributeCenterY 243 | Layoutable.size | width and height 244 | Layoutable.center | centerX and centerY 245 | Layoutable.xSide | left and right 246 | Layoutable.ySide | top and bottom 247 | Layoutable.edge | top,left,bottom,right 248 | Layoutable.topLeft | top and left 249 | Layoutable.topRight | top and right 250 | Layoutable.bottomLeft | bottom and left 251 | Layoutable.bottomRight | bottom and right 252 | 253 | 254 | ## Lisence 255 | 256 | The MIT License (MIT) 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /Layoutable/Sources/LayoutConstraint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutConstraint.swift 3 | // Cassowary 4 | // 5 | // Created by Tang,Nan(MAD) on 2018/3/20. 6 | // Copyright © 2018年 nange. All rights reserved. 7 | // 8 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 9 | /// of this software and associated documentation files (the "Software"), to deal 10 | /// in the Software without restriction, including without limitation the rights 11 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | /// copies of the Software, and to permit persons to whom the Software is 13 | /// furnished to do so, subject to the following conditions: 14 | /// 15 | /// The above copyright notice and this permission notice shall be included in 16 | /// all copies or substantial portions of the Software. 17 | /// 18 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 19 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 20 | /// Software in any work that is designed, intended, or marketed for pedagogical or 21 | /// instructional purposes related to programming, coding, application development, 22 | /// or information technology. Permission for such use, copying, modification, 23 | /// merger, publication, distribution, sublicensing, creation of derivative works, 24 | /// or sale is expressly withheld. 25 | /// 26 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | /// THE SOFTWARE. 33 | 34 | import Cassowary 35 | import CoreFoundation 36 | 37 | public enum LayoutRelation { 38 | case equal 39 | case lessThanOrEqual 40 | case greatThanOrEqual 41 | } 42 | 43 | extension LayoutRelation: CustomDebugStringConvertible{ 44 | public var debugDescription: String { 45 | switch self { 46 | case .equal: return "==" 47 | case .lessThanOrEqual: return "<=" 48 | case .greatThanOrEqual: return ">=" 49 | } 50 | } 51 | } 52 | 53 | public struct LayoutPriority: RawRepresentable,ExpressibleByFloatLiteral{ 54 | public init(rawValue: Double) { 55 | self.rawValue = rawValue 56 | } 57 | 58 | public var rawValue: Double 59 | 60 | public init(floatLiteral value: Double) { 61 | rawValue = value 62 | } 63 | 64 | public typealias RawValue = Double 65 | 66 | public typealias FloatLiteralType = Double 67 | 68 | public static let required: LayoutPriority = 1000.0 69 | public static let strong: LayoutPriority = 750.0 70 | public static let medium: LayoutPriority = 250.0 71 | public static let weak: LayoutPriority = 10.0 72 | } 73 | 74 | public enum LayoutAttribute{ 75 | 76 | case left 77 | 78 | case right 79 | 80 | case top 81 | 82 | case bottom 83 | 84 | case width 85 | 86 | case height 87 | 88 | case centerX 89 | 90 | case centerY 91 | } 92 | 93 | extension LayoutAttribute: CustomDebugStringConvertible{ 94 | public var debugDescription: String { 95 | switch self { 96 | case .left: return "left" 97 | case .right: return "right" 98 | case .top: return "top" 99 | case .bottom: return "bottom" 100 | case .width: return "width" 101 | case .height: return "height" 102 | case .centerX: return "centerX" 103 | case .centerY: return "centerY" 104 | } 105 | } 106 | } 107 | 108 | open class LayoutConstraint{ 109 | 110 | public init(firstAnchor: AnchorType,secondAnchor: AnchorType? = nil, relation: LayoutRelation = .equal, multiplier: Value = 1,constant: Value = 0){ 111 | self.firstAnchor = firstAnchor 112 | self.secondAnchor = secondAnchor 113 | self.relation = relation 114 | self.constant = constant 115 | self.multiplier = multiplier 116 | 117 | // maybe this code should not be here, need to be fix 118 | firstAnchor.item.layoutManager.translateRectIntoConstraints = false 119 | firstAnchor.item.addConstraint(self) 120 | secondAnchor?.item.layoutManager.pinedConstraints.insert(self) 121 | } 122 | 123 | public let firstAnchor: AnchorType 124 | 125 | public let secondAnchor: AnchorType? 126 | 127 | public let relation: LayoutRelation 128 | 129 | public let multiplier: Value 130 | 131 | open var constant: Value = 0{ 132 | didSet{ 133 | if let solver = solver , constant != oldValue{ 134 | solver.updateConstant(for: constraint, to: Double(constant)) 135 | } 136 | } 137 | } 138 | 139 | open var priority: LayoutPriority = .required{ 140 | didSet{ 141 | if let solver = solver{ 142 | try? solver.updateStrength(for: constraint, to: Strength(rawValue: priority.rawValue)) 143 | } 144 | } 145 | } 146 | 147 | open var isActive: Bool = false{ 148 | didSet{ 149 | if oldValue != isActive{ 150 | if isActive{ 151 | firstAnchor.item.addConstraint(self) 152 | secondAnchor?.item.layoutManager.pinedConstraints.insert(self) 153 | }else{ 154 | firstAnchor.item.removeConstraint(self) 155 | secondAnchor?.item.layoutManager.pinedConstraints.remove(self) 156 | try? solver?.remove(constraint: constraint) 157 | } 158 | } 159 | } 160 | } 161 | 162 | fileprivate weak var solver: SimplexSolver? = nil 163 | 164 | // translate LayoutConstraint to Constraint 165 | lazy var constraint: Constraint = { 166 | 167 | var constraint: Constraint 168 | let superItem = firstAnchor.item.commonSuperItem(with: secondAnchor?.item) 169 | 170 | var lhsExpr = firstAnchor.expression(in: superItem) 171 | let rhsExpr = Expression(constant: Double(constant)) 172 | 173 | if let secondAnchor = secondAnchor{ 174 | rhsExpr += secondAnchor.expression(in: superItem)*Double(multiplier) 175 | }else{ 176 | lhsExpr = firstAnchor.expression() 177 | } 178 | 179 | switch relation { 180 | case .equal: 181 | constraint = lhsExpr == rhsExpr 182 | case .greatThanOrEqual: 183 | constraint = lhsExpr >= rhsExpr 184 | case .lessThanOrEqual: 185 | constraint = lhsExpr <= rhsExpr 186 | } 187 | 188 | constraint.strength = Strength(rawValue: priority.rawValue) 189 | constraint.owner = self 190 | return constraint 191 | }() 192 | 193 | } 194 | 195 | extension LayoutConstraint{ 196 | 197 | func addToSolver(_ solver: SimplexSolver){ 198 | if self.solver === solver{ 199 | return 200 | } 201 | self.solver = solver 202 | do{ 203 | try solver.add(constraint: constraint) 204 | }catch ConstraintError.requiredFailureWithExplanation(let constraint){ 205 | let tips = """ 206 | Unable to simultaneously satisfy constraints. 207 | Probably at least one of the constraints in the following list is one you don't want. 208 | Try this: 209 | (1) look at each constraint and try to figure out which you don't expect; 210 | (2) find the code that added the unwanted constraint or constraints and fix it. 211 | """ 212 | print(tips) 213 | 214 | for (index,constraint) in constraint.enumerated(){ 215 | if let owner = constraint.owner{ 216 | print(" \(index + 1). \(String(describing: owner)) " ) 217 | } 218 | } 219 | 220 | print(""" 221 | Will attempt to recover by breaking constraint 222 | \(self) 223 | \n 224 | """) 225 | }catch{ 226 | print(error) 227 | } 228 | } 229 | 230 | public func remove(){ 231 | _ = try? solver?.remove(constraint: constraint) 232 | secondAnchor?.item.layoutManager.pinedConstraints.remove(self) 233 | solver = nil 234 | } 235 | 236 | class func active(_ constraints: [LayoutConstraint]){ 237 | constraints.forEach{ $0.isActive = true} 238 | } 239 | 240 | @discardableResult 241 | func priority(_ priority: LayoutPriority) -> LayoutConstraint{ 242 | self.priority = priority 243 | return self 244 | } 245 | } 246 | 247 | extension LayoutConstraint: Hashable{ 248 | public static func ==(lhs: LayoutConstraint, rhs: LayoutConstraint) -> Bool { 249 | return lhs === rhs 250 | } 251 | 252 | public func hash(into hasher: inout Hasher) { 253 | hasher.combine(ObjectIdentifier(self)) 254 | } 255 | } 256 | 257 | infix operator ~:TernaryPrecedence 258 | 259 | // syntax surge for setPriority 260 | // item1.left = item2.right + 10 ~ .strong 261 | @discardableResult 262 | public func ~(lhs: LayoutConstraint, rhs: LayoutPriority) -> LayoutConstraint{ 263 | lhs.priority = rhs 264 | return lhs 265 | } 266 | 267 | extension LayoutConstraint: CustomStringConvertible{ 268 | public var description: String { 269 | let lhsdesc = firstAnchor.debugDescription 270 | var desc = lhsdesc 271 | if let rhsAnchor = self.secondAnchor{ 272 | let rhsdesc = rhsAnchor.debugDescription 273 | desc = "\(lhsdesc) \(relation) \(rhsdesc)*\(multiplier) + \(constant)" 274 | }else{ 275 | desc = "\(lhsdesc) \(relation) \(constant)" 276 | } 277 | return desc 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /Layoutable/Sources/LayoutAnchor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutAnchor.swift 3 | // 4 | // Created by nangezao on 2017/7/19. 5 | // Copyright © 2017年 nange. All rights reserved. 6 | // 7 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 8 | /// of this software and associated documentation files (the "Software"), to deal 9 | /// in the Software without restriction, including without limitation the rights 10 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | /// copies of the Software, and to permit persons to whom the Software is 12 | /// furnished to do so, subject to the following conditions: 13 | /// 14 | /// The above copyright notice and this permission notice shall be included in 15 | /// all copies or substantial portions of the Software. 16 | /// 17 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 18 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 19 | /// Software in any work that is designed, intended, or marketed for pedagogical or 20 | /// instructional purposes related to programming, coding, application development, 21 | /// or information technology. Permission for such use, copying, modification, 22 | /// merger, publication, distribution, sublicensing, creation of derivative works, 23 | /// or sale is expressly withheld. 24 | /// 25 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | /// THE SOFTWARE. 32 | 33 | import Cassowary 34 | import CoreFoundation 35 | 36 | public protocol AnchorType: AnyObject, CustomDebugStringConvertible{ 37 | 38 | var item: Layoutable!{ get set} 39 | 40 | var attribute: LayoutAttribute{ get } 41 | } 42 | 43 | extension AnchorType{ 44 | func expression() -> Expression{ 45 | return item.layoutManager.variable.expressionFor(attribue: attribute) 46 | } 47 | 48 | func expression(in node: Layoutable?) -> Expression{ 49 | let expr = expression() 50 | guard let node = node else { 51 | return expr 52 | } 53 | 54 | expr.earse(node.layoutManager.variable.x) 55 | expr.earse(node.layoutManager.variable.y) 56 | 57 | if node === item { 58 | return expr 59 | } 60 | 61 | assert(item.superItem != nil) 62 | guard var superItem = item.superItem else { return expr } 63 | let xExpr = Expression() 64 | let yExpr = Expression() 65 | while !(superItem === node) { 66 | xExpr += superItem.layoutManager.variable.x 67 | yExpr += superItem.layoutManager.variable.y 68 | assert(superItem.superItem != nil) 69 | superItem = superItem.superItem! 70 | } 71 | let coffeeX = expr.coefficient(for: item.layoutManager.variable.x) 72 | let coffeeY = expr.coefficient(for: item.layoutManager.variable.y) 73 | expr += coffeeX * xExpr 74 | expr += coffeeY * yExpr 75 | return expr 76 | } 77 | } 78 | 79 | extension AnchorType{ 80 | 81 | @discardableResult 82 | public func constraint(to anchor: Self? = nil,relation: LayoutRelation, constant: Value = 0) -> LayoutConstraint{ 83 | return LayoutConstraint(firstAnchor: self , secondAnchor: anchor, relation: relation, constant:constant) 84 | } 85 | 86 | // These methods return a constraint of the form thisAnchor = otherAnchor + constant. 87 | @discardableResult 88 | public func equalTo(_ anchor: Self? = nil, constant: Value = 0) -> LayoutConstraint{ 89 | return constraint(to: anchor, relation: .equal, constant: constant) 90 | } 91 | 92 | @discardableResult 93 | public func greaterThanOrEqualTo(_ anchor: Self? = nil, constant: Value = 0) -> LayoutConstraint{ 94 | return constraint(to: anchor, relation: .greatThanOrEqual, constant: constant) 95 | } 96 | 97 | @discardableResult 98 | public func lessThanOrEqualTo(_ anchor: Self? = nil, constant: Value = 0) -> LayoutConstraint{ 99 | return constraint(to: anchor, relation: .lessThanOrEqual, constant: constant) 100 | } 101 | 102 | @discardableResult 103 | static public func == (lhs: Self, rhs: Self) -> LayoutConstraint{ 104 | return lhs.equalTo(rhs) 105 | } 106 | 107 | @discardableResult 108 | static public func <= (lhs: Self, rhs: Self) -> LayoutConstraint{ 109 | return lhs.lessThanOrEqualTo(rhs) 110 | } 111 | 112 | @discardableResult 113 | static public func >= (lhs: Self,rhs: Self) -> LayoutConstraint{ 114 | return lhs.greaterThanOrEqualTo(rhs) 115 | } 116 | 117 | @discardableResult 118 | static public func == (lhs: Self, rhs: Value) -> LayoutConstraint{ 119 | return lhs.equalTo(constant: rhs) 120 | } 121 | 122 | @discardableResult 123 | static public func <= (lhs: Self, rhs: Value) -> LayoutConstraint{ 124 | return lhs.lessThanOrEqualTo(constant: rhs) 125 | } 126 | 127 | @discardableResult 128 | static public func >= (lhs: Self,rhs: Value) -> LayoutConstraint{ 129 | return lhs.greaterThanOrEqualTo(constant: rhs) 130 | } 131 | } 132 | 133 | public class Anchor: AnchorType{ 134 | 135 | public weak var item: Layoutable! 136 | 137 | public let attribute: LayoutAttribute 138 | 139 | public init(item: Layoutable, attribute: LayoutAttribute){ 140 | self.item = item 141 | self.attribute = attribute 142 | } 143 | 144 | public var debugDescription: String{ 145 | let identifier = ObjectIdentifier(item).pure 146 | return "\(String(describing: item!))(\(identifier)).\(attribute)" 147 | } 148 | } 149 | 150 | /// Axis-specific subclasses for location anchors: top/bottom, left/right, etc. 151 | final public class XAxisAnchor : Anchor {} 152 | 153 | final public class YAxisAnchor : Anchor {} 154 | 155 | /// This layout anchor subclass is used for sizes (width & height). 156 | final public class DimensionAnchor : Anchor { 157 | 158 | /// These methods return a constraint of the form 159 | /// thisAnchor = otherAnchor * multiplier + constant. 160 | @discardableResult 161 | final public func equalTo(_ anchor: DimensionAnchor? = nil, multiplier m: Value = 1, constant: Value = 0) -> LayoutConstraint{ 162 | return LayoutConstraint(firstAnchor: self, secondAnchor: anchor, relation: .equal, multiplier: m, constant: constant) 163 | } 164 | 165 | @discardableResult 166 | final public func greaterThanOrEqualTo(_ anchor: DimensionAnchor? = nil, multiplier m: Value = 1, constant: Value = 0 ) -> LayoutConstraint{ 167 | return LayoutConstraint(firstAnchor:self, secondAnchor: anchor, relation:.greatThanOrEqual ,multiplier:m, constant:constant ) 168 | } 169 | 170 | @discardableResult 171 | final public func lessThanOrEqualTo(_ anchor: DimensionAnchor? = nil, multiplier m: Value = 1, constant: Value = 0) -> LayoutConstraint{ 172 | return LayoutConstraint(firstAnchor:self, secondAnchor: anchor,relation:.lessThanOrEqual ,multiplier:m, constant:constant ) 173 | } 174 | } 175 | 176 | final public class LayoutExpression{ 177 | var anchor: AnchorType 178 | var value: Value 179 | var multiplier: Value = 1 180 | 181 | init(anchor: AnchorType, multiplier: Value = 1, offset: Value = 0) { 182 | self.anchor = anchor 183 | self.multiplier = multiplier 184 | self.value = offset 185 | } 186 | } 187 | 188 | @discardableResult 189 | public func + (lhs: AnchorType, rhs: Value) -> LayoutExpression{ 190 | return LayoutExpression(anchor: lhs, offset: rhs) 191 | } 192 | 193 | @discardableResult 194 | public func - (lhs: AnchorType, rhs: Value) -> LayoutExpression{ 195 | return lhs + (-rhs) 196 | } 197 | 198 | @discardableResult 199 | public func == (lhs: AnchorType, rhs: LayoutExpression) -> LayoutConstraint{ 200 | return lhs.equalTo(rhs.anchor, constant: rhs.value) 201 | } 202 | 203 | @discardableResult 204 | public func >= (lhs: AnchorType, rhs: LayoutExpression) -> LayoutConstraint{ 205 | return lhs.greaterThanOrEqualTo(rhs.anchor, constant: rhs.value) 206 | } 207 | 208 | @discardableResult 209 | public func <= (lhs: AnchorType, rhs: LayoutExpression) -> LayoutConstraint{ 210 | return lhs.lessThanOrEqualTo(rhs.anchor, constant: rhs.value) 211 | } 212 | 213 | /// LayoutDimession 214 | @discardableResult 215 | public func + (lhs: LayoutExpression, rhs: Value) -> LayoutExpression{ 216 | lhs.value += rhs 217 | return lhs 218 | } 219 | 220 | @discardableResult 221 | public func - (lhs: LayoutExpression, rhs: Value) -> LayoutExpression{ 222 | return lhs + (-rhs) 223 | } 224 | 225 | @discardableResult 226 | public func * (lhs: DimensionAnchor, rhs: Value) -> LayoutExpression{ 227 | return LayoutExpression(anchor: lhs, multiplier: rhs) 228 | } 229 | 230 | @discardableResult 231 | public func / (lhs: DimensionAnchor, rhs: Value) -> LayoutExpression{ 232 | return lhs * (1/rhs) 233 | } 234 | 235 | @discardableResult 236 | public func == (lhs: DimensionAnchor, rhs: LayoutExpression) -> LayoutConstraint{ 237 | return lhs.equalTo(rhs.anchor, multiplier: rhs.multiplier, constant: rhs.value) 238 | } 239 | 240 | @discardableResult 241 | public func >= (lhs: DimensionAnchor, rhs: LayoutExpression) -> LayoutConstraint{ 242 | return lhs.greaterThanOrEqualTo(rhs.anchor, multiplier: rhs.multiplier, constant: rhs.value) 243 | } 244 | 245 | @discardableResult 246 | public func <= (lhs: DimensionAnchor, rhs: LayoutExpression) -> LayoutConstraint{ 247 | return lhs.lessThanOrEqualTo( rhs.anchor, multiplier: rhs.multiplier, constant: rhs.value) 248 | } 249 | 250 | extension ObjectIdentifier{ 251 | var pure: String{ 252 | return String(debugDescription.split(separator: "(")[1].split(separator: ")")[0]) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Layoutable/Sources/CompositAnchor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositAnchor.swift 3 | // Layout 4 | // 5 | // Created by nangezao on 2018/8/24. 6 | // Copyright © 2018 Tang Nan. All rights reserved. 7 | // 8 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 9 | /// of this software and associated documentation files (the "Software"), to deal 10 | /// in the Software without restriction, including without limitation the rights 11 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | /// copies of the Software, and to permit persons to whom the Software is 13 | /// furnished to do so, subject to the following conditions: 14 | /// 15 | /// The above copyright notice and this permission notice shall be included in 16 | /// all copies or substantial portions of the Software. 17 | /// 18 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 19 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 20 | /// Software in any work that is designed, intended, or marketed for pedagogical or 21 | /// instructional purposes related to programming, coding, application development, 22 | /// or information technology. Permission for such use, copying, modification, 23 | /// merger, publication, distribution, sublicensing, creation of derivative works, 24 | /// or sale is expressly withheld. 25 | /// 26 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | /// THE SOFTWARE. 33 | 34 | import CoreFoundation 35 | 36 | public enum LayoutAxis { 37 | case horizontal 38 | case vertical 39 | } 40 | 41 | public class CompositAnchor { 42 | let item: Layoutable 43 | 44 | public init(item: Layoutable) { 45 | self.item = item 46 | } 47 | } 48 | 49 | final public class SizeAnchor: CompositAnchor { 50 | @discardableResult 51 | public func equalTo(_ size: Size) -> [LayoutConstraint]{ 52 | return [ 53 | item.width == size.width, 54 | item.height == size.height 55 | ] 56 | } 57 | 58 | @discardableResult 59 | public func equalTo(_ layoutItem: Layoutable, offset: Offset = ( 0, 0)) -> [LayoutConstraint]{ 60 | return [ 61 | item.width == layoutItem.width + offset.x, 62 | item.height == layoutItem.height + offset.y 63 | ] 64 | } 65 | 66 | @discardableResult 67 | public static func == (lhs: SizeAnchor, rhs: Size) -> [LayoutConstraint]{ 68 | return lhs.equalTo(rhs) 69 | } 70 | } 71 | 72 | typealias PositionAttributes = (x: LayoutAttribute, y: LayoutAttribute) 73 | 74 | final public class PositionAnchor: CompositAnchor{ 75 | 76 | let attributes: PositionAttributes 77 | 78 | init(item: Layoutable, attributes: PositionAttributes) { 79 | self.attributes = attributes 80 | super.init(item: item) 81 | } 82 | 83 | public func offset(_ offset: Offset) -> CompositExpression{ 84 | return self + offset 85 | } 86 | 87 | @discardableResult 88 | public func equalTo(_ anchor: PositionAnchor, offset: Offset = (0,0)) -> [LayoutConstraint]{ 89 | 90 | let x = XAxisAnchor(item: item, 91 | attribute: attributes.x) == 92 | XAxisAnchor(item: anchor.item, 93 | attribute: anchor.attributes.x) + offset.x 94 | 95 | let y = YAxisAnchor(item: item, 96 | attribute: attributes.y) == 97 | YAxisAnchor(item: anchor.item, 98 | attribute: anchor.attributes.y) + offset.y 99 | 100 | return [x,y] 101 | } 102 | 103 | @discardableResult 104 | public func equalTo(_ layoutItem: Layoutable, offset: Offset = (0,0)) -> [LayoutConstraint]{ 105 | return equalTo(PositionAnchor(item: layoutItem, attributes: attributes), 106 | offset: offset) 107 | } 108 | 109 | @discardableResult 110 | static public func == (lhs: PositionAnchor, rhs: Layoutable) -> [LayoutConstraint]{ 111 | return lhs.equalTo(rhs) 112 | } 113 | 114 | @discardableResult 115 | static public func == (lhs: PositionAnchor, rhs: PositionAnchor) -> [LayoutConstraint]{ 116 | return lhs.equalTo(rhs) 117 | } 118 | } 119 | 120 | final public class XSideAnchor: CompositAnchor{ 121 | 122 | @discardableResult 123 | public func equalTo(_ layoutItem: Layoutable, insets: XSideInsets = (0,0)) -> [LayoutConstraint]{ 124 | return [ 125 | item.left == layoutItem.left + insets.left, 126 | item.right == layoutItem.right - insets.right 127 | ] 128 | } 129 | 130 | @discardableResult 131 | public func equalTo(_ anchor: XSideAnchor, insets: XSideInsets = (0,0)) -> [LayoutConstraint]{ 132 | return equalTo(anchor.item, insets: insets) 133 | } 134 | 135 | @discardableResult 136 | public func equalTo(_ layoutItem: Layoutable, insets: Value) -> [LayoutConstraint]{ 137 | return equalTo(layoutItem, insets: (insets, insets)) 138 | } 139 | 140 | public func insets(_ inset: XSideInsets) -> CompositExpression{ 141 | return self + inset 142 | } 143 | 144 | public func insets(_ inset: Value) -> CompositExpression{ 145 | return self + inset 146 | } 147 | 148 | @discardableResult 149 | static public func == (lhs: XSideAnchor, rhs: Layoutable) -> [LayoutConstraint]{ 150 | return lhs.equalTo(rhs) 151 | } 152 | 153 | } 154 | 155 | final public class YSideAnchor: CompositAnchor{ 156 | @discardableResult 157 | public func equalTo(_ layoutItem: Layoutable, insets: YSideInsets = (0, 0)) -> [LayoutConstraint]{ 158 | return [ 159 | item.top == layoutItem.top + insets.top, 160 | item.bottom == layoutItem.bottom - insets.bottom 161 | ] 162 | } 163 | 164 | @discardableResult 165 | public func equalTo(_ anchor: YSideAnchor, insets: YSideInsets = (0, 0)) -> [LayoutConstraint]{ 166 | return equalTo(anchor.item, insets: insets) 167 | } 168 | 169 | @discardableResult 170 | public func equalTo(_ layoutItem: Layoutable, insets: Value) -> [LayoutConstraint]{ 171 | return equalTo(layoutItem, insets: (insets, insets)) 172 | } 173 | 174 | public func insets(_ inset: YSideInsets) -> CompositExpression{ 175 | return self + inset 176 | } 177 | 178 | public func insets(_ inset: Value) -> CompositExpression{ 179 | return self + inset 180 | } 181 | 182 | @discardableResult 183 | static public func == (lhs: YSideAnchor, rhs: Layoutable) -> [LayoutConstraint]{ 184 | return lhs.equalTo(rhs) 185 | } 186 | } 187 | 188 | final public class EdgeAnchor: CompositAnchor{ 189 | 190 | public func insets(_ insets: EdgeInsets) -> CompositExpression{ 191 | return self + insets 192 | } 193 | 194 | @discardableResult 195 | public func equalTo(_ layoutItem: Layoutable, insets: EdgeInsets = EdgeInsetsZero) -> [LayoutConstraint]{ 196 | return [ 197 | item.top == layoutItem.top + insets.top, 198 | item.left == layoutItem.left + insets.left, 199 | item.bottom == layoutItem.bottom - insets.bottom, 200 | item.right == layoutItem.right - insets.right 201 | ] 202 | } 203 | 204 | @discardableResult 205 | static public func == (lhs: EdgeAnchor, rhs: Layoutable) -> [LayoutConstraint]{ 206 | return lhs.equalTo(rhs) 207 | } 208 | } 209 | 210 | final public class CompositExpression{ 211 | var anchor: AnchorType 212 | var value: valueType 213 | 214 | init(anchor: AnchorType, value: valueType) { 215 | self.anchor = anchor 216 | self.value = value 217 | } 218 | } 219 | 220 | @discardableResult 221 | public func + (lhs: Layoutable, rhs: ValueType) -> CompositExpression { 222 | return CompositExpression(anchor: lhs, value: rhs) 223 | } 224 | 225 | @discardableResult 226 | public func + (lhs: AnchorType, rhs: ValueType) -> CompositExpression { 227 | return CompositExpression(anchor: lhs, value: rhs) 228 | } 229 | 230 | @discardableResult 231 | public func == (lhs: SizeAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 233 | return lhs.equalTo(rhs.anchor, offset: rhs.value) 234 | } 235 | 236 | @discardableResult 237 | public func == (lhs: PositionAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 238 | return lhs.equalTo(rhs.anchor, offset: rhs.value) 239 | } 240 | 241 | @discardableResult 242 | public func == (lhs: PositionAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 243 | return lhs.equalTo(rhs.anchor, offset: rhs.value) 244 | } 245 | 246 | @discardableResult 247 | public func == (lhs: XSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 248 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 249 | } 250 | 251 | @discardableResult 252 | public func == (lhs: XSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 253 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 254 | } 255 | 256 | @discardableResult 257 | public func == (lhs: XSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 258 | return lhs.equalTo(rhs.anchor.item, insets: rhs.value) 259 | } 260 | 261 | @discardableResult 262 | public func == (lhs: XSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 263 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 264 | } 265 | 266 | @discardableResult 267 | public func == (lhs: YSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 268 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 269 | } 270 | 271 | @discardableResult 272 | public func == (lhs: YSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 273 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 274 | } 275 | 276 | @discardableResult 277 | public func == (lhs: YSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 278 | return lhs.equalTo(rhs.anchor.item, insets: rhs.value) 279 | } 280 | 281 | @discardableResult 282 | public func == (lhs: YSideAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 283 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 284 | } 285 | 286 | @discardableResult 287 | public func == (lhs: EdgeAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 288 | return lhs.equalTo(rhs.anchor, insets: rhs.value) 289 | } 290 | 291 | @discardableResult 292 | public func == (lhs: EdgeAnchor, rhs: CompositExpression) -> [LayoutConstraint]{ 293 | return lhs.equalTo(rhs.anchor.item, insets: rhs.value) 294 | } 295 | 296 | public extension Array where Element: Layoutable{ 297 | 298 | typealias LayoutAction = (_ pre: Layoutable, _ current: Layoutable) -> (LayoutConstraint) 299 | 300 | @discardableResult 301 | func traverse(action: LayoutAction) -> [LayoutConstraint]{ 302 | var constraints = [LayoutConstraint]() 303 | 304 | guard var preItem = self.first else { return constraints } 305 | for item in self{ 306 | if item !== preItem{ 307 | constraints.append(action(preItem, item)) 308 | } 309 | preItem = item 310 | } 311 | return constraints 312 | } 313 | 314 | @discardableResult 315 | func space(_ space: Value,axis: LayoutAxis = .vertical) -> [LayoutConstraint]{ 316 | return traverse { (preItem, currentItem) -> (LayoutConstraint) in 317 | switch axis{ 318 | case .horizontal: return currentItem.left == preItem.right + space 319 | case .vertical: return currentItem.top == preItem.bottom + space 320 | } 321 | } 322 | } 323 | 324 | @discardableResult 325 | func equal(_ attributes: LayoutAttribute...) -> [LayoutConstraint]{ 326 | var constraints = [LayoutConstraint]() 327 | for attribute in attributes{ 328 | constraints.append(contentsOf:traverse { (preItem, current) -> (LayoutConstraint) in 329 | let firstAnchor = Anchor(item: current, attribute: attribute) 330 | let secondAnchor = Anchor(item: preItem, attribute: attribute) 331 | return firstAnchor.equalTo(secondAnchor) 332 | } ) 333 | } 334 | return constraints 335 | } 336 | 337 | } 338 | 339 | 340 | -------------------------------------------------------------------------------- /Layoutable/Sources/Layoutable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstraintAble.swift 3 | // Cassowary 4 | // 5 | // Created by Tang,Nan(MAD) on 2018/4/2. 6 | // Copyright © 2018年 nange. All rights reserved. 7 | // 8 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 9 | /// of this software and associated documentation files (the "Software"), to deal 10 | /// in the Software without restriction, including without limitation the rights 11 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | /// copies of the Software, and to permit persons to whom the Software is 13 | /// furnished to do so, subject to the following conditions: 14 | /// 15 | /// The above copyright notice and this permission notice shall be included in 16 | /// all copies or substantial portions of the Software. 17 | /// 18 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 19 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 20 | /// Software in any work that is designed, intended, or marketed for pedagogical or 21 | /// instructional purposes related to programming, coding, application development, 22 | /// or information technology. Permission for such use, copying, modification, 23 | /// merger, publication, distribution, sublicensing, creation of derivative works, 24 | /// or sale is expressly withheld. 25 | /// 26 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | /// THE SOFTWARE. 33 | 34 | import Cassowary 35 | import CoreFoundation 36 | 37 | /// abstraction for constraint layout Item 38 | /// any object conform to Layoutable can use constraint to caculate frame 39 | public protocol Layoutable: AnyObject { 40 | 41 | var layoutManager: LayoutManager{ get } 42 | 43 | var superItem: Layoutable? { get } 44 | var subItems: [Layoutable]{ get } 45 | var layoutRect: CGRect { get set} 46 | 47 | /// like layoutSubviews in UIView 48 | /// this method will be called after layout pass 49 | /// frame of this item is determined 50 | func layoutSubItems() 51 | 52 | /// override point for add new constraint 53 | /// do not use this if not needed 54 | func updateConstraint() 55 | 56 | /// contentSize of node like,just like intrinsicContentSize in UIKit 57 | var itemIntrinsicContentSize: CGSize { get } 58 | 59 | /// contentSize of node, unlike intrinsicContentSize, this time width of node is determined 60 | /// this method is used to adjust height of this item 61 | /// such as text item, if numberOfLines is 0, we need maxWidth to determine number of lines and text height 62 | /// - Parameter maxWidth: maxWidth of this node 63 | /// - Returns: size of content 64 | func contentSizeFor(maxWidth: Value) -> CGSize 65 | } 66 | 67 | // public function 68 | extension Layoutable{ 69 | 70 | public var allConstraints: [LayoutConstraint]{ 71 | return Array(layoutManager.installedConstraints) + Array(layoutManager.pinedConstraints) 72 | } 73 | 74 | public var fixedWidth: Bool{ 75 | set{ layoutManager.fixedWidth = newValue } 76 | get{ return layoutManager.fixedWidth } 77 | } 78 | 79 | public var layoutNeedsUpdate: Bool{ 80 | set{ layoutManager.layoutNeedsUpdate = newValue} 81 | get{ return layoutManager.layoutNeedsUpdate } 82 | } 83 | 84 | /// disable cassowary Layout Enginer 85 | /// - Parameter disable: if set to true, all cassowary related code will return immediately 86 | /// it is useful when you want to use cached frame to Layout node, rather than caculate again 87 | public func disableLayout(_ disable: Bool = true){ 88 | layoutManager.enabled = !disable 89 | subItems.forEach{ $0.disableLayout(disable)} 90 | } 91 | 92 | /// just like layutIfNeeded in UIView 93 | /// call this method will caculate and update frame immediately 94 | /// be careful, don't call this if layout hierarchy is not ready 95 | public func layoutIfEnabled(){ 96 | 97 | if !layoutManager.enabled{ return } 98 | 99 | let item = ancestorItem 100 | 101 | /// for newly added Layoutable object 102 | /// if item is added after a layout pass,we need to add constraints to solver 103 | if let solver = item.layoutManager.solver{ 104 | item.updateSolverIfNeeded(solver) 105 | }else{ 106 | let solver = LayoutEngine.solveFor(item) 107 | solver.autoSolve = false 108 | item.addConstraintsTo(solver) 109 | try? solver.solve() 110 | solver.autoSolve = true 111 | } 112 | item.layoutFirstPass() 113 | item.layoutSecondPass() 114 | updateLayout() 115 | } 116 | 117 | /// layout info of the current node hierarchy 118 | /// provide for case of layout cache 119 | public var layoutValues: LayoutValues{ 120 | var cache = LayoutValues() 121 | if layoutManager.isConstraintValidRect{ 122 | cache.frame = layoutManager.layoutRect 123 | }else{ 124 | cache.frame = layoutRect 125 | } 126 | cache.subLayout = subItems.map{ $0.layoutValues } 127 | return cache 128 | } 129 | 130 | /// layout node hierarchy with frame 131 | /// 132 | /// - Parameter layout: layout hierarchy from this root node 133 | /// - make sure node hierarchy is exactly the same when you get this layoutValues 134 | public func apply(_ layout: LayoutValues){ 135 | layoutRect = layout.frame 136 | for (index, node) in subItems.enumerated(){ 137 | node.apply(layout.subLayout[index]) 138 | } 139 | } 140 | 141 | 142 | /// used for cleaning constraints for removed item 143 | // this function will remove all constraints for this item and it's subitems from current solver 144 | /// and break all constraints with item's supernode 145 | /// - Parameter item: item from which to break 146 | public func recursivelyReset(from item: Layoutable){ 147 | layoutManager.solver = nil 148 | while let constraint = layoutManager.installedConstraints.popFirst() { 149 | constraint.remove() 150 | if let secondItem = constraint.secondAnchor?.item{ 151 | if secondItem.ancestorItem === item{ 152 | addConstraint(constraint) 153 | secondItem.layoutManager.pinedConstraints.insert(constraint) 154 | } 155 | }else{ 156 | addConstraint(constraint) 157 | } 158 | } 159 | 160 | let pinnedToBeRemoved = layoutManager.pinedConstraints.filter{ $0.firstAnchor.item.ancestorItem !== item } 161 | pinnedToBeRemoved.forEach{ $0.remove() } 162 | subItems.forEach{ $0.recursivelyReset(from: item)} 163 | } 164 | 165 | public func setContentHuggingPriorty(for axis: LayoutAxis, priorty: LayoutPriority){ 166 | switch axis { 167 | case .horizontal: 168 | layoutManager.contentSizeConstraints.xAxis.huggingPriorty = priorty 169 | case .vertical: 170 | layoutManager.contentSizeConstraints.yAxis.huggingPriorty = priorty 171 | } 172 | } 173 | 174 | public func contentHuggingPriorty(for axis: LayoutAxis) -> LayoutPriority{ 175 | switch axis { 176 | case .horizontal: 177 | return layoutManager.contentSizeConstraints.xAxis.huggingPriorty 178 | case .vertical: 179 | return layoutManager.contentSizeConstraints.yAxis.huggingPriorty 180 | } 181 | } 182 | 183 | public func setContentCompressionPriorty(for axis: LayoutAxis, priorty: LayoutPriority){ 184 | switch axis { 185 | case .horizontal: 186 | layoutManager.contentSizeConstraints.xAxis.compressionPriorty = priorty 187 | case .vertical: 188 | layoutManager.contentSizeConstraints.yAxis.compressionPriorty = priorty 189 | } 190 | } 191 | 192 | public func contentCompressionPriorty(for axis: LayoutAxis) -> LayoutPriority{ 193 | switch axis { 194 | case .horizontal: 195 | return layoutManager.contentSizeConstraints.xAxis.compressionPriorty 196 | case .vertical: 197 | return layoutManager.contentSizeConstraints.yAxis.compressionPriorty 198 | } 199 | } 200 | } 201 | 202 | // MARK: - public property 203 | extension Layoutable{ 204 | public var left: XAxisAnchor{ 205 | return XAxisAnchor(item: self, attribute: .left) 206 | } 207 | 208 | public var right: XAxisAnchor{ 209 | return XAxisAnchor(item: self, attribute: .right) 210 | } 211 | 212 | public var top: YAxisAnchor{ 213 | return YAxisAnchor(item: self, attribute: .top) 214 | } 215 | 216 | public var bottom: YAxisAnchor{ 217 | return YAxisAnchor(item: self, attribute: .bottom) 218 | } 219 | 220 | public var width: DimensionAnchor{ 221 | return DimensionAnchor(item: self, attribute:.width) 222 | } 223 | 224 | public var height: DimensionAnchor{ 225 | return DimensionAnchor(item: self, attribute:.height) 226 | } 227 | 228 | public var centerX: XAxisAnchor{ 229 | return XAxisAnchor(item: self, attribute: .centerX) 230 | } 231 | 232 | public var centerY: YAxisAnchor{ 233 | return YAxisAnchor(item: self, attribute: .centerY) 234 | } 235 | 236 | public var size: SizeAnchor{ 237 | return SizeAnchor(item: self) 238 | } 239 | 240 | public var center: PositionAnchor{ 241 | return PositionAnchor(item: self, attributes: (.centerX,.centerY)) 242 | } 243 | 244 | public var topLeft: PositionAnchor{ 245 | return PositionAnchor(item: self, attributes: (.top,.left)) 246 | } 247 | 248 | public var topRight: PositionAnchor{ 249 | return PositionAnchor(item: self, attributes: (.top,.right)) 250 | } 251 | 252 | public var bottomLeft: PositionAnchor{ 253 | return PositionAnchor(item: self, attributes: (.bottom,.left)) 254 | } 255 | 256 | public var bottomRight: PositionAnchor{ 257 | return PositionAnchor(item: self, attributes: (.bottom,.right)) 258 | } 259 | 260 | /// left and right 261 | public var xSide: XSideAnchor{ 262 | return XSideAnchor(item: self) 263 | } 264 | 265 | /// top and bottom 266 | public var ySide: YSideAnchor{ 267 | return YSideAnchor(item: self) 268 | } 269 | 270 | /// top, left, right, bottom 271 | public var edge: EdgeAnchor{ 272 | return EdgeAnchor(item: self) 273 | } 274 | 275 | } 276 | 277 | 278 | // MARK: - internal function 279 | extension Layoutable{ 280 | 281 | func addConstraint(_ constraint: LayoutConstraint){ 282 | layoutManager.addConstraint(constraint) 283 | } 284 | 285 | func removeConstraint(_ constraint: LayoutConstraint){ 286 | layoutManager.removeConstraint(constraint) 287 | } 288 | 289 | func removeConstraints(_ constraints: [LayoutConstraint]){ 290 | constraints.forEach { 291 | self.removeConstraint($0) 292 | } 293 | } 294 | 295 | /// find common super item with another LayoutItem 296 | /// - Parameter item: item to find common superNode with 297 | /// - Returns: first super node for self and node 298 | func commonSuperItem(with item: Layoutable?) -> Layoutable?{ 299 | 300 | guard let item = item else{ 301 | return self 302 | } 303 | 304 | var depth1 = depth 305 | var depth2 = item.depth 306 | 307 | var superItem1: Layoutable = self 308 | var superItem2 = item 309 | 310 | while depth1 > depth2 { 311 | superItem1 = superItem1.superItem! 312 | depth1 -= 1 313 | } 314 | 315 | while depth2 > depth1 { 316 | superItem2 = superItem2.superItem! 317 | depth2 -= 1 318 | } 319 | 320 | while !(superItem1 === superItem2) { 321 | if superItem1.superItem == nil{ 322 | return nil 323 | } 324 | superItem1 = superItem1.superItem! 325 | superItem2 = superItem2.superItem! 326 | } 327 | 328 | return superItem1 329 | } 330 | 331 | } 332 | 333 | // MARK: - private function 334 | extension Layoutable{ 335 | 336 | private var depth: Int{ 337 | if let item = superItem{ 338 | return item.depth + 1 339 | }else{ 340 | return 1 341 | } 342 | } 343 | 344 | private func updateLayout(){ 345 | // need to be optimized 346 | if layoutManager.isConstraintValidRect{ 347 | layoutRect = layoutManager.layoutRect 348 | } 349 | subItems.forEach{ $0.updateLayout() } 350 | } 351 | 352 | private func addConstraintsTo(_ solver: SimplexSolver){ 353 | layoutManager.addConstraintsTo(solver) 354 | subItems.forEach { $0.addConstraintsTo(solver) } 355 | } 356 | 357 | private var ancestorItem: Layoutable{ 358 | if let superItem = superItem{ 359 | return superItem.ancestorItem 360 | } 361 | return self 362 | } 363 | 364 | private func updateAllConstraint(){ 365 | updateConstraint() 366 | layoutManager.updateConstraint() 367 | } 368 | 369 | private func updateSolverIfNeeded(_ solver: SimplexSolver){ 370 | if layoutManager.solver !== solver{ 371 | addConstraintsTo(solver) 372 | return 373 | } 374 | subItems.forEach{ $0.updateSolverIfNeeded(solver)} 375 | } 376 | 377 | private func layoutFirstPass(){ 378 | if layoutNeedsUpdate{ 379 | if !layoutManager.translateRectIntoConstraints{ 380 | var size = CGSize(width: InvalidIntrinsicMetric, height: 0) 381 | if !layoutManager.fixedWidth{ 382 | size = itemIntrinsicContentSize 383 | } 384 | layoutManager.updateSize(size) 385 | } 386 | }else if layoutManager.isRectConstrainted{ 387 | layoutManager.updateRect(layoutRect) 388 | /// a little weird here, when update size or origin,some constraints will be add to this item 389 | /// this item's translateRectIntoConstraints will be set to false 390 | /// correct it here. need a better way. 391 | layoutManager.translateRectIntoConstraints = true 392 | } 393 | updateAllConstraint() 394 | subItems.forEach { $0.layoutFirstPass() } 395 | } 396 | 397 | /// second layout pass is used to adjust contentSize height 398 | /// such as TextNode,at this time ,width for textNode is determined 399 | /// so we can know how manay lines this text should have 400 | private func layoutSecondPass(){ 401 | if layoutManager.sizeNeedsUpdate && !layoutManager.translateRectIntoConstraints{ 402 | var size = contentSizeFor(maxWidth: layoutManager.layoutRect.size.width) 403 | if layoutManager.fixedWidth{ 404 | size = CGSize(width: InvalidIntrinsicMetric, height: size.height) 405 | } 406 | 407 | if size != InvaidIntrinsicSize{ 408 | layoutManager.updateSize(size) 409 | } 410 | } 411 | 412 | subItems.forEach{ $0.layoutSecondPass()} 413 | layoutNeedsUpdate = false 414 | } 415 | 416 | } 417 | -------------------------------------------------------------------------------- /Layoutable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3806BA17214F628E00D6981F /* Cassowary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 380AD772214F603300C94CDD /* Cassowary.framework */; }; 11 | 3806BA1A214F635100D6981F /* Cassowary.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 380AD772214F603300C94CDD /* Cassowary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 38335FE22154681D0084DC0C /* LayoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38335FE12154681D0084DC0C /* LayoutManager.swift */; }; 13 | 3858B93B213E76D200CE161E /* TestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3858B93A213E76D200CE161E /* TestNode.swift */; }; 14 | 38C105CA2139919B00993530 /* Layoutable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38C105C02139919B00993530 /* Layoutable.framework */; }; 15 | 38C105CF2139919B00993530 /* PerformanceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105CE2139919B00993530 /* PerformanceTest.swift */; }; 16 | 38C105D12139919B00993530 /* Layoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C105C32139919B00993530 /* Layoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 38C105FA2139920800993530 /* CompositAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F32139920800993530 /* CompositAnchor.swift */; }; 18 | 38C105FB2139920800993530 /* LayoutEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F42139920800993530 /* LayoutEngine.swift */; }; 19 | 38C105FC2139920800993530 /* LayoutProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F52139920800993530 /* LayoutProperty.swift */; }; 20 | 38C105FD2139920800993530 /* LayoutAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F62139920800993530 /* LayoutAnchor.swift */; }; 21 | 38C105FE2139920800993530 /* LayoutValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F72139920800993530 /* LayoutValues.swift */; }; 22 | 38C105FF2139920800993530 /* Layoutable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F82139920800993530 /* Layoutable.swift */; }; 23 | 38C106002139920800993530 /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C105F92139920800993530 /* LayoutConstraint.swift */; }; 24 | 38C1068A213999B500993530 /* Cassowary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38C10689213999B500993530 /* Cassowary.framework */; settings = {ATTRIBUTES = (Required, ); }; }; 25 | 38D0C35E216E02FD009CD78F /* LayoutTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0C35D216E02FD009CD78F /* LayoutTest.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 38C105CB2139919B00993530 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 38C105B72139919B00993530 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 38C105BF2139919B00993530; 34 | remoteInfo = LayoutItemAble; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXCopyFilesBuildPhase section */ 39 | 3806BA19214F631D00D6981F /* CopyFiles */ = { 40 | isa = PBXCopyFilesBuildPhase; 41 | buildActionMask = 2147483647; 42 | dstPath = ""; 43 | dstSubfolderSpec = 10; 44 | files = ( 45 | 3806BA1A214F635100D6981F /* Cassowary.framework in CopyFiles */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXCopyFilesBuildPhase section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | 380AD772214F603300C94CDD /* Cassowary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cassowary.framework; path = Carthage/Build/iOS/Cassowary.framework; sourceTree = ""; }; 53 | 38335FE12154681D0084DC0C /* LayoutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutManager.swift; sourceTree = ""; }; 54 | 3858B93A213E76D200CE161E /* TestNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNode.swift; sourceTree = ""; }; 55 | 38C105C02139919B00993530 /* Layoutable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Layoutable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 38C105C32139919B00993530 /* Layoutable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Layoutable.h; sourceTree = ""; }; 57 | 38C105C42139919B00993530 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 38C105C92139919B00993530 /* LayoutableTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LayoutableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 38C105CE2139919B00993530 /* PerformanceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTest.swift; sourceTree = ""; }; 60 | 38C105D02139919B00993530 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 38C105F32139920800993530 /* CompositAnchor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositAnchor.swift; sourceTree = ""; }; 62 | 38C105F42139920800993530 /* LayoutEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutEngine.swift; sourceTree = ""; }; 63 | 38C105F52139920800993530 /* LayoutProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutProperty.swift; sourceTree = ""; }; 64 | 38C105F62139920800993530 /* LayoutAnchor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutAnchor.swift; sourceTree = ""; }; 65 | 38C105F72139920800993530 /* LayoutValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutValues.swift; sourceTree = ""; }; 66 | 38C105F82139920800993530 /* Layoutable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layoutable.swift; sourceTree = ""; }; 67 | 38C105F92139920800993530 /* LayoutConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutConstraint.swift; sourceTree = ""; }; 68 | 38C10689213999B500993530 /* Cassowary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Cassowary.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 38D0C35D216E02FD009CD78F /* LayoutTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutTest.swift; sourceTree = ""; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | 38C105BD2139919B00993530 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | 38C1068A213999B500993530 /* Cassowary.framework in Frameworks */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 38C105C62139919B00993530 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 3806BA17214F628E00D6981F /* Cassowary.framework in Frameworks */, 86 | 38C105CA2139919B00993530 /* Layoutable.framework in Frameworks */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | 38C105B62139919B00993530 = { 94 | isa = PBXGroup; 95 | children = ( 96 | 38C105C22139919B00993530 /* Layoutable */, 97 | 38C105CD2139919B00993530 /* LayoutableTests */, 98 | 38C105C12139919B00993530 /* Products */, 99 | 38C10688213999B500993530 /* Frameworks */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | 38C105C12139919B00993530 /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 38C105C02139919B00993530 /* Layoutable.framework */, 107 | 38C105C92139919B00993530 /* LayoutableTests.xctest */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 38C105C22139919B00993530 /* Layoutable */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 38C105F22139920800993530 /* Sources */, 116 | 38C105C32139919B00993530 /* Layoutable.h */, 117 | 38C105C42139919B00993530 /* Info.plist */, 118 | ); 119 | path = Layoutable; 120 | sourceTree = ""; 121 | }; 122 | 38C105CD2139919B00993530 /* LayoutableTests */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 38C105CE2139919B00993530 /* PerformanceTest.swift */, 126 | 38D0C35D216E02FD009CD78F /* LayoutTest.swift */, 127 | 3858B93A213E76D200CE161E /* TestNode.swift */, 128 | 38C105D02139919B00993530 /* Info.plist */, 129 | ); 130 | path = LayoutableTests; 131 | sourceTree = ""; 132 | }; 133 | 38C105F22139920800993530 /* Sources */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 38C105F42139920800993530 /* LayoutEngine.swift */, 137 | 38C105F52139920800993530 /* LayoutProperty.swift */, 138 | 38C105F62139920800993530 /* LayoutAnchor.swift */, 139 | 38C105F72139920800993530 /* LayoutValues.swift */, 140 | 38C105F82139920800993530 /* Layoutable.swift */, 141 | 38335FE12154681D0084DC0C /* LayoutManager.swift */, 142 | 38C105F92139920800993530 /* LayoutConstraint.swift */, 143 | 38C105F32139920800993530 /* CompositAnchor.swift */, 144 | ); 145 | path = Sources; 146 | sourceTree = ""; 147 | }; 148 | 38C10688213999B500993530 /* Frameworks */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 380AD772214F603300C94CDD /* Cassowary.framework */, 152 | 38C10689213999B500993530 /* Cassowary.framework */, 153 | ); 154 | name = Frameworks; 155 | sourceTree = ""; 156 | }; 157 | /* End PBXGroup section */ 158 | 159 | /* Begin PBXHeadersBuildPhase section */ 160 | 38C105BB2139919B00993530 /* Headers */ = { 161 | isa = PBXHeadersBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | 38C105D12139919B00993530 /* Layoutable.h in Headers */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXHeadersBuildPhase section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 38C105BF2139919B00993530 /* Layoutable */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 38C105D42139919B00993530 /* Build configuration list for PBXNativeTarget "Layoutable" */; 174 | buildPhases = ( 175 | 38C105BB2139919B00993530 /* Headers */, 176 | 38C105BC2139919B00993530 /* Sources */, 177 | 38C105BD2139919B00993530 /* Frameworks */, 178 | 38C105BE2139919B00993530 /* Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | ); 184 | name = Layoutable; 185 | productName = LayoutItemAble; 186 | productReference = 38C105C02139919B00993530 /* Layoutable.framework */; 187 | productType = "com.apple.product-type.framework"; 188 | }; 189 | 38C105C82139919B00993530 /* LayoutableTests */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = 38C105D72139919B00993530 /* Build configuration list for PBXNativeTarget "LayoutableTests" */; 192 | buildPhases = ( 193 | 38C105C52139919B00993530 /* Sources */, 194 | 38C105C62139919B00993530 /* Frameworks */, 195 | 38C105C72139919B00993530 /* Resources */, 196 | 3806BA19214F631D00D6981F /* CopyFiles */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | 38C105CC2139919B00993530 /* PBXTargetDependency */, 202 | ); 203 | name = LayoutableTests; 204 | productName = LayoutItemAbleTests; 205 | productReference = 38C105C92139919B00993530 /* LayoutableTests.xctest */; 206 | productType = "com.apple.product-type.bundle.unit-test"; 207 | }; 208 | /* End PBXNativeTarget section */ 209 | 210 | /* Begin PBXProject section */ 211 | 38C105B72139919B00993530 /* Project object */ = { 212 | isa = PBXProject; 213 | attributes = { 214 | LastSwiftUpdateCheck = 1000; 215 | LastUpgradeCheck = 1000; 216 | ORGANIZATIONNAME = "Tang Nan"; 217 | TargetAttributes = { 218 | 38C105BF2139919B00993530 = { 219 | CreatedOnToolsVersion = 10.0; 220 | }; 221 | 38C105C82139919B00993530 = { 222 | CreatedOnToolsVersion = 10.0; 223 | }; 224 | }; 225 | }; 226 | buildConfigurationList = 38C105BA2139919B00993530 /* Build configuration list for PBXProject "Layoutable" */; 227 | compatibilityVersion = "Xcode 9.3"; 228 | developmentRegion = en; 229 | hasScannedForEncodings = 0; 230 | knownRegions = ( 231 | en, 232 | ); 233 | mainGroup = 38C105B62139919B00993530; 234 | productRefGroup = 38C105C12139919B00993530 /* Products */; 235 | projectDirPath = ""; 236 | projectRoot = ""; 237 | targets = ( 238 | 38C105BF2139919B00993530 /* Layoutable */, 239 | 38C105C82139919B00993530 /* LayoutableTests */, 240 | ); 241 | }; 242 | /* End PBXProject section */ 243 | 244 | /* Begin PBXResourcesBuildPhase section */ 245 | 38C105BE2139919B00993530 /* Resources */ = { 246 | isa = PBXResourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | 38C105C72139919B00993530 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXResourcesBuildPhase section */ 260 | 261 | /* Begin PBXSourcesBuildPhase section */ 262 | 38C105BC2139919B00993530 /* Sources */ = { 263 | isa = PBXSourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 38C105FE2139920800993530 /* LayoutValues.swift in Sources */, 267 | 38C105FA2139920800993530 /* CompositAnchor.swift in Sources */, 268 | 38C105FC2139920800993530 /* LayoutProperty.swift in Sources */, 269 | 38C105FD2139920800993530 /* LayoutAnchor.swift in Sources */, 270 | 38C105FF2139920800993530 /* Layoutable.swift in Sources */, 271 | 38C106002139920800993530 /* LayoutConstraint.swift in Sources */, 272 | 38C105FB2139920800993530 /* LayoutEngine.swift in Sources */, 273 | 38335FE22154681D0084DC0C /* LayoutManager.swift in Sources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | 38C105C52139919B00993530 /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | 38D0C35E216E02FD009CD78F /* LayoutTest.swift in Sources */, 282 | 38C105CF2139919B00993530 /* PerformanceTest.swift in Sources */, 283 | 3858B93B213E76D200CE161E /* TestNode.swift in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXSourcesBuildPhase section */ 288 | 289 | /* Begin PBXTargetDependency section */ 290 | 38C105CC2139919B00993530 /* PBXTargetDependency */ = { 291 | isa = PBXTargetDependency; 292 | target = 38C105BF2139919B00993530 /* Layoutable */; 293 | targetProxy = 38C105CB2139919B00993530 /* PBXContainerItemProxy */; 294 | }; 295 | /* End PBXTargetDependency section */ 296 | 297 | /* Begin XCBuildConfiguration section */ 298 | 38C105D22139919B00993530 /* Debug */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ALWAYS_SEARCH_USER_PATHS = NO; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 305 | CLANG_CXX_LIBRARY = "libc++"; 306 | CLANG_ENABLE_MODULES = YES; 307 | CLANG_ENABLE_OBJC_ARC = YES; 308 | CLANG_ENABLE_OBJC_WEAK = YES; 309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_COMMA = YES; 312 | CLANG_WARN_CONSTANT_CONVERSION = YES; 313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | CODE_SIGN_IDENTITY = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | CURRENT_PROJECT_VERSION = 1; 333 | DEBUG_INFORMATION_FORMAT = dwarf; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | ENABLE_TESTABILITY = YES; 336 | GCC_C_LANGUAGE_STANDARD = gnu11; 337 | GCC_DYNAMIC_NO_PIC = NO; 338 | GCC_NO_COMMON_BLOCKS = YES; 339 | GCC_OPTIMIZATION_LEVEL = 0; 340 | GCC_PREPROCESSOR_DEFINITIONS = ( 341 | "DEBUG=1", 342 | "$(inherited)", 343 | ); 344 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 345 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 346 | GCC_WARN_UNDECLARED_SELECTOR = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 348 | GCC_WARN_UNUSED_FUNCTION = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 351 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 352 | MTL_FAST_MATH = YES; 353 | ONLY_ACTIVE_ARCH = YES; 354 | SDKROOT = iphoneos; 355 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 356 | SWIFT_COMPILATION_MODE = wholemodule; 357 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 358 | VERSIONING_SYSTEM = "apple-generic"; 359 | VERSION_INFO_PREFIX = ""; 360 | }; 361 | name = Debug; 362 | }; 363 | 38C105D32139919B00993530 /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_ANALYZER_NONNULL = YES; 368 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_ENABLE_OBJC_WEAK = YES; 374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 375 | CLANG_WARN_BOOL_CONVERSION = YES; 376 | CLANG_WARN_COMMA = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN_ENUM_CONVERSION = YES; 383 | CLANG_WARN_INFINITE_RECURSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 387 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 389 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 390 | CLANG_WARN_STRICT_PROTOTYPES = YES; 391 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 392 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | CODE_SIGN_IDENTITY = "iPhone Developer"; 396 | COPY_PHASE_STRIP = NO; 397 | CURRENT_PROJECT_VERSION = 1; 398 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 399 | ENABLE_NS_ASSERTIONS = NO; 400 | ENABLE_STRICT_OBJC_MSGSEND = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu11; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 410 | MTL_ENABLE_DEBUG_INFO = NO; 411 | MTL_FAST_MATH = YES; 412 | SDKROOT = iphoneos; 413 | SWIFT_COMPILATION_MODE = wholemodule; 414 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 415 | VALIDATE_PRODUCT = YES; 416 | VERSIONING_SYSTEM = "apple-generic"; 417 | VERSION_INFO_PREFIX = ""; 418 | }; 419 | name = Release; 420 | }; 421 | 38C105D52139919B00993530 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | CODE_SIGN_IDENTITY = ""; 425 | CODE_SIGN_STYLE = Automatic; 426 | DEBUG_INFORMATION_FORMAT = dwarf; 427 | DEFINES_MODULE = YES; 428 | DEVELOPMENT_TEAM = DPJ43EEV6L; 429 | DYLIB_COMPATIBILITY_VERSION = 1; 430 | DYLIB_CURRENT_VERSION = 1; 431 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 432 | FRAMEWORK_SEARCH_PATHS = ( 433 | "$(inherited)", 434 | "$(PROJECT_DIR)/Carthage/Build/iOS", 435 | ); 436 | INFOPLIST_FILE = Layoutable/Info.plist; 437 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 438 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 439 | LD_RUNPATH_SEARCH_PATHS = ( 440 | "$(inherited)", 441 | "@executable_path/Frameworks", 442 | "@loader_path/Frameworks", 443 | ); 444 | PRODUCT_BUNDLE_IDENTIFIER = com.nange.Layoutable; 445 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 446 | SKIP_INSTALL = YES; 447 | SWIFT_VERSION = 4.2; 448 | TARGETED_DEVICE_FAMILY = "1,2"; 449 | }; 450 | name = Debug; 451 | }; 452 | 38C105D62139919B00993530 /* Release */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | CODE_SIGN_IDENTITY = ""; 456 | CODE_SIGN_STYLE = Automatic; 457 | DEFINES_MODULE = YES; 458 | DEVELOPMENT_TEAM = DPJ43EEV6L; 459 | DYLIB_COMPATIBILITY_VERSION = 1; 460 | DYLIB_CURRENT_VERSION = 1; 461 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 462 | FRAMEWORK_SEARCH_PATHS = ( 463 | "$(inherited)", 464 | "$(PROJECT_DIR)/Carthage/Build/iOS", 465 | ); 466 | INFOPLIST_FILE = Layoutable/Info.plist; 467 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 468 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 469 | LD_RUNPATH_SEARCH_PATHS = ( 470 | "$(inherited)", 471 | "@executable_path/Frameworks", 472 | "@loader_path/Frameworks", 473 | ); 474 | PRODUCT_BUNDLE_IDENTIFIER = com.nange.Layoutable; 475 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 476 | SKIP_INSTALL = YES; 477 | SWIFT_VERSION = 4.2; 478 | TARGETED_DEVICE_FAMILY = "1,2"; 479 | }; 480 | name = Release; 481 | }; 482 | 38C105D82139919B00993530 /* Debug */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 486 | CODE_SIGN_STYLE = Automatic; 487 | DEVELOPMENT_TEAM = DPJ43EEV6L; 488 | FRAMEWORK_SEARCH_PATHS = ( 489 | "$(inherited)", 490 | "$(PROJECT_DIR)/Carthage/Build/iOS", 491 | ); 492 | INFOPLIST_FILE = LayoutableTests/Info.plist; 493 | LD_RUNPATH_SEARCH_PATHS = ( 494 | "$(inherited)", 495 | "@executable_path/Frameworks", 496 | "@loader_path/Frameworks", 497 | ); 498 | PRODUCT_BUNDLE_IDENTIFIER = com.nange.LayoutableTests; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | SWIFT_COMPILATION_MODE = wholemodule; 501 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 502 | SWIFT_VERSION = 5.0; 503 | TARGETED_DEVICE_FAMILY = "1,2"; 504 | }; 505 | name = Debug; 506 | }; 507 | 38C105D92139919B00993530 /* Release */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 511 | CODE_SIGN_STYLE = Automatic; 512 | DEVELOPMENT_TEAM = DPJ43EEV6L; 513 | FRAMEWORK_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "$(PROJECT_DIR)/Carthage/Build/iOS", 516 | ); 517 | INFOPLIST_FILE = LayoutableTests/Info.plist; 518 | LD_RUNPATH_SEARCH_PATHS = ( 519 | "$(inherited)", 520 | "@executable_path/Frameworks", 521 | "@loader_path/Frameworks", 522 | ); 523 | PRODUCT_BUNDLE_IDENTIFIER = com.nange.LayoutableTests; 524 | PRODUCT_NAME = "$(TARGET_NAME)"; 525 | SWIFT_COMPILATION_MODE = wholemodule; 526 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 527 | SWIFT_VERSION = 5.0; 528 | TARGETED_DEVICE_FAMILY = "1,2"; 529 | }; 530 | name = Release; 531 | }; 532 | /* End XCBuildConfiguration section */ 533 | 534 | /* Begin XCConfigurationList section */ 535 | 38C105BA2139919B00993530 /* Build configuration list for PBXProject "Layoutable" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | 38C105D22139919B00993530 /* Debug */, 539 | 38C105D32139919B00993530 /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | 38C105D42139919B00993530 /* Build configuration list for PBXNativeTarget "Layoutable" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 38C105D52139919B00993530 /* Debug */, 548 | 38C105D62139919B00993530 /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | 38C105D72139919B00993530 /* Build configuration list for PBXNativeTarget "LayoutableTests" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | 38C105D82139919B00993530 /* Debug */, 557 | 38C105D92139919B00993530 /* Release */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | /* End XCConfigurationList section */ 563 | }; 564 | rootObject = 38C105B72139919B00993530 /* Project object */; 565 | } 566 | --------------------------------------------------------------------------------