├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .jazzy.yaml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CGLayout.podspec ├── CGLayout.xcodeproj ├── CGLayoutTests_Info.plist ├── CGLayout_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── CGLayout-Package.xcscheme │ └── xcschememanagement.plist ├── CONTRIBUTING.md ├── Example ├── CGLayout-macOS │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── CGLayout-macOSTests │ ├── CGLayout_macOSTests.swift │ └── Info.plist ├── CGLayout-tvOS │ ├── Assets.xcassets │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - Large.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon - Small.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ └── Contents.json │ │ │ └── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ └── Info.plist ├── CGLayout.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── CGLayout │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── LayoutViewController.swift │ ├── ProfileViewController.swift │ ├── ScrollLayoutGuideController.swift │ ├── SecondViewController.swift │ ├── SecondViewControllerAutolayout.swift │ ├── TableViewController.swift │ └── ViewController.swift ├── CGLayoutPlayground.playground │ ├── Pages │ │ ├── LayoutTests.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── LayoutWorkspaceAlign.xcplaygroundpage │ │ │ ├── Contents.swift │ │ │ └── timeline.xctimeline │ │ ├── LayoutWorkspaceLimit.xcplaygroundpage │ │ │ ├── Contents.swift │ │ │ └── timeline.xctimeline │ │ ├── LayoutWorkspacePull.xcplaygroundpage │ │ │ ├── Contents.swift │ │ │ └── timeline.xctimeline │ │ ├── RectAnchors.xcplaygroundpage │ │ │ ├── Contents.swift │ │ │ └── timeline.xctimeline │ │ ├── Stack.xcplaygroundpage │ │ │ └── Contents.swift │ │ └── SwiftUI.xcplaygroundpage │ │ │ └── Contents.swift │ └── contents.xcplayground ├── CGLayout_Example.entitlements ├── CONTRIBUTING.md ├── Podfile └── Tests │ └── Info.plist ├── LICENSE ├── Package.swift ├── README.md ├── Resources ├── benchmark_result.png ├── layout1.png ├── layout2.png ├── logo.png └── logo.xd ├── Sources ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── common.cglayout.swift │ ├── container.cglayout.swift │ ├── coordinate.cglayout.swift │ ├── core.cglayout.swift │ ├── deprecated.cglayout.swift │ ├── evolution.cglayout │ ├── anchors.cglayout.swift │ ├── backend.anchors.cglayout.swift │ ├── system.cglayout.swift │ └── workspace.cglayout.swift │ ├── layoutBlock.cglayout.swift │ ├── layoutConstraint.cglayout.swift │ ├── layoutGuide.cglayout.swift │ ├── private.cglayout.swift │ ├── rtl.cglayout.swift │ ├── scroll.layoutGuide.cglayout.swift │ ├── stack.layoutGuide.cglayout.swift │ └── support.cglayout.swift ├── Tests ├── CGLayoutTests │ ├── CGLayoutTests.swift │ ├── Tests.swift │ ├── XCTestManifests.swift │ └── support_linux.tests.swift └── LinuxMain.swift ├── docs ├── Classes.html ├── Classes │ ├── LayerPlaceholder.html │ ├── LayoutBlock.html │ ├── LayoutGuide.html │ ├── LayoutPlaceholder.html │ ├── MutableLayoutConstraint.html │ ├── ScrollAnimationDeceleration.html │ ├── ScrollLayoutGuide.html │ ├── StackLayoutGuide.html │ └── ViewPlaceholder.html ├── Enums.html ├── Enums │ └── LayoutAnchor.html ├── Extensions.html ├── Extensions │ ├── CALayer.html │ ├── CGRect.html │ ├── EdgeInsets.html │ ├── Equatable.html │ ├── LayoutCoordinateSpace.html │ ├── LayoutGuide.html │ ├── NSControl.html │ ├── NSScrollView.html │ ├── NSView.html │ ├── StackLayoutGuide.html │ ├── String.html │ ├── UIImageView.html │ ├── UILabel.html │ ├── UILabel │ │ └── Baseline.html │ ├── UIScrollView.html │ └── UIView.html ├── Protocols.html ├── Protocols │ ├── AdaptiveLayoutElement.html │ ├── AdjustableLayoutElement.html │ ├── ElementInLayoutTime.html │ ├── Extended.html │ ├── Extensible.html │ ├── LayoutBlockProtocol.html │ ├── LayoutConstraintProtocol.html │ ├── LayoutCoordinateSpace.html │ ├── LayoutElement.html │ ├── LayoutElementsContainer.html │ ├── LayoutSnapshotProtocol.html │ ├── RectBasedConstraint.html │ ├── RectBasedElement.html │ ├── RectBasedLayout.html │ └── TextPresentedElement.html ├── Structs.html ├── Structs │ ├── AdjustLayoutConstraint.html │ ├── AnonymConstraint.html │ ├── AnyRectBasedConstraint.html │ ├── AnyRectBasedLayout.html │ ├── Baseline.html │ ├── BaselineLayoutConstraint.html │ ├── Bottom.html │ ├── Bottom │ │ ├── AlignDependence.html │ │ ├── LimitDependence.html │ │ └── PullDependence.html │ ├── Center.html │ ├── Center │ │ └── AlignDependence.html │ ├── ContentLayoutConstraint.html │ ├── Equal.html │ ├── Inset.html │ ├── Layout.html │ ├── Layout │ │ ├── Alignment.html │ │ ├── Alignment │ │ │ ├── Horizontal.html │ │ │ └── Vertical.html │ │ ├── Filling.html │ │ └── Filling │ │ │ ├── Horizontal.html │ │ │ └── Vertical.html │ ├── LayoutConstraint.html │ ├── LayoutScheme.html │ ├── Leading.html │ ├── Leading │ │ ├── Align.html │ │ ├── Align │ │ │ └── Dependence.html │ │ ├── Limit.html │ │ ├── Limit │ │ │ └── Dependence.html │ │ ├── Pull.html │ │ └── Pull │ │ │ └── Dependence.html │ ├── Left.html │ ├── Left │ │ ├── AlignDependence.html │ │ ├── LimitDependence.html │ │ └── PullDependence.html │ ├── Right.html │ ├── Right │ │ ├── AlignDependence.html │ │ ├── LimitDependence.html │ │ └── PullDependence.html │ ├── ScrollDirection.html │ ├── Size.html │ ├── StackDistribution.html │ ├── StackDistribution │ │ ├── Alignment.html │ │ ├── Direction.html │ │ ├── Filling.html │ │ └── Spacing.html │ ├── StackLayoutScheme.html │ ├── StringLayoutAnchor.html │ ├── Top.html │ ├── Top │ │ ├── AlignDependence.html │ │ ├── LimitDependence.html │ │ └── PullDependence.html │ ├── Trailing.html │ └── Trailing │ │ ├── Align.html │ │ ├── Align │ │ └── Dependence.html │ │ ├── Limit.html │ │ ├── Limit │ │ └── Dependence.html │ │ ├── Pull.html │ │ └── Pull │ │ └── Dependence.html ├── Typealiases.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── CGLayout.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── LayerPlaceholder.html │ │ │ │ ├── LayoutBlock.html │ │ │ │ ├── LayoutGuide.html │ │ │ │ ├── LayoutPlaceholder.html │ │ │ │ ├── MutableLayoutConstraint.html │ │ │ │ ├── ScrollAnimationDeceleration.html │ │ │ │ ├── ScrollLayoutGuide.html │ │ │ │ ├── StackLayoutGuide.html │ │ │ │ └── ViewPlaceholder.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ └── LayoutAnchor.html │ │ │ ├── Extensions.html │ │ │ ├── Extensions │ │ │ │ ├── CALayer.html │ │ │ │ ├── CGRect.html │ │ │ │ ├── EdgeInsets.html │ │ │ │ ├── Equatable.html │ │ │ │ ├── LayoutCoordinateSpace.html │ │ │ │ ├── LayoutGuide.html │ │ │ │ ├── NSControl.html │ │ │ │ ├── NSScrollView.html │ │ │ │ ├── NSView.html │ │ │ │ ├── StackLayoutGuide.html │ │ │ │ ├── String.html │ │ │ │ ├── UIImageView.html │ │ │ │ ├── UILabel.html │ │ │ │ ├── UILabel │ │ │ │ │ └── Baseline.html │ │ │ │ ├── UIScrollView.html │ │ │ │ └── UIView.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── AdaptiveLayoutElement.html │ │ │ │ ├── AdjustableLayoutElement.html │ │ │ │ ├── ElementInLayoutTime.html │ │ │ │ ├── Extended.html │ │ │ │ ├── Extensible.html │ │ │ │ ├── LayoutBlockProtocol.html │ │ │ │ ├── LayoutConstraintProtocol.html │ │ │ │ ├── LayoutCoordinateSpace.html │ │ │ │ ├── LayoutElement.html │ │ │ │ ├── LayoutElementsContainer.html │ │ │ │ ├── LayoutSnapshotProtocol.html │ │ │ │ ├── RectBasedConstraint.html │ │ │ │ ├── RectBasedElement.html │ │ │ │ ├── RectBasedLayout.html │ │ │ │ └── TextPresentedElement.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── AdjustLayoutConstraint.html │ │ │ │ ├── AnonymConstraint.html │ │ │ │ ├── AnyRectBasedConstraint.html │ │ │ │ ├── AnyRectBasedLayout.html │ │ │ │ ├── Baseline.html │ │ │ │ ├── BaselineLayoutConstraint.html │ │ │ │ ├── Bottom.html │ │ │ │ ├── Bottom │ │ │ │ │ ├── AlignDependence.html │ │ │ │ │ ├── LimitDependence.html │ │ │ │ │ └── PullDependence.html │ │ │ │ ├── Center.html │ │ │ │ ├── Center │ │ │ │ │ └── AlignDependence.html │ │ │ │ ├── ContentLayoutConstraint.html │ │ │ │ ├── Equal.html │ │ │ │ ├── Inset.html │ │ │ │ ├── Layout.html │ │ │ │ ├── Layout │ │ │ │ │ ├── Alignment.html │ │ │ │ │ ├── Alignment │ │ │ │ │ │ ├── Horizontal.html │ │ │ │ │ │ └── Vertical.html │ │ │ │ │ ├── Filling.html │ │ │ │ │ └── Filling │ │ │ │ │ │ ├── Horizontal.html │ │ │ │ │ │ └── Vertical.html │ │ │ │ ├── LayoutConstraint.html │ │ │ │ ├── LayoutScheme.html │ │ │ │ ├── Leading.html │ │ │ │ ├── Leading │ │ │ │ │ ├── Align.html │ │ │ │ │ ├── Align │ │ │ │ │ │ └── Dependence.html │ │ │ │ │ ├── Limit.html │ │ │ │ │ ├── Limit │ │ │ │ │ │ └── Dependence.html │ │ │ │ │ ├── Pull.html │ │ │ │ │ └── Pull │ │ │ │ │ │ └── Dependence.html │ │ │ │ ├── Left.html │ │ │ │ ├── Left │ │ │ │ │ ├── AlignDependence.html │ │ │ │ │ ├── LimitDependence.html │ │ │ │ │ └── PullDependence.html │ │ │ │ ├── Right.html │ │ │ │ ├── Right │ │ │ │ │ ├── AlignDependence.html │ │ │ │ │ ├── LimitDependence.html │ │ │ │ │ └── PullDependence.html │ │ │ │ ├── ScrollDirection.html │ │ │ │ ├── Size.html │ │ │ │ ├── StackDistribution.html │ │ │ │ ├── StackDistribution │ │ │ │ │ ├── Alignment.html │ │ │ │ │ ├── Direction.html │ │ │ │ │ ├── Filling.html │ │ │ │ │ └── Spacing.html │ │ │ │ ├── StackLayoutScheme.html │ │ │ │ ├── StringLayoutAnchor.html │ │ │ │ ├── Top.html │ │ │ │ ├── Top │ │ │ │ │ ├── AlignDependence.html │ │ │ │ │ ├── LimitDependence.html │ │ │ │ │ └── PullDependence.html │ │ │ │ ├── Trailing.html │ │ │ │ └── Trailing │ │ │ │ │ ├── Align.html │ │ │ │ │ ├── Align │ │ │ │ │ └── Dependence.html │ │ │ │ │ ├── Limit.html │ │ │ │ │ ├── Limit │ │ │ │ │ └── Dependence.html │ │ │ │ │ ├── Pull.html │ │ │ │ │ └── Pull │ │ │ │ │ └── Dependence.html │ │ │ ├── Typealiases.html │ │ │ ├── badge.svg │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ └── CGLayout.tgz ├── img │ ├── carat.png │ ├── dash.png │ └── gh.png ├── index.html ├── js │ ├── jazzy.js │ └── jquery.min.js ├── search.json └── undocumented.json └── linux_example ├── .gitignore ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── CGLSDL │ ├── app.swift │ ├── main.swift │ └── view.swift └── Tests ├── CGLSDLTests ├── CGLSDLTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macOS-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: swift build -v 14 | - name: Run tests 15 | run: swift test -v 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | # Pods/ 34 | Example/Pods 35 | Podfile.lock 36 | Example/CGLayout.xcworkspace 37 | 38 | # Linux 39 | .build/* 40 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | output: docs 2 | swift_version: 4.2 3 | exclude: Sources/Classes/evolution.cglayout/* 4 | xcodebuild_arguments: 5 | - "-project" 6 | - "CGLayout.xcodeproj" 7 | - "-scheme" 8 | - "CGLayout-Package" 9 | 10 | theme: apple 11 | author: Koryttsev Denis 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Test", 8 | "cwd": "${workspaceFolder}", 9 | "preLaunchTask": "swift-test" 10 | }, 11 | { 12 | "type": "lldb", 13 | "request": "launch", 14 | "name": "Build", 15 | "cwd": "${workspaceFolder}", 16 | "preLaunchTask": "swift-build" 17 | }, 18 | // Running executables 19 | { 20 | "type": "lldb", 21 | "request": "launch", 22 | "name": "Run your Executable", 23 | "program": "${workspaceFolder}/.build/debug/your-executable", 24 | "args": [], 25 | "cwd": "${workspaceFolder}", 26 | "preLaunchTask": "swift-build" 27 | }, 28 | // Running unit tests 29 | { 30 | "type": "lldb", 31 | "request": "launch", 32 | "name": "Debug tests on Linux", 33 | "program": "./.build/x86_64-unknown-linux/debug/CGLayoutPackageTests.xctest", 34 | "preLaunchTask": "swift-build-tests" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // The fully path to the sourcekite(SDE's LS backend). 3 | "swift.path.sourcekite": "/home/k-o-d-e-n/Downloads/sourcekite/.build/release/sourcekite", 4 | // The fully path to the swift driver binary. 5 | "swift.path.swift_driver_bin": "/opt/swift-4.1-RELEASE-ubuntu16.10/" 6 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "swift-build", 6 | "type": "shell", 7 | "command": "swift build", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | } 12 | }, 13 | { 14 | "label": "swift-build-tests", 15 | "type": "shell", 16 | "command": "swift build --build-tests", 17 | "group": "build" 18 | }, 19 | { 20 | "label": "swift-test", 21 | "type": "shell", 22 | "command": "swift test", 23 | "group": "test" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /CGLayout.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint CGLayout.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'CGLayout' 11 | s.version = '0.7.2' 12 | s.summary = 'Constraint-based autolayout system written on Swift. Not Autolayout wrapper.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = 'Powerful autolayout framework, that can manage UIView(NSView), CALayer and not rendered views. Has cross-hierarchy coordinate space. Implementation performed on rect-based constraints. Fast, asynchronous, declarative, cacheable, extensible. Supported iOS, macOS, tvOS, Linux.' 21 | 22 | s.homepage = 'https://github.com/k-o-d-e-n/CGLayout' 23 | s.screenshots = 'https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/master/Resources/logo.png' 24 | s.license = { :type => 'MIT', :file => 'LICENSE' } 25 | s.author = { 'Denis Koryttsev' => 'koden.u8800@gmail.com' } 26 | s.source = { :git => 'https://github.com/k-o-d-e-n/CGLayout.git', :tag => '0.7.2' } 27 | s.social_media_url = 'https://twitter.com/K_o_D_e_N' 28 | s.documentation_url = 'https://k-o-d-e-n.github.io/CGLayout/' 29 | 30 | s.swift_version = '5.0' 31 | 32 | s.ios.deployment_target = '9.0' 33 | s.tvos.deployment_target = '10.0' 34 | s.osx.deployment_target = '10.10' 35 | 36 | s.source_files = 'Sources/Classes/**/*' 37 | end 38 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/CGLayoutTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/CGLayout_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/xcshareddata/xcschemes/CGLayout-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /CGLayout.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | CGLayout-Package.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | #### Have you feedback or recommendations? Please, create issue, or pull request. 4 | 5 | Recomendations for your pull requests: 6 | * Write tests. 7 | * Follow style guide. 8 | * Write a good commit message 9 | -------------------------------------------------------------------------------- /Example/CGLayout-macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CGLayout-macOS 4 | // 5 | // Created by Denis Koryttsev on 02/10/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | func applicationDidFinishLaunching(_ aNotification: Notification) { 14 | // Insert code here to initialize your application 15 | } 16 | 17 | func applicationWillTerminate(_ aNotification: Notification) { 18 | // Insert code here to tear down your application 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Example/CGLayout-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example/CGLayout-macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 CocoaPods. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/CGLayout-macOSTests/CGLayout_macOSTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGLayout_macOSTests.swift 3 | // CGLayout-macOSTests 4 | // 5 | // Created by Denis Koryttsev on 03/10/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CGLayout 11 | 12 | class CGLayout_macOSTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testEdgeInsetsApplyRect() { 25 | var rect = CGRect(x: 20, y: 10, width: 100, height: 200) 26 | let insets = EdgeInsets(top: 20, left: -5, bottom: 10, right: 0) 27 | 28 | rect.apply(edgeInsets: insets) 29 | 30 | XCTAssertTrue(rect == CGRect(x: 15, y: 30, width: 105, height: 170)) 31 | } 32 | func testCoordinateSpaceCGRect() { 33 | let window = NSWindow(contentRect: NSRect(origin: .zero, size: CGSize(width: 500, height: 500)), 34 | styleMask: .borderless, backing: .nonretained, defer: false) 35 | let bounds = window.frame 36 | let superview = NSScrollView(frame: bounds.insetBy(dx: 100, dy: 100)) 37 | window.contentView = superview 38 | superview.documentView = NSView(frame: bounds) 39 | superview.documentView?.scroll(NSPoint(x: 150, y: superview.documentView!.frame.origin.y)) 40 | let view = LayoutPlaceholder(frame: CGRect(x: 20, y: 10, width: 40, height: 60)) 41 | superview.add(layoutGuide: view) 42 | 43 | let converted = view.convert(rect: CGRect(x: 10, y: -5, width: 20, height: 10), to: superview) 44 | let converted2 = view.convert(rect: CGRect(x: 150, y: 0, width: 30, height: 20), from: superview) 45 | 46 | XCTAssertTrue(converted.origin.x == 30) 47 | XCTAssertTrue(converted.origin.y == 5) 48 | 49 | XCTAssertTrue(converted2.origin.x == 130) 50 | XCTAssertTrue(converted2.origin.y == -10) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Example/CGLayout-macOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/CGLayout-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIMainStoryboardFile 24 | Main 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/CGLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/CGLayout/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CGLayout 4 | // 5 | // Created by k-o-d-e-n on 08/31/2017. 6 | // Copyright (c) 2017 k-o-d-e-n. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | let isRTL = true 18 | CGLConfiguration.default.isRTLMode = isRTL 19 | UIView.appearance().semanticContentAttribute = isRTL ? .forceRightToLeft : .forceLeftToRight 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Example/CGLayout/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/CGLayout/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example/CGLayout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.6 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeRight 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationPortraitUpsideDown 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/CGLayout/LayoutViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutViewController.swift 3 | // CGLayout_Example 4 | // 5 | // Created by Denis Koryttsev on 21/05/2018. 6 | // Copyright © 2018 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | final class ContentView: UIView { 13 | override func layoutSubviews() { 14 | super.layoutSubviews() 15 | print(#function + " called") 16 | } 17 | } 18 | 19 | @available(iOS 10.0, *) 20 | class LayoutViewController: UIViewController { 21 | var contentView: ContentView! 22 | var layoutManager: LayoutManager! 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Change frame", style: .plain, target: self, action: #selector(changeFrame)) 28 | 29 | contentView = ContentView(frame: view.bounds) 30 | view.addSubview(contentView) 31 | 32 | let redView = UIView(backgroundColor: .red) 33 | contentView.addSubview(redView) 34 | let greenView = UIView(backgroundColor: .green) 35 | contentView.addSubview(greenView) 36 | let blueView = UIView(backgroundColor: .blue) 37 | contentView.addSubview(blueView) 38 | 39 | let purpleView = UIView(backgroundColor: .purple) 40 | contentView.addSubview(purpleView) 41 | 42 | self.layoutManager = LayoutManager(view: contentView, scheme: 43 | LayoutScheme(blocks: [ 44 | redView.layoutBlock(with: Layout(x: .left(), y: .top(), width: .fixed(200), height: .fixed(150))), 45 | blueView.layoutBlock(with: Layout(x: .center(), y: .center(), width: .fixed(200), height: .fixed(200))), 46 | greenView.layoutBlock(with: Layout(x: .left(), y: .bottom(), width: .fixed(150), height: .fixed(200))), 47 | purpleView.layoutBlock(with: .equal + .left(20...) + .right(20...) + .top(25...) + .bottom(25...), constraints: [ 48 | blueView.layoutConstraint(for: [.bottom(.limit(on: .outer))]), 49 | greenView.layoutConstraint(for: [.top(.limit(on: .outer))]) 50 | ]) 51 | ]) 52 | ) 53 | } 54 | 55 | @objc func changeFrame() { 56 | let alert = UIAlertController(title: nil, message: "New frame", preferredStyle: .alert) 57 | 58 | 59 | alert.addTextField { (tf) in 60 | tf.text = "\(self.contentView.frame.origin.x)" 61 | } 62 | alert.addTextField { (tf) in 63 | tf.text = "\(self.contentView.frame.origin.y)" 64 | } 65 | alert.addTextField { (tf) in 66 | tf.text = "\(self.contentView.frame.width)" 67 | } 68 | alert.addTextField { (tf) in 69 | tf.text = "\(self.contentView.frame.height)" 70 | } 71 | 72 | alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in 73 | self.contentView.frame = CGRect(x: Double(alert.textFields![0].text ?? "0") ?? 0, y: Double(alert.textFields![1].text ?? "0") ?? 0, 74 | width: Double(alert.textFields![2].text ?? "0") ?? 0, height: Double(alert.textFields![3].text ?? "0") ?? 0) 75 | })) 76 | 77 | present(alert, animated: false, completion: nil) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Example/CGLayout/ProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileViewController.swift 3 | // CGLayout_Example 4 | // 5 | // Created by Denis Koryttsev on 12/10/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | extension UIViewController { 13 | func buildView(_ type: V.Type, bg: UIColor) -> V { 14 | let v = V.init() 15 | v.backgroundColor = bg 16 | view.addSubview(v) 17 | return v 18 | } 19 | } 20 | 21 | extension ProfileViewController { 22 | func addLayoutGuide(_ lg: LayoutGuide) { 23 | view.add(layoutGuide: lg) 24 | layoutGuides.append(lg) 25 | } 26 | } 27 | 28 | class ProfileViewController: UIViewController { 29 | var scheme: LayoutScheme! 30 | var layoutGuides: [LayoutGuide] = [] 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | let headerView = buildView(UIView.self, bg: .white) 36 | let headerRightGroupGuide = StackLayoutGuide() 37 | headerRightGroupGuide.scheme.direction = .fromTrailing 38 | headerRightGroupGuide.scheme.spacing = .equal(10) 39 | headerRightGroupGuide.scheme.filling = .equal(40) 40 | headerRightGroupGuide.contentInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) 41 | layoutGuides.append(headerRightGroupGuide) 42 | headerView.add(layoutGuide: headerRightGroupGuide) 43 | let headerRightGroup = headerRightGroupGuide.layoutBlock( 44 | constraints: { (anchors) in 45 | anchors.centerY.align(by: headerView.layoutAnchors.centerY) 46 | anchors.width.equalIntrinsicSize() 47 | anchors.height.equal(to: 40) 48 | } 49 | ) 50 | let hrb1Button = buildView(UIButton.self, bg: .black) 51 | headerRightGroupGuide.addArranged(element: .uiView(hrb1Button)) 52 | let hrb2Button = buildView(UIButton.self, bg: .lightGray) 53 | headerRightGroupGuide.addArranged(element: .uiView(hrb2Button)) 54 | let hrb3Button = buildView(UIButton.self, bg: .lightGray) 55 | headerRightGroupGuide.addArranged(element: .uiView(hrb3Button)) 56 | 57 | let header = LayoutScheme(blocks: [ 58 | headerView.layoutBlock { (anchors) in 59 | if #available(iOS 11.0, tvOS 11.0, *) { 60 | anchors.top.align(by: view.safeAreaLayoutGuide.layoutAnchors.top) 61 | } else { 62 | anchors.top.align(by: navigationController!.navigationBar.layoutAnchors.bottom) 63 | } 64 | anchors.height.equal(to: 64) 65 | anchors.width.equal(to: view.layoutAnchors.width) 66 | }, 67 | headerRightGroup 68 | ]) 69 | 70 | let avatarView = buildView(UIImageView.self, bg: .gray) 71 | let avatar = avatarView.layoutBlock(with: Layout(y: .top(20))) { (anchors) in 72 | anchors.height.equal(to: 100) 73 | anchors.width.equal(to: 100) 74 | anchors.top.align(by: headerView.layoutAnchors.bottom) 75 | anchors.centerX.align(by: view.layoutAnchors.centerX) 76 | } 77 | 78 | let nameLabel = buildView(UILabel.self, bg: .gray) 79 | nameLabel.font = UIFont.boldSystemFont(ofSize: 30) 80 | nameLabel.text = "A SUPER MAN" 81 | let name = nameLabel.layoutBlock(with: Layout(y: .top(10))) { (anchors) in 82 | anchors.top.align(by: avatarView.layoutAnchors.bottom) 83 | anchors.centerX.align(by: view.layoutAnchors.centerX) 84 | anchors.width.equalIntrinsicSize() 85 | anchors.height.equalIntrinsicSize() 86 | } 87 | 88 | let socialLabelPrefix = buildView(UILabel.self, bg: .lightGray) 89 | socialLabelPrefix.font = UIFont.monospacedDigitSystemFont(ofSize: 20, weight: .bold) 90 | socialLabelPrefix.text = "@" 91 | let socialPrefix = socialLabelPrefix.layoutBlock(with: Layout(y: .top(8))) { (anchors) in 92 | anchors.top.align(by: nameLabel.layoutAnchors.bottom) 93 | anchors.centerX.align(by: view.layoutAnchors.centerX) 94 | anchors.width.equalIntrinsicSize() 95 | anchors.height.equalIntrinsicSize() 96 | } 97 | 98 | let socialLabel = buildView(UILabel.self, bg: .lightGray) 99 | socialLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 16, weight: .light) 100 | socialLabel.text = "super_man" 101 | 102 | let social = socialLabel.layoutBlock( 103 | constraints: { (anchors) in 104 | anchors.leading.pull(to: socialLabelPrefix.layoutAnchors.trailing) 105 | // TODO: Baseline is unavailable in anchors 106 | // anchors.baseline.align(by: socialLabelPrefix.layoutAnchors.baseline) // baseline does not working because block deals with label frame. need two blocks 107 | anchors.height.equalIntrinsicSize() 108 | anchors.width.equalIntrinsicSize(alignment: Layout.Alignment(horizontal: .leading(), vertical: .top())) 109 | } 110 | ) 111 | let socialPosition = socialLabel.baselineElement.layoutBlock( 112 | with: .vertical(.bottom()), 113 | constraints: { (anchors) in 114 | anchors.bottom.align(by: socialLabelPrefix.baselineElement.layoutAnchors.bottom) 115 | } 116 | ) 117 | 118 | let buttonsGroupGuide = StackLayoutGuide() // cannot calcute size based on elements 119 | buttonsGroupGuide.scheme.direction = .fromCenter 120 | buttonsGroupGuide.scheme.spacing = .equal(10) 121 | buttonsGroupGuide.scheme.filling = .equal(130) 122 | addLayoutGuide(buttonsGroupGuide) 123 | let btnsGroup = buttonsGroupGuide.layoutBlock(with: Layout(y: .top(20))) { (anchors) in 124 | anchors.top.align(by: socialLabelPrefix.layoutAnchors.bottom) 125 | anchors.centerX.align(by: view.layoutAnchors.centerX) 126 | anchors.width.equalIntrinsicSize() 127 | anchors.height.equal(to: 40) 128 | } 129 | let btn1Button = buildView(UIButton.self, bg: .black) 130 | btn1Button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .caption1) 131 | btn1Button.setTitle("CHANGE", for: .normal) 132 | buttonsGroupGuide.addArranged(element: .uiView(btn1Button)) 133 | let btn2Button = buildView(UIButton.self, bg: .lightGray) 134 | btn2Button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .caption1) 135 | btn2Button.setTitle("DELETE", for: .normal) 136 | buttonsGroupGuide.addArranged(element: .uiView(btn2Button)) 137 | 138 | let socialGroupGuide = StackLayoutGuide() // cannot calcute size based on elements 139 | socialGroupGuide.scheme.direction = .fromCenter 140 | socialGroupGuide.scheme.spacing = .equal(10) 141 | socialGroupGuide.scheme.filling = .equal(40) 142 | addLayoutGuide(socialGroupGuide) 143 | let socialGroup = socialGroupGuide.layoutBlock(with: Layout(y: .top(15))) { (anchors) in 144 | anchors.top.align(by: buttonsGroupGuide.layoutAnchors.bottom) 145 | anchors.centerX.align(by: view.layoutAnchors.centerX) 146 | anchors.width.equalIntrinsicSize() 147 | anchors.height.equal(to: 40) 148 | } 149 | let scl1Button = buildView(UIButton.self, bg: .black) 150 | socialGroupGuide.addArranged(element: .uiView(scl1Button)) 151 | let scl2Button = buildView(UIButton.self, bg: .lightGray) 152 | socialGroupGuide.addArranged(element: .uiView(scl2Button)) 153 | let scl3Button = buildView(UIButton.self, bg: .lightGray) 154 | socialGroupGuide.addArranged(element: .uiView(scl3Button)) 155 | 156 | let title1Label = buildView(UILabel.self, bg: .gray) 157 | title1Label.font = UIFont.preferredFont(forTextStyle: .title3) 158 | title1Label.text = "About me" 159 | let title1 = title1Label.layoutBlock(with: Layout(y: .top(20))) { (anchors) in 160 | anchors.top.align(by: socialGroupGuide.layoutAnchors.bottom) 161 | anchors.centerX.align(by: view.layoutAnchors.centerX) 162 | anchors.width.equalIntrinsicSize() 163 | anchors.height.equalIntrinsicSize() 164 | } 165 | 166 | let bodyLabel = buildView(UILabel.self, bg: .lightGray) 167 | bodyLabel.numberOfLines = 0 168 | bodyLabel.font = UIFont.preferredFont(forTextStyle: .body) 169 | bodyLabel.text = "The purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content" 170 | let body = bodyLabel.layoutBlock(with: Layout(y: .top(15))) { (anchors) in 171 | anchors.top.align(by: title1Label.layoutAnchors.bottom) 172 | anchors.centerX.align(by: view.layoutAnchors.centerX) 173 | if #available(iOS 11.0, tvOS 11.0, *) { 174 | anchors.leading.limit(by: view.safeAreaLayoutGuide.layoutAnchors.leading) 175 | anchors.trailing.limit(by: view.safeAreaLayoutGuide.layoutAnchors.trailing) 176 | } else { 177 | } 178 | anchors.width.equalIntrinsicSize() 179 | anchors.height.equalIntrinsicSize() 180 | } 181 | 182 | scheme = LayoutScheme(blocks: [ 183 | header, avatar, name, socialPrefix, social, socialPosition, btnsGroup, socialGroup, title1, body 184 | ]) 185 | } 186 | 187 | override func viewDidLayoutSubviews() { 188 | super.viewDidLayoutSubviews() 189 | scheme.layout(in: view.bounds) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Example/CGLayout/ScrollLayoutGuideController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollLayoutViewController.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 15/10/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | extension UIViewController { 13 | func loadContentScheme(subviews: inout [UIView]) -> (scheme: LayoutScheme, guide: LayoutGuide) { 14 | let redView = UIView(backgroundColor: .red) 15 | subviews.append(redView) 16 | let greenView = UIView(backgroundColor: .green) 17 | subviews.append(greenView) 18 | let blueView = UIView(backgroundColor: .blue) 19 | subviews.append(blueView) 20 | let contentGuide = LayoutGuide(frame: view.bounds.insetBy(dx: -100, dy: -300)) 21 | 22 | // let contentLayer = CALayer() 23 | // contentLayer.actions = ["position" : NSNull(), "bounds" : NSNull(), "path" : NSNull()] 24 | // contentLayer.borderWidth = 1 25 | // view.layer.addSublayer(contentLayer) 26 | 27 | return ( 28 | scheme: LayoutScheme(blocks: [ 29 | contentGuide.layoutBlock(with: Layout(x: .left(), y: .top(), width: .fixed(contentGuide.frame.width), height: .fixed(contentGuide.frame.height))), 30 | redView.layoutBlock(with: Layout(x: .left(), y: .top(), width: .fixed(200), height: .fixed(150))), 31 | blueView.layoutBlock(with: Layout(x: .center(), y: .center(), width: .fixed(200), height: .fixed(200))), 32 | greenView.layoutBlock(with: Layout(x: .left(), y: .bottom(), width: .fixed(150), height: .fixed(200)), 33 | constraints: [contentGuide.layoutConstraint(for: [LayoutAnchor.left(.align(by: .inner)), LayoutAnchor.bottom(.align(by: .inner))])]), 34 | // contentLayer.layoutBlock() 35 | ]), 36 | guide: contentGuide 37 | ) 38 | } 39 | } 40 | 41 | class ScrollLayoutGuideController: UIViewController { 42 | var scrollLayoutGuide: ScrollLayoutGuide! 43 | 44 | var subviews: [UIView] = [] 45 | var scheme: LayoutScheme! 46 | 47 | override func viewDidLoad() { 48 | super.viewDidLoad() 49 | 50 | let content = loadContentScheme(subviews: &subviews) 51 | 52 | scrollLayoutGuide = ScrollLayoutGuide(layout: content.scheme) 53 | scrollLayoutGuide.contentSize = content.guide.bounds.size 54 | scheme = LayoutScheme(blocks: [ 55 | scrollLayoutGuide.layoutBlock( 56 | with: Layout(x: .left(), y: .top(), width: .scaled(1), height: .scaled(1)), 57 | constraints: [(topLayoutGuide as! UIView).layoutConstraint(for: [LayoutAnchor.bottom(.limit(on: .outer))])] 58 | ), 59 | content.scheme 60 | ]) 61 | 62 | view.add(layoutGuide: scrollLayoutGuide) 63 | view.add(layoutGuide: content.guide) 64 | subviews.forEach({ view.addSubview($0) }) 65 | 66 | view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:)))) 67 | 68 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Native", style: .plain, target: self, action: #selector(openNative)) 69 | } 70 | 71 | override func viewDidLayoutSubviews() { 72 | super.viewDidLayoutSubviews() 73 | scheme.layout(in: view.layoutBounds) 74 | } 75 | 76 | var start: CGPoint = .zero 77 | var timer: Timer? = nil 78 | @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { 79 | let velocity = recognizer 80 | .velocity(in: recognizer.view) 81 | .clamped(max: 1500) 82 | 83 | switch recognizer.state { 84 | case .began: 85 | timer?.invalidate() 86 | timer = nil 87 | start = scrollLayoutGuide.contentOffset 88 | case .changed: 89 | let translation = recognizer.translation(in: recognizer.view) 90 | _ = scrollLayoutGuide.decelerate(start: start, translation: translation, velocity: velocity) 91 | case .ended: 92 | if let animation = scrollLayoutGuide.decelerate(start: start, translation: nil, velocity: velocity) { 93 | if #available(iOS 10.0, *) { 94 | timer?.invalidate() 95 | timer = Timer(timeInterval: 1/60, repeats: true, block: { timer in 96 | if animation.step() { 97 | timer.invalidate() 98 | } 99 | }) 100 | RunLoop.current.add(timer!, forMode: .default) 101 | } else { 102 | // Fallback on earlier versions 103 | } 104 | } 105 | default: break 106 | } 107 | } 108 | 109 | @objc func openNative() { 110 | let vc = UIScrollViewController() 111 | navigationController?.pushViewController(vc, animated: true) 112 | } 113 | } 114 | 115 | extension CGPoint { 116 | func clamped(max: CGFloat) -> CGPoint { 117 | return CGPoint(x: clamp(x, min: -max, max: max), y: clamp(y, min: -max, max: max)) 118 | } 119 | private func clamp(_ v: CGFloat, min: CGFloat, max: CGFloat) -> CGFloat { 120 | return (v < min) ? min : (v > max) ? max : v 121 | } 122 | } 123 | 124 | final class UIScrollViewController: UIViewController { 125 | var subviews: [UIView] = [] 126 | var scheme: LayoutScheme! 127 | 128 | override func loadView() { 129 | view = UIScrollView(frame: UIScreen.main.bounds) 130 | } 131 | 132 | override func viewDidLoad() { 133 | super.viewDidLoad() 134 | view.backgroundColor = .white 135 | 136 | let content = loadContentScheme(subviews: &subviews) 137 | scheme = LayoutScheme(blocks: [ 138 | content.scheme 139 | ]) 140 | (view as! UIScrollView).contentSize = content.guide.bounds.size 141 | 142 | view.add(layoutGuide: content.guide) 143 | subviews.forEach({ view.addSubview($0) }) 144 | } 145 | 146 | override func viewDidLayoutSubviews() { 147 | super.viewDidLayoutSubviews() 148 | scheme.layout(in: view.layoutBounds) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Example/CGLayout/SecondViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 01/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | /// Example extending 13 | extension Size { 14 | static func stringSize(_ string: String?, 15 | options: NSStringDrawingOptions = .usesLineFragmentOrigin, 16 | attributes: [NSAttributedString.Key: Any], 17 | context: NSStringDrawingContext? = nil) -> Size { 18 | return .build(StringLayoutAnchor(string: string, options: options, attributes: attributes, context: context)) 19 | } 20 | } 21 | extension Center { 22 | static var centerTop: Center { return .build(CenterTop()) } 23 | private struct CenterTop: RectBasedConstraint { 24 | func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 25 | sourceRect.origin.x = rect.midX - (sourceRect.width / 2) 26 | sourceRect.origin.y = rect.midY 27 | } 28 | } 29 | } 30 | 31 | public class SecondViewController: UIViewController { 32 | @IBOutlet weak var logoImageView: UIImageView! 33 | @IBOutlet weak var titleLabel: UILabel! 34 | @IBOutlet weak var nameLabel: UILabel! 35 | @IBOutlet weak var presentationLabel: UILabel! 36 | @IBOutlet weak var rainImageView: UIImageView! 37 | @IBOutlet weak var rainLabel: UILabel! 38 | @IBOutlet weak var distanceLabel: UILabel! 39 | @IBOutlet weak var weatherImageView: UIImageView! 40 | @IBOutlet weak var weatherLabel: UILabel! 41 | weak var separator1Layer: CALayer! 42 | weak var separator2Layer: CALayer! 43 | 44 | lazy var layoutScheme: LayoutScheme = buildScheme() 45 | 46 | var portraitSnapshot: LayoutSnapshotProtocol! 47 | var landscapeSnapshot: LayoutSnapshotProtocol! 48 | 49 | override public func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | let separator1 = CALayer(backgroundColor: .black) 53 | view.layer.addSublayer(separator1) 54 | separator1Layer = separator1 55 | let separator2 = CALayer(backgroundColor: .black) 56 | view.layer.addSublayer(separator2) 57 | separator2Layer = separator2 58 | 59 | #if os(iOS) 60 | let bounds = view.bounds 61 | let isLandscape = UIDevice.current.orientation.isLandscape 62 | let scheme = self.layoutScheme 63 | DispatchQueue.global(qos: .background).async { 64 | let portraitSnapshot = scheme.snapshot(for: isLandscape ? CGRect(x: 0, y: 0, width: bounds.height, height: bounds.width) : bounds) 65 | let landscapeSnapshot = scheme.snapshot(for: isLandscape ? bounds : CGRect(x: 0, y: 0, width: bounds.height, height: bounds.width)) 66 | DispatchQueue.main.sync { 67 | self.portraitSnapshot = portraitSnapshot 68 | self.landscapeSnapshot = landscapeSnapshot 69 | scheme.apply(snapshot: UIDevice.current.orientation.isLandscape ? landscapeSnapshot : portraitSnapshot) 70 | } 71 | } 72 | #endif 73 | } 74 | 75 | override public func viewDidLayoutSubviews() { 76 | super.viewDidLayoutSubviews() 77 | 78 | // layout directly 79 | // layoutScheme.layout() 80 | 81 | // layout in background 82 | // let bounds = view.bounds 83 | // DispatchQueue.global(qos: .background).async { 84 | // let snapshot = self.layoutScheme.snapshot(for: bounds) 85 | // DispatchQueue.main.sync { 86 | // self.layoutScheme.apply(snapshot: snapshot) 87 | // } 88 | // } 89 | 90 | // cached layout 91 | #if os(iOS) 92 | if UIDevice.current.orientation.isPortrait, let snapshot = portraitSnapshot { 93 | layoutScheme.apply(snapshot: snapshot) 94 | } else if UIDevice.current.orientation.isLandscape, let snapshot = landscapeSnapshot { 95 | layoutScheme.apply(snapshot: snapshot) 96 | } else { 97 | layoutScheme.layout(in: view.layoutBounds) 98 | } 99 | #endif 100 | #if os(tvOS) 101 | layoutScheme.layout() 102 | #endif 103 | } 104 | 105 | func buildScheme() -> LayoutScheme { 106 | let topLayoutGuideConstraint: LayoutConstraint 107 | if #available(iOS 11.0, tvOS 11.0, *) { 108 | topLayoutGuideConstraint = view.safeAreaLayoutGuide.layoutConstraint(for: [.top(.limit(on: .inner))]) 109 | } else { 110 | topLayoutGuideConstraint = navigationController!.navigationBar.layoutConstraint(for: [.bottom(.limit(on: .outer))]) 111 | } 112 | let bottomLayoutGuideConstraint: LayoutConstraint 113 | if #available(iOS 11.0, tvOS 11.0, *) { 114 | bottomLayoutGuideConstraint = view.safeAreaLayoutGuide.layoutConstraint(for: [.bottom(.limit(on: .inner))]) 115 | } else { 116 | bottomLayoutGuideConstraint = view.layoutConstraint(for: [.bottom(.limit(on: .inner))]) 117 | } 118 | return LayoutScheme(blocks: [ 119 | distanceLabel.layoutBlock( 120 | with: Layout(x: .center(), y: .bottom(50), width: .fixed(70), height: .fixed(30)), 121 | constraints: [bottomLayoutGuideConstraint] 122 | ), 123 | separator1Layer.layoutBlock( 124 | with: Layout(x: .trailing(25), y: .top(), width: .fixed(1), height: .scaled(1)), 125 | constraints: [distanceLabel.layoutConstraint(for: [.leading(.limit(on: .outer)), .top(.limit(on: .inner)), .size(.height())])] 126 | ), 127 | separator2Layer.layoutBlock( 128 | with: Layout(x: .leading(25), y: .bottom(), width: .fixed(1), height: .scaled(1)), 129 | constraints: [distanceLabel.layoutConstraint(for: [.size(.height()), .trailing(.limit(on: .outer)), .bottom(.align(by: .inner))])] 130 | ), 131 | weatherImageView.layoutBlock( 132 | with: Layout(x: .leading(20), y: .top(), width: .fixed(30), height: .fixed(30)), 133 | constraints: [separator2Layer.layoutConstraint(for: [.trailing(.limit(on: .outer)), .top(.limit(on: .inner))])] 134 | ), 135 | weatherLabel.layoutBlock( 136 | constraints: [ 137 | weatherImageView.layoutConstraint(for: [.top(.limit(on: .inner)), .trailing(.limit(on: .outer)), .size(.height())]), 138 | weatherLabel.adjustLayoutConstraint(for: [.width()], alignment: .init(horizontal: .leading(10), vertical: .top())) 139 | ] 140 | ), 141 | rainLabel.layoutBlock( 142 | with: Layout(x: .trailing(20), y: .top(), width: .scaled(1), height: .fixed(30)), 143 | constraints: [ 144 | rainLabel.adjustLayoutConstraint(for: [.width()]), 145 | separator1Layer.layoutConstraint(for: [.top(.limit(on: .inner)), .leading(.align(by: .outer))]) 146 | ] 147 | ), 148 | rainImageView.layoutBlock( 149 | with: Layout(x: .trailing(10), y: .top(), width: .fixed(30), height: .fixed(30)), 150 | constraints: [rainLabel.layoutConstraint(for: [.leading(.limit(on: .outer)), .top(.limit(on: .inner))])] 151 | ), 152 | logoImageView.layoutBlock( 153 | with: Layout(x: .center(), y: .top(80), width: .fixed(70), height: .fixed(70)), 154 | constraints: [topLayoutGuideConstraint] 155 | ), 156 | /// example including other scheme to top level scheme 157 | LayoutScheme(blocks: [ 158 | titleLabel.layoutBlock( 159 | with: Layout(x: .center(), y: .top(5), width: .scaled(1), height: .fixed(120)), 160 | constraints: [logoImageView.layoutConstraint(for: [.bottom(.limit(on: .outer))])] 161 | ), 162 | nameLabel.layoutBlock(with: Layout(x: .center(), y: .center(20), width: .scaled(1), height: .fixed(30))), 163 | presentationLabel.layoutBlock( 164 | with: Layout(x: .center(), y: .top(20), width: .equal, height: .equal), 165 | constraints: [ 166 | nameLabel.layoutConstraint(for: [.bottom(.limit(on: .outer))]), 167 | presentationLabel.adjustLayoutConstraint(for: [.height()]) 168 | ] 169 | ) 170 | ]) 171 | ]) 172 | } 173 | } 174 | 175 | extension CALayer { 176 | convenience init(backgroundColor: UIColor) { 177 | self.init() 178 | self.backgroundColor = backgroundColor.cgColor 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Example/CGLayout/SecondViewControllerAutolayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewControllerAutolayout.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 18/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | open class UIAdjustViewPlaceholder: UIViewPlaceholder { 13 | open override func viewDidLoad() { 14 | super.viewDidLoad() 15 | NSLayoutConstraint.activate([ 16 | view.leadingAnchor.constraint(equalTo: leadingAnchor), 17 | view.topAnchor.constraint(equalTo: topAnchor), 18 | view.bottomAnchor.constraint(equalTo: bottomAnchor), 19 | view.trailingAnchor.constraint(equalTo: trailingAnchor) 20 | ]) 21 | view.setContentHuggingPriority(.required, for: .horizontal) 22 | view.setContentHuggingPriority(.required, for: .vertical) 23 | view.setContentCompressionResistancePriority(.required, for: .vertical) 24 | view.setContentCompressionResistancePriority(.required, for: .horizontal) 25 | } 26 | } 27 | 28 | class SecondViewControllerAutolayout: UIViewController { 29 | lazy var placeholder: UIAdjustViewPlaceholder = .init() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | view.addLayoutGuide(placeholder) 34 | NSLayoutConstraint.activate([ 35 | placeholder.leadingAnchor.constraint(equalTo: view.leadingAnchor), 36 | placeholder.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor) 37 | ]) 38 | } 39 | 40 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 41 | super.viewWillTransition(to: size, with: coordinator) 42 | if !placeholder.isViewLoaded { 43 | placeholder.view.numberOfLines = 0 44 | placeholder.view.widthAnchor.constraint(equalToConstant: 100).isActive = true 45 | } 46 | placeholder.view.text = "Placeholder text" 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Example/CGLayout/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 21/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | struct IfBetween: RectBasedLayout { 13 | let axis: RectAxis 14 | let space: ClosedRange 15 | let modifier: (CGFloat) -> CGFloat 16 | func formLayout(rect: inout CGRect, in source: CGRect) { 17 | let size = axis.get(sizeAt: source) 18 | if space.contains(size) { 19 | axis.set(size: modifier(size), for: &rect) 20 | } 21 | } 22 | } 23 | 24 | extension Layout.Filling.Vertical { 25 | static func `if`(between: ClosedRange, modifier: @escaping (CGFloat) -> CGFloat) -> Layout.Filling.Vertical { 26 | return .build(IfBetween(axis: CGRectAxis.vertical, space: between, modifier: modifier)) 27 | } 28 | } 29 | extension Layout.Filling.Horizontal { 30 | static func `if`(between: ClosedRange, modifier: @escaping (CGFloat) -> CGFloat) -> Layout.Filling.Horizontal { 31 | return .build(IfBetween(axis: CGRectAxis.horizontal, space: between, modifier: modifier)) 32 | } 33 | } 34 | 35 | class TextCell: UITableViewCell { 36 | weak var label: UILabel! 37 | 38 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 39 | super.init(style: style, reuseIdentifier: reuseIdentifier) 40 | let label = UILabel() 41 | label.lineBreakMode = .byWordWrapping 42 | label.font = UIFont.systemFont(ofSize: 14) 43 | label.numberOfLines = 0 44 | contentView.addSubview(label) 45 | self.label = label 46 | } 47 | 48 | required init?(coder aDecoder: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | } 52 | 53 | // TODO: Add convenience entity for layout reuseable views, and calculation metrics based on content. 54 | struct ReuseLayoutBlock { 55 | let layout: RectBasedLayout 56 | let targetConstraints: [RectBasedConstraint] 57 | let contentConstraints: [RectBasedConstraint] 58 | 59 | func contentRect(fitting rect: CGRect) -> CGRect { 60 | return contentConstraints.reduce(rect) { $1.constrained(sourceRect: $0, by: rect) } 61 | } 62 | func apply(for item: LayoutElement) { 63 | layout.apply(for: item, in: item.superElement!.layoutBounds, use: [(item.superElement!.bounds, targetConstraints)]) 64 | } 65 | } 66 | 67 | class TableViewController: UITableViewController { 68 | let strings = "Lorem Ipsum - это текст-\"рыба\", часто используемый в печати и вэб-дизайне. Lorem Ipsum является стандартной \"рыбой\" для текстов на латинице с начала XVI века. В то время некий безымянный печатник создал большую коллекцию размеров и форм шрифтов, используя Lorem Ipsum для распечатки образцов. Lorem Ipsum не только успешно пережил без заметных изменений пять веков, но и перешагнул в электронный дизайн. Его популяризации в новое время послужили публикация листов Letraset. С образцами Lorem Ipsum в 60-х годах. В более недавнее время, программы электронной вёрстки типа Aldus PageMaker. В шаблонах которых используется Lorem Ipsum".components(separatedBy: ". ") 69 | 70 | let bottomView = UIView() 71 | let bottomView2 = UIView() 72 | let bottomView3 = UIView() 73 | let top1View = UIView() 74 | let layoutGuide = LayoutGuide(frame: UIScreen.main.bounds.insetBy(dx: 0, dy: 200)) 75 | lazy var scheme = self.buildScheme() 76 | 77 | @available(iOS 10.0, *) 78 | lazy var blocks: [ReuseLayoutBlock] = self.strings.map { 79 | ReuseLayoutBlock( 80 | layout: Layout.equal, 81 | targetConstraints: [Inset(UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20))], 82 | contentConstraints: [ 83 | Inset(UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)), 84 | $0.layoutConstraint(attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14)]), 85 | Inset(UIEdgeInsets(top: -10, left: 0, bottom: -10, right: 0)) 86 | ] 87 | ) 88 | } 89 | 90 | override func viewDidLoad() { 91 | super.viewDidLoad() 92 | 93 | tableView.register(TextCell.self, forCellReuseIdentifier: "reuseIdentifier") 94 | top1View.backgroundColor = .black 95 | bottomView.backgroundColor = .red 96 | bottomView2.backgroundColor = .yellow 97 | bottomView3.backgroundColor = .green 98 | bottomView3.layer.borderWidth = 1 99 | tableView.addSubview(top1View) 100 | tableView.addSubview(bottomView) 101 | tableView.addSubview(bottomView2) 102 | tableView.addSubview(bottomView3) 103 | tableView.add(layoutGuide: layoutGuide) 104 | } 105 | 106 | override func viewDidLayoutSubviews() { 107 | super.viewDidLayoutSubviews() 108 | scheme.layout(in: view.frame) 109 | } 110 | 111 | private func buildScheme() -> LayoutScheme { 112 | let topLayoutGuideConstraint: LayoutConstraint 113 | if #available(iOS 11.0, tvOS 11.0, *) { 114 | topLayoutGuideConstraint = view.safeAreaLayoutGuide.layoutConstraint(for: [.top(.pull(from: .inner))]) 115 | } else { 116 | topLayoutGuideConstraint = navigationController!.navigationBar.layoutConstraint(for: [.bottom(.pull(from: .outer))]) 117 | } 118 | return LayoutScheme(blocks: [ 119 | top1View.layoutBlock( // pull to refresh 120 | with: Layout( 121 | x: .center(), y: .center(), 122 | width: .scaled(0.8) + .if(between: 375...767, modifier: { $0 * 0.6 }) + .if(between: 768...1366, modifier: { $0 * 0.4 }), 123 | height: .scaled(0.8) + .if(between: 0...50, modifier: { $0 * 0.5 }) 124 | ), 125 | constraints: [ 126 | topLayoutGuideConstraint, 127 | tableView.contentLayoutConstraint(for: [.top(.pull(from: .outer))]) 128 | ] 129 | ), 130 | bottomView.layoutBlock( // red 131 | with: Layout(x: .center(), y: .bottom(), width: .fixed(100), height: .fixed(50)), 132 | constraints: [ 133 | view.layoutConstraint(for: [.bottom(.limit(on: .inner))]), 134 | layoutGuide.layoutConstraint(for: [.bottom(.limit(on: .inner))]) 135 | ] 136 | ), 137 | bottomView2.layoutBlock( // yellow 138 | with: Layout(x: .center(), y: .top(), width: .fixed(50), height: .fixed(50)), 139 | constraints: [ 140 | layoutGuide.layoutConstraint(for: [.bottom(.limit(on: .inner))]), 141 | tableView.contentLayoutConstraint(for: [.bottom(.align(by: .outer))]) 142 | ] 143 | ), 144 | bottomView3.layoutBlock( // green 145 | with: Layout(x: .center(), y: .top(between: 0...10), width: .fixed(50), height: .between(30...70)), 146 | constraints: [ 147 | bottomView2.layoutConstraint(for: [.bottom(.align(by: .outer))]), 148 | view.layoutConstraint(for: [.bottom(.limit(on: .inner))]) 149 | ] 150 | ) 151 | ]) 152 | } 153 | 154 | // MARK: - Table view data source 155 | 156 | override func numberOfSections(in tableView: UITableView) -> Int { 157 | return 1 158 | } 159 | 160 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 161 | return strings.count 162 | } 163 | 164 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 165 | return tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) 166 | } 167 | 168 | override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 169 | guard let cell = cell as? TextCell else { return } 170 | 171 | cell.label.text = strings[indexPath.row] 172 | if #available(iOS 10.0, *) { 173 | blocks[indexPath.row].apply(for: cell.label) 174 | } else { 175 | // Fallback on earlier versions 176 | } 177 | // Layout.equal.apply(for: cell.label, use: [(cell.bounds, LayoutAnchor.insets(UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)))]) 178 | } 179 | 180 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 181 | // let stringConstraint = strings[indexPath.row].layoutConstraint(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 14)]) 182 | // let expandedRect = CGRect(origin: .zero, size: CGSize(width: tableView.frame.width - 40, height: CGFloat.greatestFiniteMagnitude)) 183 | // 184 | // return stringConstraint.constrained(sourceRect: .zero, by: expandedRect).height.rounded(.up) + 20 185 | if #available(iOS 10.0, *) { 186 | return blocks[indexPath.row].contentRect(fitting: CGRect(origin: .zero, size: CGSize(width: tableView.frame.width, height: CGFloat.greatestFiniteMagnitude))).height.rounded(.up) 187 | } else { 188 | return 100 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Example/CGLayout/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CGLayout 4 | // 5 | // Created by k-o-d-e-n on 08/31/2017. 6 | // Copyright (c) 2017 k-o-d-e-n. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CGLayout 11 | 12 | extension UIView { 13 | convenience init(backgroundColor: UIColor) { 14 | self.init() 15 | self.backgroundColor = backgroundColor 16 | } 17 | } 18 | 19 | extension UILabel { 20 | convenience init(text: String) { 21 | self.init() 22 | self.text = text 23 | self.numberOfLines = 0 24 | } 25 | } 26 | 27 | class LabelPlaceholder: ViewPlaceholder { 28 | var font: UIFont? 29 | var textColor: UIColor? 30 | override var frame: CGRect { 31 | didSet { elementIfLoaded?.frame = frame } 32 | } 33 | 34 | open override func elementDidLoad() { 35 | super.elementDidLoad() 36 | 37 | element.font = font 38 | element.textColor = textColor 39 | } 40 | 41 | convenience init() { 42 | self.init(frame: .zero) 43 | } 44 | convenience init(font: UIFont, textColor: UIColor) { 45 | self.init() 46 | self.font = font 47 | self.textColor = textColor 48 | } 49 | } 50 | 51 | class ViewController: UIViewController { 52 | var scrollView: UIScrollView { return view as! UIScrollView } 53 | var elements: [UIView] = [] 54 | 55 | lazy var scheme: LayoutScheme = self.buildScheme() 56 | 57 | override func viewDidLoad() { 58 | super.viewDidLoad() 59 | 60 | scrollView.contentSize.height = view.frame.height 61 | scrollView.contentSize.width = view.frame.width 62 | 63 | self.elements = (0..<10).map { (i) -> UIView in 64 | let view = buildView(UIView.self, bg: UIColor(white: CGFloat(i) / 10, alpha: 1)) 65 | view.translatesAutoresizingMaskIntoConstraints = false 66 | scrollView.addSubview(view) 67 | return view 68 | } 69 | } 70 | 71 | override func viewDidLayoutSubviews() { 72 | super.viewDidLayoutSubviews() 73 | scheme.layout(in: view.layoutBounds) 74 | } 75 | 76 | private func buildScheme() -> LayoutScheme { 77 | let borderLayer = CALayer() 78 | borderLayer.borderWidth = 1 79 | view.layer.addSublayer(borderLayer) 80 | 81 | let initial: (blocks: [LayoutBlockProtocol], last: UIView?) = ([], nil) 82 | return LayoutScheme( 83 | blocks: elements.reduce(into: initial) { (blocks, view) -> Void in 84 | var constraints: [LayoutConstraintProtocol] = [] 85 | if let last = blocks.last { 86 | constraints.append(last.layoutConstraint(for: [.top(.limit(on: .outer))])) 87 | } else { 88 | constraints.append(scrollView.layoutConstraint(for: [.bottom(.limit(on: .inner))])) 89 | } 90 | blocks.blocks += [ 91 | view.layoutBlock( 92 | with: Layout(x: .equal, y: blocks.last == nil ? .bottom() : .bottom(between: 0...10), width: .equal, height: .fixed(50)) + .top(0...), 93 | constraints: constraints 94 | ) 95 | ] 96 | blocks.last = view 97 | }.blocks + [ 98 | borderLayer.layoutBlock(constraints: [ 99 | scrollView.contentLayoutConstraint(for: [.equally]) 100 | ]) 101 | ] 102 | ) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutTests.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | import CGLayout 5 | import PlaygroundSupport 6 | 7 | let layout = Layout(x: .left(15), y: .top(15), width: .scaled(0.5), height: .fixed(20)) 8 | let sourceView = UIView(frame: UIScreen.main.bounds.insetBy(dx: 200, dy: 200)) 9 | sourceView.backgroundColor = .red 10 | let targetView = UIView(frame: CGRect(x: 20, y: 400, width: 200, height: 40)) 11 | targetView.backgroundColor = .black 12 | sourceView.addSubview(targetView) 13 | 14 | layout.apply(for: targetView) 15 | 16 | let scaleView = UIView() 17 | scaleView.backgroundColor = .gray 18 | let scaledView = UIView() 19 | scaledView.backgroundColor = .green 20 | let scaled2View = UIView() 21 | scaled2View.backgroundColor = .lightGray 22 | sourceView.addSubview(scaled2View) 23 | sourceView.addSubview(scaledView) 24 | sourceView.addSubview(scaleView) 25 | 26 | let scheme = LayoutScheme(blocks: [ 27 | scaleView.layoutBlock( 28 | with: Layout(x: .center(), y: .center(), width: .fixed(50), height: .fixed(50)) 29 | ), 30 | scaledView.layoutBlock( 31 | with: Layout(x: .left(-10), y: .top(-10), width: .boxed(-20), height: .boxed(-20)), 32 | constraints: [ 33 | scaleView.layoutConstraint(for: [ 34 | .size(.width()), .size(.height()), .center(.align(by: .center)) 35 | ]) 36 | ] 37 | ), 38 | scaled2View.layoutBlock( 39 | with: Layout(x: .left(multiplier: -0.25), y: .top(multiplier: -0.25), width: .scaled(1.5), height: .scaled(1.5)), 40 | constraints: [ 41 | scaledView.layoutConstraint(for: [ 42 | .size(.width()), .size(.height()), .center(.align(by: .center)) 43 | ]) 44 | ] 45 | ) 46 | ]) 47 | 48 | scheme.layout() 49 | 50 | PlaygroundPage.current.liveView = sourceView 51 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutWorkspaceAlign.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import UIKit 4 | import CGLayout 5 | import PlaygroundSupport 6 | 7 | extension UIView { 8 | convenience init(frame: CGRect, backgroundColor: UIColor) { 9 | self.init(frame: frame) 10 | self.backgroundColor = backgroundColor 11 | } 12 | } 13 | 14 | public extension UIView { 15 | func addSubviews(_ subviews: S) where S.Iterator.Element: UIView { 16 | subviews.map { 17 | $0.backgroundColor = $0.backgroundColor?.withAlphaComponent(0.5) 18 | return $0 19 | }.forEach(addSubview) 20 | } 21 | } 22 | public extension CGRect { 23 | static func random(in source: CGRect) -> CGRect { 24 | let o = CGPoint(x: CGFloat(arc4random_uniform(UInt32(source.width))), y: CGFloat(arc4random_uniform(UInt32(source.height)))) 25 | let s = CGSize(width: CGFloat(arc4random_uniform(UInt32(source.width - o.x))), height: CGFloat(arc4random_uniform(UInt32(source.height - o.y)))) 26 | 27 | return CGRect(origin: o, size: s) 28 | } 29 | } 30 | 31 | func view(by index: Int, color: UIColor, frame: CGRect) -> UIView { 32 | let label = UILabel(frame: frame, backgroundColor: color) 33 | label.text = String(index) 34 | label.textAlignment = .center 35 | return label 36 | } 37 | 38 | let workspaceView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568)) 39 | workspaceView.backgroundColor = .lightGray 40 | PlaygroundPage.current.liveView = workspaceView 41 | 42 | let rect1 = view(by: 1, color: .red, 43 | frame: workspaceView.bounds.insetBy(dx: 100, dy: 200)) 44 | 45 | let inner = LayoutWorkspace.After.align(axis: _RectAxis.horizontal, anchor: _RectAxisAnchor.center) 46 | 47 | // cropped 48 | let rect2 = view(by: 2, color: .blue, 49 | frame: CGRect(origin: .zero, size: CGSize(width: 250, height: 100))) 50 | // cropped to zero 51 | let rect4 = view(by: 4, color: .yellow, 52 | frame: CGRect(x: 40, y: 400, width: 40, height: 40)) 53 | // equal 54 | let rect6 = view(by: 6, color: .cyan, 55 | frame: CGRect(x: 120, y: 500, width: 40, height: 40)) 56 | 57 | let outer = LayoutWorkspace.Before.align(axis: _RectAxis.horizontal, anchor: _RectAxisAnchor.center) 58 | // cropped 59 | let rect3 = view(by: 3, color: .green, 60 | frame: CGRect(origin: CGPoint(x: 70, y: 200), size: CGSize(width: 50, height: 100))) 61 | // cropped to zero 62 | let rect5 = view(by: 5, color: .magenta, 63 | frame: CGRect(x: 120, y: 400, width: 40, height: 40)) 64 | // equal 65 | let rect7 = view(by: 7, color: .brown, 66 | frame: CGRect(x: 10, y: 450, width: 40, height: 40)) 67 | 68 | /// comment for show initial state 69 | inner.formConstrain(sourceRect: &rect2.frame, by: rect1.frame) 70 | inner.formConstrain(sourceRect: &rect4.frame, by: rect1.frame) 71 | inner.formConstrain(sourceRect: &rect6.frame, by: rect1.frame) 72 | outer.formConstrain(sourceRect: &rect3.frame, by: rect1.frame) 73 | outer.formConstrain(sourceRect: &rect5.frame, by: rect1.frame) 74 | outer.formConstrain(sourceRect: &rect7.frame, by: rect1.frame) 75 | 76 | workspaceView.addSubviews([rect1, rect2, rect3, rect4, rect5, rect6, rect7]) 77 | 78 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutWorkspaceAlign.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutWorkspaceLimit.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import UIKit 4 | import CGLayout 5 | import PlaygroundSupport 6 | 7 | extension UIView { 8 | convenience init(frame: CGRect, backgroundColor: UIColor) { 9 | self.init(frame: frame) 10 | self.backgroundColor = backgroundColor 11 | } 12 | } 13 | 14 | public extension UIView { 15 | func addSubviews(_ subviews: S) where S.Iterator.Element: UIView { 16 | subviews.map { 17 | $0.backgroundColor = $0.backgroundColor?.withAlphaComponent(0.5) 18 | return $0 19 | }.forEach(addSubview) 20 | } 21 | } 22 | public extension CGRect { 23 | static func random(in source: CGRect) -> CGRect { 24 | let o = CGPoint(x: CGFloat(arc4random_uniform(UInt32(source.width))), y: CGFloat(arc4random_uniform(UInt32(source.height)))) 25 | let s = CGSize(width: CGFloat(arc4random_uniform(UInt32(source.width - o.x))), height: CGFloat(arc4random_uniform(UInt32(source.height - o.y)))) 26 | 27 | return CGRect(origin: o, size: s) 28 | } 29 | } 30 | 31 | func view(by index: Int, color: UIColor, frame: CGRect) -> UIView { 32 | let label = UILabel(frame: frame, backgroundColor: color) 33 | label.text = String(index) 34 | label.textAlignment = .center 35 | return label 36 | } 37 | 38 | let workspaceView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568)) 39 | workspaceView.backgroundColor = .lightGray 40 | PlaygroundPage.current.liveView = workspaceView 41 | 42 | let rect1 = view(by: 1, color: .red, 43 | frame: workspaceView.bounds.insetBy(dx: 100, dy: 200)) 44 | 45 | let inner = LayoutWorkspace.After.limit(axis: _RectAxis.horizontal, anchor: _RectAxisAnchor.leading) 46 | 47 | // cropped 48 | let rect2 = view(by: 2, color: .blue, 49 | frame: CGRect(origin: .zero, size: CGSize(width: 250, height: 100))) 50 | // cropped to zero 51 | let rect4 = view(by: 4, color: .yellow, 52 | frame: CGRect(x: 40, y: 400, width: 40, height: 40)) 53 | // equal 54 | let rect6 = view(by: 6, color: .cyan, 55 | frame: CGRect(x: 120, y: 500, width: 40, height: 40)) 56 | 57 | let outer = LayoutWorkspace.Before.limit(axis: _RectAxis.horizontal, anchor: _RectAxisAnchor.leading) 58 | // cropped 59 | let rect3 = view(by: 3, color: .green, 60 | frame: CGRect(origin: CGPoint(x: 70, y: 200), size: CGSize(width: 50, height: 100))) 61 | // cropped to zero 62 | let rect5 = view(by: 5, color: .magenta, 63 | frame: CGRect(x: 120, y: 400, width: 40, height: 40)) 64 | // equal 65 | let rect7 = view(by: 7, color: .brown, 66 | frame: CGRect(x: 10, y: 450, width: 40, height: 40)) 67 | 68 | /// comment for show initial state 69 | inner.formConstrain(sourceRect: &rect2.frame, by: rect1.frame) 70 | inner.formConstrain(sourceRect: &rect4.frame, by: rect1.frame) 71 | inner.formConstrain(sourceRect: &rect6.frame, by: rect1.frame) 72 | outer.formConstrain(sourceRect: &rect3.frame, by: rect1.frame) 73 | outer.formConstrain(sourceRect: &rect5.frame, by: rect1.frame) 74 | outer.formConstrain(sourceRect: &rect7.frame, by: rect1.frame) 75 | 76 | workspaceView.addSubviews([rect1, rect2, rect3, rect4, rect5, rect6, rect7]) 77 | 78 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutWorkspaceLimit.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutWorkspacePull.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import UIKit 4 | import CGLayout 5 | import PlaygroundSupport 6 | 7 | extension UIView { 8 | convenience init(frame: CGRect, backgroundColor: UIColor) { 9 | self.init(frame: frame) 10 | self.backgroundColor = backgroundColor 11 | } 12 | } 13 | 14 | public extension UIView { 15 | func addSubviews(_ subviews: S) where S.Iterator.Element: UIView { 16 | subviews.map { 17 | $0.backgroundColor = $0.backgroundColor?.withAlphaComponent(0.5) 18 | return $0 19 | }.forEach(addSubview) 20 | } 21 | } 22 | public extension CGRect { 23 | static func random(in source: CGRect) -> CGRect { 24 | let o = CGPoint(x: CGFloat(arc4random_uniform(UInt32(source.width))), y: CGFloat(arc4random_uniform(UInt32(source.height)))) 25 | let s = CGSize(width: CGFloat(arc4random_uniform(UInt32(source.width - o.x))), height: CGFloat(arc4random_uniform(UInt32(source.height - o.y)))) 26 | 27 | return CGRect(origin: o, size: s) 28 | } 29 | } 30 | 31 | func view(by index: Int, color: UIColor, frame: CGRect) -> UIView { 32 | let label = UILabel(frame: frame, backgroundColor: color) 33 | label.text = String(index) 34 | label.textAlignment = .center 35 | return label 36 | } 37 | 38 | let workspaceView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568)) 39 | workspaceView.backgroundColor = .lightGray 40 | PlaygroundPage.current.liveView = workspaceView 41 | 42 | let rect1 = view(by: 1, color: .red, 43 | frame: workspaceView.bounds.insetBy(dx: 100, dy: 200)) 44 | 45 | let inner = LayoutWorkspace.After.pull(axis: _RectAxis.horizontal, anchor: _RectAxisAnchor.leading) 46 | // cropped 47 | let rect2 = view(by: 2, color: .blue, 48 | frame: CGRect(origin: .zero, size: CGSize(width: 250, height: 100))) 49 | // cropped to zero 50 | let rect4 = view(by: 4, color: .yellow, 51 | frame: CGRect(x: 40, y: 400, width: 40, height: 40)) 52 | // pulled 53 | let rect6 = view(by: 6, color: .cyan, 54 | frame: CGRect(x: 120, y: 500, width: 40, height: 40)) 55 | 56 | let outer = LayoutWorkspace.Before.pull(axis: _RectAxis.horizontal, anchor: _RectAxisAnchor.leading) 57 | // cropped 58 | let rect3 = view(by: 3, color: .green, 59 | frame: CGRect(origin: CGPoint(x: 70, y: 200), size: CGSize(width: 50, height: 100))) 60 | // cropped to zero 61 | let rect5 = view(by: 5, color: .magenta, 62 | frame: CGRect(x: 120, y: 400, width: 40, height: 40)) 63 | // pulled 64 | let rect7 = view(by: 7, color: .brown, 65 | frame: CGRect(x: 10, y: 450, width: 40, height: 40)) 66 | 67 | /// comment for show initial state 68 | inner.formConstrain(sourceRect: &rect2.frame, by: rect1.frame) 69 | inner.formConstrain(sourceRect: &rect4.frame, by: rect1.frame) 70 | inner.formConstrain(sourceRect: &rect6.frame, by: rect1.frame) 71 | outer.formConstrain(sourceRect: &rect3.frame, by: rect1.frame) 72 | outer.formConstrain(sourceRect: &rect5.frame, by: rect1.frame) 73 | outer.formConstrain(sourceRect: &rect7.frame, by: rect1.frame) 74 | 75 | workspaceView.addSubviews([rect1, rect2, rect3, rect4, rect5, rect6, rect7]) 76 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/LayoutWorkspacePull.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/RectAnchors.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import UIKit 4 | import CGLayout 5 | import PlaygroundSupport 6 | 7 | let sourceView = UIView(frame: UIScreen.main.bounds.insetBy(dx: 200, dy: 200)) 8 | sourceView.backgroundColor = .red 9 | let targetView = UIView() 10 | targetView.backgroundColor = .black 11 | sourceView.addSubview(targetView) 12 | 13 | PlaygroundPage.current.liveView = sourceView 14 | 15 | let layout = targetView.block { (anchors) in 16 | anchors.width.equal(to: 200) 17 | anchors.height.equal(to: 40) 18 | anchors.centerX.align(by: sourceView.layoutAnchors.centerX) 19 | anchors.centerY.align(by: sourceView.layoutAnchors.centerY) 20 | } 21 | 22 | layout.layout() 23 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/RectAnchors.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/Stack.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | import PlaygroundSupport 5 | import CGLayout 6 | 7 | let sourceView = UIView(frame: UIScreen.main.bounds) 8 | sourceView.backgroundColor = .black 9 | let stackLayoutGuide = StackLayoutGuide() 10 | sourceView.add(layoutGuide: stackLayoutGuide) 11 | 12 | // view 13 | let view = UIView() 14 | view.backgroundColor = .lightGray 15 | stackLayoutGuide.addArranged(element: .uiView(view)) 16 | 17 | // layer 18 | let layer = CALayer() 19 | layer.backgroundColor = UIColor.gray.cgColor 20 | stackLayoutGuide.addArranged(element: .caLayer(layer)) 21 | 22 | // layout guide 23 | let stack = StackLayoutGuide() 24 | stack.scheme.axis = CGRectAxis.vertical 25 | let view2 = UILabel() 26 | view2.backgroundColor = .lightGray 27 | view2.text = "Stack" 28 | let layer2 = CALayer() 29 | layer.backgroundColor = UIColor.gray.cgColor 30 | stack.addArranged(element: .uiView(view2)) 31 | stack.addArranged(element: .caLayer(layer2)) 32 | stackLayoutGuide.addArranged(element: .layoutGuide(stack)) 33 | 34 | PlaygroundPage.current.liveView = sourceView 35 | 36 | let layout = stackLayoutGuide.layoutBlock() 37 | 38 | layout.layout() 39 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/Pages/SwiftUI.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PlaygroundSupport 3 | import SwiftUI 4 | 5 | struct SomeView: View { 6 | var body: some View { 7 | Text("Hello world!") 8 | } 9 | } 10 | 11 | let viewController = UIHostingController(rootView: SomeView()) 12 | 13 | PlaygroundPage.current.liveView = viewController 14 | -------------------------------------------------------------------------------- /Example/CGLayoutPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Example/CGLayout_Example.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | #### Have you feedback or recommendations? Please, create issue, or pull request. 4 | 5 | Recomendations for your pull requests: 6 | * Write tests. 7 | * Follow style guide. 8 | * Write a good commit message 9 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | pod 'CGLayout', :path => '../' 4 | 5 | target 'CGLayout_Example' do 6 | 7 | target 'CGLayout_Tests' do 8 | inherit! :search_paths 9 | 10 | end 11 | end 12 | target 'CGLayout-tvOS' do 13 | end 14 | target 'CGLayout-macOS' do 15 | target 'CGLayout-macOSTests' do 16 | inherit! :search_paths 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 k-o-d-e-n 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CGLayout", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "CGLayout", 12 | targets: ["CGLayout"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "CGLayout", 23 | dependencies: [], 24 | path: "Sources/Classes"), 25 | .testTarget( 26 | name: "CGLayoutTests", 27 | dependencies: ["CGLayout"]), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CGLayout 2 | 3 | # [DEPRECATED] Replaced with new implementation - [LayoutUI](https://github.com/k-o-d-e-n/LayoutUI) 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/CGLayout.svg?style=flat)](http://cocoapods.org/pods/CGLayout) 6 | [![License](https://img.shields.io/cocoapods/l/CGLayout.svg?style=flat)](http://cocoapods.org/pods/CGLayout) 7 | [![Platform](https://img.shields.io/cocoapods/p/CGLayout.svg?style=flat)](http://cocoapods.org/pods/CGLayout) 8 | 9 |

10 | 11 |

12 | 13 | Powerful autolayout framework, that can manage UIView(NSView), CALayer and not rendered views. Has cross-hierarchy coordinate space. Implementation performed on rect-based constraints. 14 | Fast, asynchronous, declarative, cacheable, extensible. Supported iOS, macOS, tvOS, Linux. 15 | 16 |

17 | 18 |

19 | Performed by [LayoutBenchmarkFramework](https://github.com/lucdion/LayoutFrameworkBenchmark) 20 | 21 | ## Quick tutorial 22 | 23 | Layout with `CGLayout` built using layout-blocks. To combine blocks into single unit use `LayoutScheme` entity (or other entities that has suffix `Scheme`). 24 | ```swift 25 | let subviewsScheme = LayoutScheme(blocks: [ 26 | // ... layout blocks 27 | ]) 28 | ``` 29 | To define block for "view" element use `LayoutBlock` entity, or just use convenience getter methods `func layoutBlock(with:constraints:)`. 30 | ```swift 31 | titleLabel.layoutBlock( 32 | with: Layout(x: .center(), y: .top(5), width: .scaled(1), height: .fixed(120)), 33 | constraints: [ 34 | logoImageView.layoutConstraint(for: [.bottom(.limit(on: .inner))]) 35 | ] 36 | ) 37 | /// or using anchors 38 | titleLabel.layoutBlock( 39 | with: Layout(x: .center(), y: .top(5), width: .scaled(1), height: .fixed(120)), 40 | constraints: { anchors in 41 | anchors.top.equal(to: logoImageView.layoutAnchors.bottom) 42 | } 43 | ) 44 | ``` 45 | For understanding how need to built layout block, let's see layout process in `LayoutBlock`. 46 | For example we have this configuration: 47 | ```swift 48 | LayoutBlock( 49 | with: layoutElement, 50 | layout: Layout(x: .left(10), y: .top(10), width: .boxed(10), height: .boxed(10)), 51 | constraints: [ 52 | element1.layoutConstraint(for: [ 53 | .bottom(.limit(on: .outer)), .right(.limit(on: .inner)) 54 | ]), 55 | element2.layoutConstraint(for: [ 56 | .right(.limit(on: .outer)), .bottom(.limit(on: .inner)) 57 | ]) 58 | ] 59 | ) 60 | ``` 61 |

62 | 63 | 64 |

65 | You have to carefully approach the creation of blocks, because anchors and based on them constraints not have priority and is applying sequentially. 66 | Constraints should operate actual frames, therefore next layout block must have constraints with "views", that will not change frame. 67 | 68 | Layout anchors are limiters, that is oriented on frame properties (such as sides, size, position). 69 | Any side-based anchors have three base implementations: alignment, limitation(cropping), pulling. Each this implementation have dependency on working space: inner and outer. 70 | Size-based anchors are represented by two implementations: size, insets. 71 | All layout anchors you can find in `enum LayoutAnchor`. 72 | 73 | To create associated layout constraints use `protocol LayoutConstraintProtocol`. 74 | Framework provides such default implementations: 75 | - `LayoutConstraint`: simple associated constraint that uses `var frame` of passed element to constrain source rect. Use him to build dependency on external workspace. 76 | - `AdjustLayoutConstraint`: associated constraint to adjust size of source space. Only elements conform to `protocol AdjustableLayoutElement` can use it. 77 | - `ContentLayoutConstraint`: associated constraint that uses internal bounds to constrain, defined in 'layoutBounds' property of `protocol LayoutElement`. Use it if you need to create dependency on internal workspace. For example, element inside `UIScrollView`. 78 | - `AnonymConstraint`: constraint to restrict source space independently from external environment. 79 | - `MutableLayoutConstraint`: Layout constraint that creates possibility to change active state. 80 | You can find all this constraints through convenience functions in related elements. Use him to build layout blocks. 81 | 82 | In common case, adjust constraints should be apply after any other constraints (but not always). 83 | ```swift 84 | weatherLabel.layoutBlock( 85 | with: Layout(x: .left(10), y: .top(), width: .scaled(1), height: .scaled(1)), 86 | constraints: [ 87 | weatherImageView.layoutConstraint(for: [.top(.limit(.inner)), .right(.limit(.outer)), .height()]), 88 | weatherLabel.adjustLayoutConstraint(for: [.width()]) 89 | ] 90 | ) 91 | ``` 92 | 93 | ```swift 94 | AnonymConstraint(anchors: [ 95 | Inset(UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 15)) 96 | ]) 97 | ``` 98 | 99 | For implementing custom layout entities and save strong typed code, use `static func build(_ base: Conformed) -> Self` method. 100 | 101 | Each layout-block has methods for layout, take snapshot and applying snapshot. 102 | Consequently you may use layout-blocks for direct layout, background layout and cached layout: 103 | ```swift 104 | // layout directly 105 | layoutScheme.layout() 106 | 107 | // layout in background 108 | let bounds = view.bounds 109 | DispatchQueue.global(qos: .background).async { 110 | let snapshot = self.layoutScheme.snapshot(for: bounds) 111 | DispatchQueue.main.sync { 112 | self.layoutScheme.apply(snapshot: snapshot) 113 | } 114 | } 115 | 116 | // cached layout 117 | if UIDevice.current.orientation.isPortrait, let snapshot = portraitSnapshot { 118 | layoutScheme.apply(snapshot: snapshot) 119 | } else if UIDevice.current.orientation.isLandscape, let snapshot = landscapeSnapshot { 120 | layoutScheme.apply(snapshot: snapshot) 121 | } else { 122 | layoutScheme.layout() 123 | } 124 | ``` 125 | 126 | Typical implementation `sizeThatFits(_:)` method 127 | 128 | ```swift 129 | func sizeThatFits(_ size: CGSize) -> CGSize { 130 | let sourceRect = CGRect(origin: .zero, size: size) 131 | let snapshot = scheme.snapshot(for: sourceRect) 132 | return snapshot.frame 133 | } 134 | ``` 135 | 136 | ### LayoutGuide 137 | 138 | Framework provides `LayoutGuide` as analogue `UILayoutGuide`. It has possible to generate views and add them to hierarchy. 139 | `LayoutGuide` can used as invisible limiter and also as layout container. 140 | Default layout containers: 141 | - `StackLayoutGuide` - simple implementation of stack. 142 | - `ScrollLayoutGuide` - has similar interface with `UIScrollView`. By use him we can enable scrolling absolutely everywhere. 143 | - `LayoutPlaceholder` - single element container that can load view lazily. Has default implementations for `CALayer` - `LayerPlaceholder` and `UIView` - `ViewPlaceholder`. 144 | - `UIViewPlaceholder` - single element container based on `UILayoutGuide`. 145 | 146 | `UILayouGuide` also adopts `LayoutElement` protocol. Therefore you can safely build constraints based on `UIView.safeAreaLayoutGuide` and others. 147 | 148 | ### RTL 149 | To enable Righ-to-Left mode use global configuration: 150 | ```swift 151 | CGLConfiguration.default.isRTLMode = true 152 | ``` 153 | 154 | For more details, see documentation and example project. 155 | 156 | ## Code documentation 157 | 158 | See [here](https://k-o-d-e-n.github.io/CGLayout/) 159 | 160 | ## Example 161 | 162 | ### macOS, iOS, tvOS 163 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 164 | 165 | ### Linux 166 | To run the example project, clone the repo, and run `swift run` from the `linux_example` directory first. 167 | 168 | ## Requirements 169 | 170 | Swift 5 171 | 172 | ## Installation 173 | 174 | CGLayout is available through [CocoaPods](http://cocoapods.org). To install 175 | it, simply add the following line to your Podfile: 176 | 177 | ```ruby 178 | pod "CGLayout" 179 | ``` 180 | 181 | ## Contributing 182 | 183 | I will be happy your feedback, advices and pull requests. For more information read [here](https://github.com/k-o-d-e-n/CGLayout/blob/master/CONTRIBUTING.md) 184 | 185 | ## Author 186 | 187 | Denis Koryttsev 188 | Email: koden.u8800@gmail.com 189 | Twitter: https://twitter.com/K_o_D_e_N 190 | 191 | ## License 192 | 193 | CGLayout is available under the MIT license. See the LICENSE file for more info. 194 | -------------------------------------------------------------------------------- /Resources/benchmark_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Resources/benchmark_result.png -------------------------------------------------------------------------------- /Resources/layout1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Resources/layout1.png -------------------------------------------------------------------------------- /Resources/layout2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Resources/layout2.png -------------------------------------------------------------------------------- /Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Resources/logo.png -------------------------------------------------------------------------------- /Resources/logo.xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Resources/logo.xd -------------------------------------------------------------------------------- /Sources/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Sources/Assets/.gitkeep -------------------------------------------------------------------------------- /Sources/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/Sources/Classes/.gitkeep -------------------------------------------------------------------------------- /Sources/Classes/common.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonExtensions.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 04/09/2017. 6 | // Copyright © 2017 K-o-D-e-N. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #elseif os(Linux) 14 | import Foundation 15 | #endif 16 | 17 | internal func debugAction(_ action: () -> Void) { 18 | #if DEBUG 19 | action() 20 | #endif 21 | } 22 | 23 | internal func debugLog(_ message: String, _ file: String = #file, _ line: Int = #line) { 24 | debugAction { 25 | debugPrint("File: \(file)") 26 | debugPrint("Line: \(line)") 27 | debugPrint("Message: \(message)") 28 | } 29 | } 30 | 31 | internal func debugWarning(_ message: @autoclosure () -> String) { 32 | debugWarning(true, message()) 33 | } 34 | 35 | internal func debugWarning(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String) { 36 | debugAction { 37 | if condition() { 38 | if ProcessInfo.processInfo.arguments.contains("CGL_LOG_WARNINGS") { 39 | debugPrint("CGLayout WARNING: \(message())") 40 | } 41 | if ProcessInfo.processInfo.arguments.contains("CGL_THROW_ON_WARNING") { fatalError() } 42 | } 43 | } 44 | } 45 | 46 | internal func debugFatalError(_ condition: @autoclosure () -> Bool = true, 47 | _ message: String = "", _ file: String = #file, _ line: Int = #line) { 48 | debugAction { 49 | if condition() { 50 | debugLog(message, file, line) 51 | fatalError(message) 52 | } 53 | } 54 | } 55 | 56 | @discardableResult 57 | func syncGuard(mainThread action: @autoclosure () -> T) -> T { 58 | return _syncGuard(action) 59 | } 60 | 61 | @discardableResult 62 | func syncGuard(mainThread action: () -> T) -> T { 63 | return _syncGuard(action) 64 | } 65 | 66 | @discardableResult 67 | func _syncGuard(_ action: () -> T) -> T { 68 | #if os(iOS) || os(tvOS) || os(macOS) 69 | if !Thread.isMainThread { 70 | return DispatchQueue.main.sync(execute: action) 71 | } else { 72 | return action() 73 | } 74 | #else 75 | return action() 76 | #endif 77 | } 78 | 79 | #if os(iOS) || os(tvOS) 80 | public typealias EdgeInsets = UIEdgeInsets 81 | #endif 82 | #if os(macOS) || os(Linux) 83 | public typealias EdgeInsets = NSEdgeInsets 84 | #endif 85 | 86 | func -(l: CGSize, r: CGFloat) -> CGSize { return CGSize(width: l.width - r, height: l.height - r) } 87 | func +(l: CGSize, r: CGFloat) -> CGSize { return CGSize(width: l.width + r, height: l.height + r) } 88 | func *(l: CGSize, r: CGFloat) -> CGSize { return CGSize(width: l.width * r, height: l.height * r) } 89 | 90 | extension CGPoint { 91 | func positive() -> CGPoint { return CGPoint(x: abs(x), y: abs(y)) } 92 | func negated() -> CGPoint { return CGPoint(x: -x, y: -y) } 93 | } 94 | 95 | extension CGRect { 96 | var left: CGFloat { return minX } 97 | var right: CGFloat { return maxX } 98 | var top: CGFloat { return minY } 99 | var bottom: CGFloat { return maxY } 100 | 101 | var distanceFromOrigin: CGSize { return CGSize(width: maxX, height: maxY) } 102 | func distance(from point: CGPoint) -> CGSize { return CGSize(width: maxX - point.x, height: maxY - point.y) } 103 | } 104 | extension CGRect { 105 | mutating func apply(edgeInsets: EdgeInsets) { 106 | self = EdgeInsetsInsetRect(self, edgeInsets) 107 | } 108 | func applying(edgeInsets: EdgeInsets) -> CGRect { var this = self; this.apply(edgeInsets: edgeInsets); return this } 109 | 110 | public func asLayout() -> Layout { return Layout(x: .left(origin.x), y: .top(origin.y), width: .fixed(width), height: .fixed(height)) } 111 | } 112 | 113 | func EdgeInsetsInsetRect(_ rect: CGRect, _ edgeInsets: EdgeInsets) -> CGRect { 114 | #if os(iOS) || os(tvOS) 115 | return rect.inset(by: edgeInsets) 116 | #else 117 | return CGRect(x: rect.origin.x + edgeInsets.left, y: rect.origin.y + edgeInsets.top, 118 | width: rect.size.width - edgeInsets.horizontal, height: rect.size.height - edgeInsets.vertical) 119 | #endif 120 | } 121 | 122 | #if os(macOS) || os(Linux) 123 | extension EdgeInsets: Equatable { 124 | public static func ==(lhs: EdgeInsets, rhs: EdgeInsets) -> Bool { 125 | return lhs.left == rhs.left && lhs.right == rhs.right 126 | && lhs.top == rhs.top && lhs.bottom == rhs.bottom 127 | } 128 | } 129 | #endif 130 | 131 | extension EdgeInsets { 132 | var horizontal: CGFloat { return left + right } 133 | var vertical: CGFloat { return top + bottom } 134 | #if os(macOS) || os(Linux) 135 | public static var zero: EdgeInsets { return EdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } 136 | #endif 137 | } 138 | 139 | #if os(macOS) || os(iOS) || os(tvOS) 140 | public extension CALayer { 141 | convenience init(frame: CGRect) { 142 | self.init() 143 | self.frame = frame 144 | } 145 | } 146 | #endif 147 | 148 | extension Bool { 149 | mutating func `switch`() { 150 | self = self ? false : true 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Sources/Classes/container.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutElementsContainer.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 01/10/2017. 6 | // 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #elseif os(Linux) 14 | import Foundation 15 | #endif 16 | 17 | protocol EnterPoint { 18 | associatedtype Container 19 | 20 | var child: LayoutElement { get } 21 | 22 | func add(to container: Container) 23 | } 24 | 25 | protocol ContainerManagement { 26 | associatedtype Child 27 | associatedtype Container 28 | func add(_ child: Child, to container: Container) 29 | } 30 | 31 | struct Enter: EnterPoint { 32 | let base: _AnyEnterPoint 33 | var child: LayoutElement { return base.child } 34 | 35 | init(_ base: Point) where Point.Container == Container { 36 | self.base = _Enter(base) 37 | } 38 | 39 | init(_ element: Management.Child, managedBy management: Management) where Management.Child: LayoutElement, Management.Container == Container { 40 | self.base = _Enter(Enter.Any.init(element: element, management: management)) 41 | } 42 | 43 | func add(to container: Container) { 44 | base.add(to: container) 45 | } 46 | 47 | private struct `Any`: EnterPoint where Management.Child: LayoutElement, Management.Container == Container { 48 | let element: Management.Child 49 | let management: Management 50 | var child: LayoutElement { element } 51 | func add(to container: Container) { 52 | management.add(element, to: container) 53 | } 54 | } 55 | } 56 | 57 | class _AnyEnterPoint: EnterPoint { 58 | var child: LayoutElement { fatalError("Unimplemented") } 59 | func add(to container: Container) { 60 | fatalError("Unimplemented") 61 | } 62 | } 63 | final class _Enter: _AnyEnterPoint { 64 | private let base: Base 65 | override var child: LayoutElement { base.child } 66 | init(_ base: Base) { 67 | self.base = base 68 | } 69 | override func add(to container: Base.Container) { 70 | base.add(to: container) 71 | } 72 | } 73 | 74 | 75 | /// The container does not know which child is being added, 76 | /// but the child knows exactly where it is being added 77 | 78 | protocol ChildrenProtocol { 79 | associatedtype Child 80 | func add(_ child: Child) 81 | //func remove(_ child: Child) 82 | } 83 | 84 | #if os(iOS) 85 | extension UIView { 86 | struct SublayerManagement: ContainerManagement { 87 | func add(_ child: CALayer, to container: Container) { 88 | container.layer.addSublayer(child) 89 | } 90 | } 91 | } 92 | 93 | extension CALayer { 94 | struct Layers: ChildrenProtocol { 95 | let layer: CALayer 96 | func add(_ child: CALayer) { 97 | layer.addSublayer(layer) 98 | } 99 | } 100 | } 101 | 102 | public extension UIView { 103 | var sublayers: Layers { return Layers(base: CALayer.Layers(layer: layer)) } 104 | struct Layers: ChildrenProtocol { 105 | let base: CALayer.Layers 106 | func add(_ child: CALayer) { 107 | base.add(child) 108 | } 109 | } 110 | var layoutGuides: LayoutGuides { return LayoutGuides(view: self) } 111 | struct LayoutGuides: ChildrenProtocol { 112 | let view: UIView 113 | func add(_ child: LayoutGuide) { 114 | child.add(to: view) 115 | } 116 | } 117 | } 118 | 119 | public extension StackLayoutGuide where Parent: UIView { 120 | var views: Views { return Views(stackLayoutGuide: self) } 121 | struct Views: ChildrenProtocol { 122 | let stackLayoutGuide: StackLayoutGuide 123 | func add(_ child: UIView) { 124 | stackLayoutGuide.ownerElement?.addSubview(child) 125 | stackLayoutGuide.items.append(.uiView(child)) 126 | } 127 | } 128 | 129 | var layers: Layers { return Layers(stackLayoutGuide: self) } 130 | struct Layers: ChildrenProtocol { 131 | let stackLayoutGuide: StackLayoutGuide 132 | func add(_ child: CALayer) { 133 | stackLayoutGuide.ownerElement?.layer.addSublayer(child) 134 | stackLayoutGuide.items.append(.caLayer(child)) 135 | } 136 | } 137 | 138 | var layoutGuides: LayoutGuides { return LayoutGuides(stackLayoutGuide: self) } 139 | struct LayoutGuides: ChildrenProtocol { 140 | let stackLayoutGuide: StackLayoutGuide 141 | func add(_ child: LayoutGuide) { 142 | stackLayoutGuide.ownerElement?.add(layoutGuide: child) 143 | stackLayoutGuide.items.append(.layoutGuide(child)) 144 | } 145 | } 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /Sources/Classes/deprecated.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGLayoutDeprecated.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 07/10/2017. 6 | // 7 | 8 | import Foundation 9 | 10 | extension LayoutSnapshotProtocol { 11 | @available(*, deprecated, renamed: "frame") 12 | public var snapshotFrame: CGRect { return frame } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Classes/evolution.cglayout/system.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // system.cglayout.swift 3 | // Pods 4 | // 5 | // Created by Denis Koryttsev on 12/10/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | #if os(macOS) || os(iOS) || os(tvOS) 11 | 12 | @available(macOS 10.12, iOS 10.0, *) 13 | public class LayoutManager: NSObject { 14 | var deinitialization: ((LayoutManager) -> Void)? 15 | weak var item: LayoutElement! 16 | var scheme: LayoutScheme! 17 | private var isNeedLayout: Bool = false 18 | 19 | public func setNeedsLayout() { 20 | if !isNeedLayout { 21 | isNeedLayout = true 22 | scheduleLayout() 23 | } 24 | } 25 | 26 | override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 27 | guard change != nil, item != nil else { 28 | return 29 | } 30 | scheduleLayout() 31 | } 32 | 33 | private func scheduleLayout() { 34 | RunLoop.current.perform { 35 | self.scheme.layout(in: self.item.layoutBounds) 36 | self.isNeedLayout = false 37 | } 38 | } 39 | 40 | deinit { 41 | deinitialization?(self) 42 | } 43 | } 44 | #endif 45 | 46 | #if os(iOS) 47 | @available(iOS 10.0, *) 48 | public extension LayoutManager where Item: UIView { 49 | convenience init(view: UIView, scheme: LayoutScheme) { 50 | self.init() 51 | self.item = view 52 | self.scheme = scheme 53 | self.deinitialization = { lm in view.removeObserver(lm, forKeyPath: "layer.bounds") } 54 | view.addObserver(self, forKeyPath: "layer.bounds", options: [], context: nil) 55 | scheme.layout(in: view.layoutBounds) 56 | } 57 | } 58 | 59 | /// Base class with layout skeleton implementation. 60 | open class AutolayoutViewController: UIViewController { 61 | fileprivate lazy var internalLayout: LayoutScheme = self.loadInternalLayout() 62 | public lazy internal(set) var layoutScheme: LayoutScheme = self.loadLayout() 63 | public lazy var freeAreaLayoutGuide = LayoutGuide() 64 | 65 | override open func viewDidLoad() { 66 | super.viewDidLoad() 67 | view.add(layoutGuide: freeAreaLayoutGuide) 68 | } 69 | 70 | override open func viewDidLayoutSubviews() { 71 | super.viewDidLayoutSubviews() 72 | internalLayout.layout(in: view.layoutBounds) 73 | update(scheme: &layoutScheme) 74 | layout() 75 | } 76 | 77 | open func layout() { 78 | layoutScheme.layout(in: view.layoutBounds) 79 | } 80 | 81 | open func update(scheme: inout LayoutScheme) { 82 | /// subclass override 83 | /// use for update dynamic elements 84 | } 85 | 86 | open func loadLayout() -> LayoutScheme { 87 | /// subclass override 88 | /// layout = LayoutScheme(blocks: [LayoutBlockProtocol]) 89 | fatalError("You should override loading layout method") 90 | } 91 | 92 | fileprivate func loadInternalLayout() -> LayoutScheme { 93 | let visible: (inout CGRect) -> Void = { [unowned self] rect in 94 | if #available(iOS 11.0, tvOS 11.0, *) { 95 | rect = rect.inset(by: self.view.safeAreaInsets) 96 | } else { 97 | rect = rect.inset(by: self.viewContentInsets) 98 | } 99 | } 100 | return LayoutScheme( 101 | blocks: [freeAreaLayoutGuide.layoutBlock(constraints: [AnonymConstraint(transform: visible)])] 102 | ) 103 | } 104 | 105 | @available(iOS 9.0, *) 106 | private var viewContentInsets: UIEdgeInsets { 107 | let bars = heightBars 108 | return UIEdgeInsets(top: bars.top, 109 | left: 0, 110 | bottom: bars.bottom, 111 | right: 0) 112 | } 113 | 114 | @available(iOS 9.0, *) 115 | private var heightBars: (top: CGFloat, bottom: CGFloat) { 116 | guard let window = UIApplication.shared.delegate.flatMap({ $0.window }).flatMap({ $0 }), let superview = viewIfLoaded?.superview else { 117 | return (UIApplication.shared.statusBarFrame.height + (navigationController.map { $0.isNavigationBarHidden ? 0 : $0.navigationBar.frame.height } ?? 0), 118 | tabBarController.map { $0.tabBar.isHidden ? 0 : $0.tabBar.frame.height } ?? 0) 119 | } 120 | 121 | var topFrame = window.convert(UIApplication.shared.statusBarFrame, to: superview) 122 | topFrame = topFrame.union(navigationController.map { contr -> CGRect in 123 | contr.isNavigationBarHidden ? 124 | .zero : 125 | superview.convert(contr.navigationBar.frame, from: contr.navigationBar.superview) 126 | } ?? .zero) 127 | 128 | let bottomBarsTop = tabBarController.map { contr -> CGPoint in 129 | contr.tabBar.isHidden ? 130 | .zero : 131 | superview.convert(contr.tabBar.frame.origin, from: contr.tabBar.superview) 132 | } 133 | 134 | return (max(0, topFrame.maxY - view.frame.origin.y), 135 | max(0, bottomBarsTop.map { $0.y - view.frame.maxY } ?? 0)) 136 | } 137 | 138 | public func removeInactiveLayoutBlocks() { 139 | layoutScheme.removeInactiveBlocks() 140 | } 141 | public func insertLayout(block: LayoutBlockProtocol) { 142 | layoutScheme.insertLayout(block: block) 143 | } 144 | } 145 | 146 | open class ScrollLayoutViewController: AutolayoutViewController { 147 | private var isNeedCalculateContent: Bool = true 148 | open var scrollView: UIScrollView { fatalError() } 149 | var isScrolling: Bool { return scrollView.isDragging || scrollView.isDecelerating || scrollView.isZooming } 150 | 151 | open override func viewDidLayoutSubviews() { 152 | // skips super call 153 | if isNeedCalculateContent || !isScrolling { 154 | internalLayout.layout(in: scrollView.layoutBounds) 155 | update(scheme: &layoutScheme) 156 | layout() 157 | isNeedCalculateContent = false 158 | } 159 | } 160 | 161 | open override func layout() { 162 | super.layout() 163 | let contentRect = layoutScheme.currentRect 164 | scrollView.contentSize = CGSize(width: contentRect.maxX, height: contentRect.maxY) 165 | } 166 | 167 | public func setNeedsUpdateContentSize() { 168 | isNeedCalculateContent = true 169 | view.setNeedsLayout() 170 | } 171 | } 172 | #endif 173 | -------------------------------------------------------------------------------- /Sources/Classes/evolution.cglayout/workspace.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // workspace.cglayout.swift 3 | // Pods 4 | // 5 | // Created by Denis Koryttsev on 12/10/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol RectAxisAnchor { 11 | // func set(value: CGFloat, for rect: inout CGRect, in axis: RectAxis) 12 | func get(for rect: CGRect, in axis: RectAxis) -> CGFloat 13 | } 14 | struct CGRectAxisAnchor { 15 | public static var leading: RectAxisAnchor = Leading() 16 | struct Leading: RectAxisAnchor { 17 | func get(for rect: CGRect, in axis: RectAxis) -> CGFloat { 18 | return axis.get(minOf: rect) 19 | } 20 | } 21 | public static var trailing: RectAxisAnchor = Trailing() 22 | struct Trailing: RectAxisAnchor { 23 | func get(for rect: CGRect, in axis: RectAxis) -> CGFloat { 24 | return axis.get(maxOf: rect) 25 | } 26 | } 27 | public static var center: RectAxisAnchor = Center() 28 | struct Center: RectAxisAnchor { 29 | func get(for rect: CGRect, in axis: RectAxis) -> CGFloat { 30 | return axis.get(midOf: rect) 31 | } 32 | } 33 | public static var size: RectAxisAnchor = Size() 34 | struct Size: RectAxisAnchor { 35 | func get(for rect: CGRect, in axis: RectAxis) -> CGFloat { 36 | return axis.get(sizeAt: rect) 37 | } 38 | } 39 | } 40 | 41 | struct LayoutWorkspace { 42 | public struct Before { 43 | public static func align(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Align(axis: axis, anchor: anchor) } 44 | internal struct Align: RectBasedConstraint { 45 | let axis: RectAxis 46 | let anchor: RectAxisAnchor 47 | 48 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 49 | axis.set(origin: anchor.get(for: rect, in: axis) - axis.get(sizeAt: sourceRect), for: &sourceRect) 50 | } 51 | } 52 | public static func limit(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Limit(axis: axis, anchor: anchor) } 53 | internal struct Limit: RectBasedConstraint { 54 | let axis: RectAxis 55 | let anchor: RectAxisAnchor 56 | 57 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 58 | let anchorPosition = anchor.get(for: rect, in: axis) 59 | axis.set(size: max(0, min(axis.get(sizeAt: sourceRect), anchorPosition - axis.get(minOf: sourceRect))), 60 | for: &sourceRect) 61 | axis.set(origin: min(anchorPosition, axis.get(minOf: sourceRect)), for: &sourceRect) 62 | } 63 | } 64 | public static func pull(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Pull(axis: axis, anchor: anchor) } 65 | internal struct Pull: RectBasedConstraint { 66 | let axis: RectAxis 67 | let anchor: RectAxisAnchor 68 | 69 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 70 | let anchorPosition = anchor.get(for: rect, in: axis) 71 | axis.set(size: max(0, anchorPosition - axis.get(minOf: sourceRect)), 72 | for: &sourceRect) 73 | axis.set(origin: anchorPosition - axis.get(sizeAt: sourceRect), for: &sourceRect) 74 | } 75 | } 76 | } 77 | public struct After { 78 | public static func align(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Align(axis: axis, anchor: anchor) } 79 | internal struct Align: RectBasedConstraint { 80 | let axis: RectAxis 81 | let anchor: RectAxisAnchor 82 | 83 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 84 | axis.set(origin: anchor.get(for: rect, in: axis), for: &sourceRect) 85 | } 86 | } 87 | public static func limit(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Limit(axis: axis, anchor: anchor) } 88 | internal struct Limit: RectBasedConstraint { 89 | let axis: RectAxis 90 | let anchor: RectAxisAnchor 91 | 92 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 93 | let anchorPosition = anchor.get(for: rect, in: axis) 94 | axis.set(size: max(0, min(axis.get(sizeAt: sourceRect), axis.get(maxOf: sourceRect) - anchorPosition)), 95 | for: &sourceRect) 96 | axis.set(origin: max(anchorPosition, axis.get(minOf: sourceRect)), for: &sourceRect) 97 | } 98 | } 99 | public static func pull(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Pull(axis: axis, anchor: anchor) } 100 | internal struct Pull: RectBasedConstraint { 101 | let axis: RectAxis 102 | let anchor: RectAxisAnchor 103 | 104 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 105 | let anchorPosition = anchor.get(for: rect, in: axis) 106 | axis.set(size: max(0, axis.get(maxOf: sourceRect) - anchorPosition), 107 | for: &sourceRect) 108 | axis.set(origin: anchorPosition, for: &sourceRect) 109 | } 110 | } 111 | } 112 | public struct Center { 113 | public static func align(axis: RectAxis, anchor: RectAxisAnchor) -> RectBasedConstraint { return Align(axis: axis, anchor: anchor) } 114 | internal struct Align: RectBasedConstraint { 115 | let axis: RectAxis 116 | let anchor: RectAxisAnchor 117 | 118 | public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 119 | axis.set(origin: anchor.get(for: rect, in: axis) - axis.get(sizeAt: sourceRect) * 0.5, for: &sourceRect) 120 | } 121 | } 122 | // public static func limit(axis: RectAxis, anchor: RectAxisAnchor, limit limitAnchor: RectAxisAnchor) -> RectBasedConstraint { return Limit(axis: axis, anchor: anchor, limitAnchor: limitAnchor) } 123 | // internal struct Limit: RectBasedConstraint { 124 | // let axis: RectAxis 125 | // let anchor: RectAxisAnchor 126 | // let limitAnchor: RectAxisAnchor 127 | // 128 | // public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 129 | // let anchorPosition = anchor.value(for: rect, in: axis) 130 | // let limitAnchorPosition = limitAnchor.value(for: sourceRect, in: axis) 131 | // axis.set(size: max(0, min(axis.get(sizeAt: sourceRect), max( - anchorPosition))), 132 | // for: &sourceRect) 133 | // axis.set(origin: max(anchorPosition, axis.get(minOf: sourceRect)), for: &sourceRect) 134 | // } 135 | // } 136 | // public static func pull(axis: RectAxis, anchor: RectAxisAnchor, pull pullAnchor: RectAxisAnchor) -> RectBasedConstraint { return Pull(axis: axis, anchor: anchor, pullAnchor: pullAnchor) } 137 | // internal struct Pull: RectBasedConstraint { 138 | // let axis: RectAxis 139 | // let anchor: RectAxisAnchor 140 | // let pullAnchor: RectAxisAnchor 141 | // 142 | // public func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 143 | // let anchorPosition = anchor.value(for: rect, in: axis) 144 | // axis.set(size: max(0, abs(pullAnchor.value(for: sourceRect, in: axis) - anchorPosition)), 145 | // for: &sourceRect) 146 | // axis.set(origin: anchorPosition, for: &sourceRect) 147 | // } 148 | // } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Sources/Classes/private.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGLayoutPrivate.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 07/10/2017. 6 | // 7 | 8 | #if os(iOS) || os(tvOS) 9 | import UIKit 10 | #elseif os(macOS) 11 | import Cocoa 12 | #elseif os(Linux) 13 | import Foundation 14 | #endif 15 | 16 | // MARK: Protocols 17 | 18 | internal protocol AxisEntity { 19 | var axis: RectAxis { get } 20 | func by(axis: RectAxis) -> Self 21 | } 22 | internal protocol RectAxisLayout: RectBasedLayout, AxisEntity {} 23 | 24 | // MARK: Implementations 25 | 26 | internal struct ConstraintsAggregator: RectBasedConstraint { 27 | let constraints: [RectBasedConstraint] 28 | 29 | init(_ constraints: [RectBasedConstraint]) { 30 | self.constraints = constraints 31 | } 32 | 33 | func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 34 | constraints.forEach { $0.formConstrain(sourceRect: &sourceRect, by: rect) } 35 | } 36 | } 37 | 38 | /// Represents frame of block where was received. Contains snapshots for child blocks. 39 | internal struct LayoutSnapshot: LayoutSnapshotProtocol { 40 | let childSnapshots: [LayoutSnapshotProtocol] 41 | let frame: CGRect 42 | } 43 | 44 | internal struct _SizeThatFitsConstraint: RectBasedConstraint { 45 | weak var item: AdaptiveLayoutElement! 46 | func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 47 | sourceRect.size = item.sizeThatFits(rect.size) 48 | } 49 | } 50 | internal struct _MainThreadSizeThatFitsConstraint: RectBasedConstraint { 51 | weak var item: AdaptiveLayoutElement! 52 | func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 53 | sourceRect.size = syncGuard(mainThread: item.sizeThatFits(rect.size)) 54 | } 55 | } 56 | 57 | internal struct _MainThreadItemInLayoutTime: ElementInLayoutTime { 58 | var layoutBounds: CGRect { return syncGuard(mainThread: { item.layoutBounds }) } 59 | var superElement: LayoutElement? { return syncGuard(mainThread: { item.superElement }) } 60 | var frame: CGRect { 61 | set { 62 | let item = self.item 63 | syncGuard { item.frame = newValue }() 64 | } 65 | get { return syncGuard { item.frame } } 66 | } 67 | var bounds: CGRect { 68 | set { 69 | let item = self.item 70 | syncGuard { item.bounds = newValue }() 71 | } 72 | get { return syncGuard { item.bounds } } 73 | } 74 | 75 | var item: Item 76 | } 77 | -------------------------------------------------------------------------------- /Sources/Classes/rtl.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // rtl.cglayout.swift 3 | // Pods 4 | // 5 | // Created by Denis Koryttsev on 13/10/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CGLConfiguration { 11 | public var isRTLMode: Bool = false 12 | 13 | public static var `default` = CGLConfiguration() 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Classes/support.cglayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Layout.swift 3 | // CGLayout 4 | // 5 | // Created by Denis Koryttsev on 29/08/2017. 6 | // Copyright © 2017 K-o-D-e-N. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #elseif os(Linux) 14 | import Foundation 15 | #endif 16 | 17 | #if os(iOS) || os(tvOS) || os(macOS) 18 | extension CALayer: LayoutElement { 19 | public var inLayoutTime: ElementInLayoutTime { return _MainThreadItemInLayoutTime(item: self) } 20 | public var layoutBounds: CGRect { return bounds } 21 | public var superElement: LayoutElement? { return superlayer } 22 | public func removeFromSuperElement() { removeFromSuperlayer() } 23 | } 24 | #endif 25 | 26 | #if os(iOS) || os(tvOS) 27 | extension UIView: AdaptiveLayoutElement { 28 | public /// Entity that represents element in layout time 29 | var inLayoutTime: ElementInLayoutTime { return _MainThreadItemInLayoutTime(item: self) } 30 | @objc public /// Internal space for layout subelements 31 | var layoutBounds: CGRect { return bounds } 32 | /// Layout element that maintained this layout entity 33 | public var superElement: LayoutElement? { return superview } 34 | /// Removes layout element from hierarchy 35 | public func removeFromSuperElement() { removeFromSuperview() } 36 | } 37 | extension UIImageView: AdjustableLayoutElement { 38 | struct ContentConstraint: RectBasedConstraint { 39 | unowned var imageView: UIImageView 40 | 41 | func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 42 | if let image = syncGuard(mainThread: imageView.image) { 43 | let imageSize = image.size 44 | let minWidth = rect.width < rect.height 45 | switch syncGuard(mainThread: imageView.contentMode) { 46 | case .scaleAspectFit: 47 | if minWidth { 48 | sourceRect.size.width = rect.width 49 | sourceRect.size.height = (imageSize.height / imageSize.width) * rect.width 50 | } else { 51 | sourceRect.size.height = rect.height 52 | sourceRect.size.width = (imageSize.width / imageSize.height) * rect.height 53 | } 54 | case .scaleAspectFill: 55 | if minWidth { 56 | sourceRect.size.height = rect.height 57 | sourceRect.size.width = (imageSize.width / imageSize.height) * rect.height 58 | } else { 59 | sourceRect.size.width = rect.width 60 | sourceRect.size.height = (imageSize.height / imageSize.width) * rect.width 61 | } 62 | default: 63 | sourceRect.size = image.size 64 | } 65 | } else { 66 | sourceRect.size = .zero 67 | } 68 | } 69 | } 70 | public var contentConstraint: RectBasedConstraint { 71 | return ContentConstraint(imageView: self) 72 | } 73 | } 74 | extension UILabel: TextPresentedElement, AdjustableLayoutElement { 75 | struct ContentConstraint: RectBasedConstraint { 76 | unowned let label: UILabel 77 | func formConstrain(sourceRect: inout CGRect, by rect: CGRect) { 78 | // TODO: numberOfLines 79 | if let txt = syncGuard(mainThread: label.text) { 80 | #if os(iOS) && !os(tvOS) 81 | let font = syncGuard(mainThread: label.font) ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) 82 | #else 83 | let font = syncGuard(mainThread: label.font) ?? UIFont.systemFont(ofSize: 14) 84 | #endif 85 | sourceRect.size = txt.boundingRect( 86 | with: rect.size, 87 | options: [.usesLineFragmentOrigin, .usesFontLeading], 88 | attributes: [.font: font], 89 | context: nil 90 | ).size 91 | } else if let attrTxt = syncGuard(mainThread: label.attributedText) { 92 | sourceRect.size = attrTxt.boundingRect( 93 | with: rect.size, 94 | options: [.usesLineFragmentOrigin, .usesFontLeading], 95 | context: nil 96 | ).size 97 | } else { 98 | sourceRect.size = .zero 99 | } 100 | } 101 | } 102 | public var contentConstraint: RectBasedConstraint { 103 | return ContentConstraint(label: self) 104 | } 105 | public var baselineElement: Baseline { 106 | let key = "cglayout.label.baseline" 107 | guard case let baseline as Baseline = layer.value(forKey: key) else { 108 | let baseline = Baseline(label: self) 109 | layer.setValue(baseline, forKey: key) 110 | return baseline 111 | } 112 | 113 | return baseline 114 | } 115 | 116 | public final class Baseline: AnchoredLayoutElement, ElementInLayoutTime { 117 | unowned let label: UILabel 118 | public var frame: CGRect { 119 | set(newValue) { 120 | _syncGuard({ 121 | var rect = newValue 122 | rect.size.height = label.frame.height 123 | rect.origin.x = label.frame.minX 124 | #if os(iOS) 125 | let font = label.font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) 126 | #else 127 | let font = label.font ?? UIFont.systemFont(ofSize: 14) 128 | #endif 129 | rect.origin.y -= label.textRect(forBounds: label.bounds, limitedToNumberOfLines: label.numberOfLines).origin.y + font.ascender 130 | label.frame = rect 131 | }) 132 | } 133 | get { 134 | return _syncGuard({ 135 | var rect = label.frame 136 | rect.size.height = 0 137 | #if os(iOS) 138 | let font = label.font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) 139 | #else 140 | let font = label.font ?? UIFont.systemFont(ofSize: 14) 141 | #endif 142 | rect.origin.y += label.textRect(forBounds: label.bounds, limitedToNumberOfLines: label.numberOfLines).origin.y + font.ascender 143 | return rect 144 | }) 145 | } 146 | } 147 | public var bounds: CGRect { 148 | set {} 149 | get { return CGRect(origin: .zero, size: frame.size) } 150 | } 151 | public var superElement: LayoutElement? { return label.superview } 152 | public var layoutBounds: CGRect { return bounds } 153 | public var inLayoutTime: ElementInLayoutTime { return self } 154 | 155 | public func removeFromSuperElement() {} 156 | 157 | init(label: UILabel) { 158 | self.label = label 159 | } 160 | } 161 | } 162 | extension UIScrollView { 163 | public /// Internal space for layout subelements 164 | override var layoutBounds: CGRect { return CGRect(origin: .zero, size: contentSize) } 165 | } 166 | 167 | @available(iOS 9.0, *) 168 | extension UILayoutGuide: LayoutElement { 169 | @objc open var layoutBounds: CGRect { return bounds } 170 | public var inLayoutTime: ElementInLayoutTime { return _MainThreadItemInLayoutTime(item: self) } 171 | @objc open var frame: CGRect { get { return layoutFrame } set {} } 172 | @objc open var bounds: CGRect { get { return CGRect(origin: .zero, size: layoutFrame.size) } set {} } 173 | public var superElement: LayoutElement? { return owningView } 174 | @objc open func removeFromSuperElement() { owningView.map { $0.removeLayoutGuide(self) } } 175 | } 176 | #endif 177 | 178 | #if os(macOS) 179 | public typealias CGRect = NSRect 180 | extension NSView: LayoutElement { 181 | public /// Removes layout element from hierarchy 182 | func removeFromSuperElement() { removeFromSuperview() } 183 | public /// Entity that represents element in layout time 184 | var inLayoutTime: ElementInLayoutTime { return _MainThreadItemInLayoutTime(item: self) } 185 | public /// Layout element that maintains this layout entity 186 | weak var superElement: LayoutElement? { return superview } 187 | @objc public /// Internal space for layout subelements 188 | var layoutBounds: CGRect { return bounds } 189 | } 190 | extension NSScrollView { 191 | public /// Internal space for layout subelements 192 | override var layoutBounds: CGRect { return documentView?.bounds ?? contentView.bounds } 193 | } 194 | extension NSControl: AdaptiveLayoutElement, AdjustableLayoutElement { 195 | /// Constraint, that defines content size for item 196 | public var contentConstraint: RectBasedConstraint { return _MainThreadSizeThatFitsConstraint(item: self) } 197 | } 198 | extension NSView: AnchoredLayoutElement {} 199 | #endif 200 | 201 | #if os(iOS) || os(tvOS) 202 | extension UIView: AnchoredLayoutElement {} 203 | extension UILayoutGuide: AnchoredLayoutElement {} 204 | #endif 205 | 206 | #if os(iOS) || os(tvOS) || os(macOS) 207 | extension CALayer: AnchoredLayoutElement {} 208 | #endif 209 | -------------------------------------------------------------------------------- /Tests/CGLayoutTests/CGLayoutTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CGLayout 3 | 4 | #if os(Linux) 5 | 6 | class CGLayoutTests: XCTestCase { 7 | let bounds = CGRect(x: 0, y: 0, width: 500, height: 500) 8 | func testTopAlignment() { 9 | let view1 = Layer(frame: CGRect(x: 230, y: 305, width: 200, height: 100)) 10 | let view2 = Layer(frame: bounds) 11 | let alignment = Layout.Alignment.Vertical.top() 12 | 13 | alignment.formLayout(rect: &view1.frame, in: view2.frame) 14 | 15 | XCTAssertTrue(view1.frame.origin.y == view2.frame.origin.y) 16 | } 17 | 18 | func testContainer() { 19 | let layer = Layer(frame: CGRect(x: 230, y: 305, width: 200, height: 100)) 20 | let view = View(layer: layer) 21 | let lg = LayoutGuide(frame: .zero) 22 | let subview = View(frame: CGRect(x: 230, y: 305, width: 200, height: 100)) 23 | 24 | view.addSubItem(.layoutGuide(.inView(lg))) 25 | view.addSubItem(.view(subview)) 26 | 27 | XCTAssertTrue(lg.ownerElement! === view) 28 | XCTAssertTrue(view.subviews.contains(where: { $0 === subview })) 29 | XCTAssertTrue(view.layer.sublayers.contains(where: { $0 === subview.layer })) 30 | } 31 | 32 | func testNewAnchors2() { 33 | let window = Window(frame: CGRect(x: 0, y: 0, width: 500, height: 500)) 34 | let sourceView = View(frame: CGRect(x: 100, y: 100, width: 300, height: 300)) 35 | let targetView = View(frame: .zero) 36 | window.addSubview(sourceView) 37 | sourceView.addSubview(targetView) 38 | let layout = targetView.layoutBlock { (anchors) in 39 | anchors.width.equal(to: 200) 40 | anchors.height.equal(to: 40) 41 | anchors.centerX.align(by: sourceView.layoutAnchors.centerX) 42 | anchors.centerY.align(by: sourceView.layoutAnchors.centerY) 43 | } 44 | 45 | // print("Before: ", targetView.frame) 46 | layout.layout() 47 | // print("After: ", targetView.frame) 48 | 49 | XCTAssertTrue(targetView.frame.origin.x == ((500 - 200) - 200) / 2) 50 | XCTAssertTrue(targetView.frame.origin.y == ((500 - 200) - 40) / 2) 51 | XCTAssertTrue(targetView.frame.size.width == 200) 52 | XCTAssertTrue(targetView.frame.size.height == 40) 53 | } 54 | 55 | func testNewAnchors3() { 56 | let window = Window(frame: CGRect(x: 0, y: 0, width: 500, height: 500)) 57 | let sourceView = View(frame: CGRect(x: 100, y: 100, width: 300, height: 300)) 58 | let targetView = View(frame: .zero) 59 | window.addSubview(sourceView) 60 | sourceView.addSubview(targetView) 61 | let layout = targetView.layoutBlock { (anchors) in 62 | // anchors.centerY.align(by: sourceView.layoutAnchors.centerY) // 4 63 | anchors.height.scaled(by: sourceView.layoutAnchors.height, scale: 0.5) // 3 64 | anchors.left.pull(to: sourceView.layoutAnchors.centerX) // 1 65 | anchors.right.pull(to: sourceView.layoutAnchors.right) // 5, conflicted and broken down #2 66 | // anchors.width.equal(to: 300) // 2 67 | // anchors.top.limit(by: sourceView.layoutAnchors.centerY) // 6, conflicted and broken down #3, #4 68 | // anchors.top.align(by: sourceView.layoutAnchors.centerY) 69 | anchors.top.fartherThanOrEqual(to: sourceView.layoutAnchors.centerY) 70 | 71 | // print(anchors.constraints.reduce(CGRect.zero, { current, constraints in 72 | // print(current) 73 | // return current.constrainedBy(rect: sourceView.bounds, use: constraints) 74 | // })) 75 | } 76 | 77 | // print("Before: ", targetView.frame) 78 | layout.layout() 79 | // print("After: ", targetView.frame) 80 | 81 | XCTAssertTrue(targetView.frame.origin.x == 150) 82 | XCTAssertTrue(targetView.frame.origin.y == 150) 83 | XCTAssertTrue(targetView.frame.size.width == 150) 84 | XCTAssertTrue(targetView.frame.size.height == 150) 85 | } 86 | 87 | func testNewAnchors4() { 88 | let window = Window(frame: CGRect(x: 0, y: 0, width: 500, height: 500)) 89 | let sourceView = View(frame: CGRect(x: 100, y: 100, width: 300, height: 300)) 90 | let targetView = Label(frame: .zero) 91 | targetView.text = "Test label intrinsic size" 92 | window.addSubview(sourceView) 93 | sourceView.addSubview(targetView) 94 | 95 | let layout = targetView.layoutBlock { (anchors) in 96 | // anchors.centerY.align(by: sourceView.layoutAnchors.centerY) // 4 97 | anchors.height.equalIntrinsicSize() // 3 98 | anchors.left.pull(to: sourceView.layoutAnchors.centerX) // 1 99 | anchors.right.pull(to: sourceView.layoutAnchors.right) // 5, conflicted and broken down #2 100 | // anchors.width.equalIntrinsicSize() // 2 101 | // anchors.top.limit(by: sourceView.layoutAnchors.centerY) // 6, conflicted and broken down #3, #4 102 | anchors.top.align(by: sourceView.layoutAnchors.centerY) 103 | 104 | // print(anchors.constraints.reduce(CGRect.zero, { current, constraints in 105 | // print(current) 106 | // return current.constrainedBy(rect: sourceView.bounds, use: constraints) 107 | // })) 108 | } 109 | 110 | // print("Before: ", targetView.frame) 111 | layout.layout() 112 | // print("After: ", targetView.frame) 113 | 114 | XCTAssertTrue(targetView.frame.origin.x == 150) 115 | XCTAssertTrue(targetView.frame.origin.y == 150) 116 | XCTAssertTrue(targetView.frame.size.width == 150) 117 | let height = targetView.contentConstraint.constrained(sourceRect: .zero, by: CGRect(x: 0, y: 0, width: 150, height: 0)).size.height 118 | XCTAssertTrue(targetView.frame.size.height == height) 119 | } 120 | 121 | static var allTests = [ 122 | ("testTopAlignment", testTopAlignment), 123 | ("testContainer", testContainer), 124 | ("testNewAnchors2", testNewAnchors2), 125 | ("testNewAnchors3", testNewAnchors3), 126 | ("testNewAnchors4", testNewAnchors4) 127 | ] 128 | } 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /Tests/CGLayoutTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | #if !canImport(ObjectiveC) 2 | import XCTest 3 | 4 | extension Tests { 5 | // DO NOT MODIFY: This is autogenerated, use: 6 | // `swift test --generate-linuxmain` 7 | // to regenerate. 8 | static let __allTests__Tests = [ 9 | ("testAnchorBottomAlign", testAnchorBottomAlign), 10 | ("testAnchorBottomLimit", testAnchorBottomLimit), 11 | ("testAnchorBottomPull", testAnchorBottomPull), 12 | ("testAnchorLeftAlign", testAnchorLeftAlign), 13 | ("testAnchorLeftLimit", testAnchorLeftLimit), 14 | ("testAnchorLeftPull", testAnchorLeftPull), 15 | ("testAnchorRightAlign", testAnchorRightAlign), 16 | ("testAnchorRightLimit", testAnchorRightLimit), 17 | ("testAnchorRightPull", testAnchorRightPull), 18 | ("testAnchorTopAlign", testAnchorTopAlign), 19 | ("testAnchorTopLimit", testAnchorTopLimit), 20 | ("testAnchorTopPull", testAnchorTopPull), 21 | ("testApplyingSnapshotEqualLayoutDirectly", testApplyingSnapshotEqualLayoutDirectly), 22 | ("testBottomAlignment", testBottomAlignment), 23 | ("testBottomAlignmentWithMultiplier", testBottomAlignmentWithMultiplier), 24 | ("testBottomAlignmentWithOffset", testBottomAlignmentWithOffset), 25 | ("testBottomAlignmentWithSpace", testBottomAlignmentWithSpace), 26 | ("testCenterToCenterAnchor", testCenterToCenterAnchor), 27 | ("testCenterToOriginAnchor", testCenterToOriginAnchor), 28 | ("testCoordinateSpacePointLayoutGuide", testCoordinateSpacePointLayoutGuide), 29 | ("testCurrentSnapshotEqualLayoutDirectly", testCurrentSnapshotEqualLayoutDirectly), 30 | ("testFillingBetween", testFillingBetween), 31 | ("testFillingBoxed", testFillingBoxed), 32 | ("testFillingFixed", testFillingFixed), 33 | ("testFillingFrom", testFillingFrom), 34 | ("testFillingScaled", testFillingScaled), 35 | ("testFillingUpTo", testFillingUpTo), 36 | ("testHeightAnchor", testHeightAnchor), 37 | ("testInsetAnchor", testInsetAnchor), 38 | ("testLayout", testLayout), 39 | ("testLayoutDistribution", testLayoutDistribution), 40 | ("testLayoutDistributionFunc1Performance", testLayoutDistributionFunc1Performance), 41 | ("testLayoutDistributionFunc2Performance", testLayoutDistributionFunc2Performance), 42 | ("testLayoutWorkspaceAfterLeadingAlign", testLayoutWorkspaceAfterLeadingAlign), 43 | ("testLayoutWorkspaceAfterTrailingAlign", testLayoutWorkspaceAfterTrailingAlign), 44 | ("testLayoutWorkspaceBeforeLeadingAlign", testLayoutWorkspaceBeforeLeadingAlign), 45 | ("testLayoutWorkspaceBeforeTrailingAlign", testLayoutWorkspaceBeforeTrailingAlign), 46 | ("testLazyFilter", testLazyFilter), 47 | ("testLeftAlignment", testLeftAlignment), 48 | ("testLeftAlignmentWithMultiplier", testLeftAlignmentWithMultiplier), 49 | ("testLeftAlignmentWithOffset", testLeftAlignmentWithOffset), 50 | ("testLeftAlignmentWithSpace", testLeftAlignmentWithSpace), 51 | ("testPerformanceLayout", testPerformanceLayout), 52 | ("testRightAlignment", testRightAlignment), 53 | ("testRightAlignmentWithMultiplier", testRightAlignmentWithMultiplier), 54 | ("testRightAlignmentWithOffset", testRightAlignmentWithOffset), 55 | ("testRightAlignmentWithSpace", testRightAlignmentWithSpace), 56 | ("testSnapshotEqualLayoutDirectly", testSnapshotEqualLayoutDirectly), 57 | ("testStackLayoutScheme", testStackLayoutScheme), 58 | ("testTopAlignment", testTopAlignment), 59 | ("testTopAlignmentWithMultiplier", testTopAlignmentWithMultiplier), 60 | ("testTopAlignmentWithOffset", testTopAlignmentWithOffset), 61 | ("testTopAlignmentWithSpace", testTopAlignmentWithSpace), 62 | ("testWidthAnchor", testWidthAnchor), 63 | ] 64 | } 65 | 66 | public func __allTests() -> [XCTestCaseEntry] { 67 | return [ 68 | testCase(Tests.__allTests__Tests), 69 | ] 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import CGLayoutTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += CGLayoutTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 58% 23 | 24 | 25 | 58% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/css/jazzy.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { 2 | background: transparent; 3 | border: 0; 4 | margin: 0; 5 | outline: 0; 6 | padding: 0; 7 | vertical-align: baseline; } 8 | 9 | body { 10 | background-color: #f2f2f2; 11 | font-family: Helvetica, freesans, Arial, sans-serif; 12 | font-size: 14px; 13 | -webkit-font-smoothing: subpixel-antialiased; 14 | word-wrap: break-word; } 15 | 16 | h1, h2, h3 { 17 | margin-top: 0.8em; 18 | margin-bottom: 0.3em; 19 | font-weight: 100; 20 | color: black; } 21 | 22 | h1 { 23 | font-size: 2.5em; } 24 | 25 | h2 { 26 | font-size: 2em; 27 | border-bottom: 1px solid #e2e2e2; } 28 | 29 | h4 { 30 | font-size: 13px; 31 | line-height: 1.5; 32 | margin-top: 21px; } 33 | 34 | h5 { 35 | font-size: 1.1em; } 36 | 37 | h6 { 38 | font-size: 1.1em; 39 | color: #777; } 40 | 41 | .section-name { 42 | color: gray; 43 | display: block; 44 | font-family: Helvetica; 45 | font-size: 22px; 46 | font-weight: 100; 47 | margin-bottom: 15px; } 48 | 49 | pre, code { 50 | font: 0.95em Menlo, monospace; 51 | color: #777; 52 | word-wrap: normal; } 53 | 54 | p code, li code { 55 | background-color: #eee; 56 | padding: 2px 4px; 57 | border-radius: 4px; } 58 | 59 | a { 60 | color: #0088cc; 61 | text-decoration: none; } 62 | 63 | ul { 64 | padding-left: 15px; } 65 | 66 | li { 67 | line-height: 1.8em; } 68 | 69 | img { 70 | max-width: 100%; } 71 | 72 | blockquote { 73 | margin-left: 0; 74 | padding: 0 10px; 75 | border-left: 4px solid #ccc; } 76 | 77 | .content-wrapper { 78 | margin: 0 auto; 79 | width: 980px; } 80 | 81 | header { 82 | font-size: 0.85em; 83 | line-height: 26px; 84 | background-color: #414141; 85 | position: fixed; 86 | width: 100%; 87 | z-index: 2; } 88 | header img { 89 | padding-right: 6px; 90 | vertical-align: -4px; 91 | height: 16px; } 92 | header a { 93 | color: #fff; } 94 | header p { 95 | float: left; 96 | color: #999; } 97 | header .header-right { 98 | float: right; 99 | margin-left: 16px; } 100 | 101 | #breadcrumbs { 102 | background-color: #f2f2f2; 103 | height: 27px; 104 | padding-top: 17px; 105 | position: fixed; 106 | width: 100%; 107 | z-index: 2; 108 | margin-top: 26px; } 109 | #breadcrumbs #carat { 110 | height: 10px; 111 | margin: 0 5px; } 112 | 113 | .sidebar { 114 | background-color: #f9f9f9; 115 | border: 1px solid #e2e2e2; 116 | overflow-y: auto; 117 | overflow-x: hidden; 118 | position: fixed; 119 | top: 70px; 120 | bottom: 0; 121 | width: 230px; 122 | word-wrap: normal; } 123 | 124 | .nav-groups { 125 | list-style-type: none; 126 | background: #fff; 127 | padding-left: 0; } 128 | 129 | .nav-group-name { 130 | border-bottom: 1px solid #e2e2e2; 131 | font-size: 1.1em; 132 | font-weight: 100; 133 | padding: 15px 0 15px 20px; } 134 | .nav-group-name > a { 135 | color: #333; } 136 | 137 | .nav-group-tasks { 138 | margin-top: 5px; } 139 | 140 | .nav-group-task { 141 | font-size: 0.9em; 142 | list-style-type: none; 143 | white-space: nowrap; } 144 | .nav-group-task a { 145 | color: #888; } 146 | 147 | .main-content { 148 | background-color: #fff; 149 | border: 1px solid #e2e2e2; 150 | margin-left: 246px; 151 | position: absolute; 152 | overflow: hidden; 153 | padding-bottom: 20px; 154 | top: 70px; 155 | width: 734px; } 156 | .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { 157 | margin-bottom: 1em; } 158 | .main-content p { 159 | line-height: 1.8em; } 160 | .main-content section .section:first-child { 161 | margin-top: 0; 162 | padding-top: 0; } 163 | .main-content section .task-group-section .task-group:first-of-type { 164 | padding-top: 10px; } 165 | .main-content section .task-group-section .task-group:first-of-type .section-name { 166 | padding-top: 15px; } 167 | .main-content section .heading:before { 168 | content: ""; 169 | display: block; 170 | padding-top: 70px; 171 | margin: -70px 0 0; } 172 | .main-content .section-name p { 173 | margin-bottom: inherit; 174 | line-height: inherit; } 175 | .main-content .section-name code { 176 | background-color: inherit; 177 | padding: inherit; 178 | color: inherit; } 179 | 180 | .section { 181 | padding: 0 25px; } 182 | 183 | .highlight { 184 | background-color: #eee; 185 | padding: 10px 12px; 186 | border: 1px solid #e2e2e2; 187 | border-radius: 4px; 188 | overflow-x: auto; } 189 | 190 | .declaration .highlight { 191 | overflow-x: initial; 192 | padding: 0 40px 40px 0; 193 | margin-bottom: -25px; 194 | background-color: transparent; 195 | border: none; } 196 | 197 | .section-name { 198 | margin: 0; 199 | margin-left: 18px; } 200 | 201 | .task-group-section { 202 | padding-left: 6px; 203 | border-top: 1px solid #e2e2e2; } 204 | 205 | .task-group { 206 | padding-top: 0px; } 207 | 208 | .task-name-container a[name]:before { 209 | content: ""; 210 | display: block; 211 | padding-top: 70px; 212 | margin: -70px 0 0; } 213 | 214 | .section-name-container { 215 | position: relative; 216 | display: inline-block; } 217 | .section-name-container .section-name-link { 218 | position: absolute; 219 | top: 0; 220 | left: 0; 221 | bottom: 0; 222 | right: 0; 223 | margin-bottom: 0; } 224 | .section-name-container .section-name { 225 | position: relative; 226 | pointer-events: none; 227 | z-index: 1; } 228 | .section-name-container .section-name a { 229 | pointer-events: auto; } 230 | 231 | .item { 232 | padding-top: 8px; 233 | width: 100%; 234 | list-style-type: none; } 235 | .item a[name]:before { 236 | content: ""; 237 | display: block; 238 | padding-top: 70px; 239 | margin: -70px 0 0; } 240 | .item code { 241 | background-color: transparent; 242 | padding: 0; } 243 | .item .token, .item .direct-link { 244 | padding-left: 3px; 245 | margin-left: 15px; 246 | font-size: 11.9px; 247 | transition: all 300ms; } 248 | .item .token-open { 249 | margin-left: 0px; } 250 | .item .discouraged { 251 | text-decoration: line-through; } 252 | .item .declaration-note { 253 | font-size: .85em; 254 | color: gray; 255 | font-style: italic; } 256 | 257 | .pointer-container { 258 | border-bottom: 1px solid #e2e2e2; 259 | left: -23px; 260 | padding-bottom: 13px; 261 | position: relative; 262 | width: 110%; } 263 | 264 | .pointer { 265 | background: #f9f9f9; 266 | border-left: 1px solid #e2e2e2; 267 | border-top: 1px solid #e2e2e2; 268 | height: 12px; 269 | left: 21px; 270 | top: -7px; 271 | -webkit-transform: rotate(45deg); 272 | -moz-transform: rotate(45deg); 273 | -o-transform: rotate(45deg); 274 | transform: rotate(45deg); 275 | position: absolute; 276 | width: 12px; } 277 | 278 | .height-container { 279 | display: none; 280 | left: -25px; 281 | padding: 0 25px; 282 | position: relative; 283 | width: 100%; 284 | overflow: hidden; } 285 | .height-container .section { 286 | background: #f9f9f9; 287 | border-bottom: 1px solid #e2e2e2; 288 | left: -25px; 289 | position: relative; 290 | width: 100%; 291 | padding-top: 10px; 292 | padding-bottom: 5px; } 293 | 294 | .aside, .language { 295 | padding: 6px 12px; 296 | margin: 12px 0; 297 | border-left: 5px solid #dddddd; 298 | overflow-y: hidden; } 299 | .aside .aside-title, .language .aside-title { 300 | font-size: 9px; 301 | letter-spacing: 2px; 302 | text-transform: uppercase; 303 | padding-bottom: 0; 304 | margin: 0; 305 | color: #aaa; 306 | -webkit-user-select: none; } 307 | .aside p:last-child, .language p:last-child { 308 | margin-bottom: 0; } 309 | 310 | .language { 311 | border-left: 5px solid #cde9f4; } 312 | .language .aside-title { 313 | color: #4b8afb; } 314 | 315 | .aside-warning, .aside-deprecated, .aside-unavailable { 316 | border-left: 5px solid #ff6666; } 317 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { 318 | color: #ff0000; } 319 | 320 | .graybox { 321 | border-collapse: collapse; 322 | width: 100%; } 323 | .graybox p { 324 | margin: 0; 325 | word-break: break-word; 326 | min-width: 50px; } 327 | .graybox td { 328 | border: 1px solid #e2e2e2; 329 | padding: 5px 25px 5px 10px; 330 | vertical-align: middle; } 331 | .graybox tr td:first-of-type { 332 | text-align: right; 333 | padding: 7px; 334 | vertical-align: top; 335 | word-break: normal; 336 | width: 40px; } 337 | 338 | .slightly-smaller { 339 | font-size: 0.9em; } 340 | 341 | #footer { 342 | position: relative; 343 | top: 10px; 344 | bottom: 0px; 345 | margin-left: 25px; } 346 | #footer p { 347 | margin: 0; 348 | color: #aaa; 349 | font-size: 0.8em; } 350 | 351 | html.dash header, html.dash #breadcrumbs, html.dash .sidebar { 352 | display: none; } 353 | 354 | html.dash .main-content { 355 | width: 980px; 356 | margin-left: 0; 357 | border: none; 358 | width: 100%; 359 | top: 0; 360 | padding-bottom: 0; } 361 | 362 | html.dash .height-container { 363 | display: block; } 364 | 365 | html.dash .item .token { 366 | margin-left: 0; } 367 | 368 | html.dash .content-wrapper { 369 | width: auto; } 370 | 371 | html.dash #footer { 372 | position: static; } 373 | -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.cglayout 7 | CFBundleName 8 | CGLayout 9 | DocSetPlatformFamily 10 | cglayout 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 57% 23 | 24 | 25 | 57% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/css/jazzy.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { 2 | background: transparent; 3 | border: 0; 4 | margin: 0; 5 | outline: 0; 6 | padding: 0; 7 | vertical-align: baseline; } 8 | 9 | body { 10 | background-color: #f2f2f2; 11 | font-family: Helvetica, freesans, Arial, sans-serif; 12 | font-size: 14px; 13 | -webkit-font-smoothing: subpixel-antialiased; 14 | word-wrap: break-word; } 15 | 16 | h1, h2, h3 { 17 | margin-top: 0.8em; 18 | margin-bottom: 0.3em; 19 | font-weight: 100; 20 | color: black; } 21 | 22 | h1 { 23 | font-size: 2.5em; } 24 | 25 | h2 { 26 | font-size: 2em; 27 | border-bottom: 1px solid #e2e2e2; } 28 | 29 | h4 { 30 | font-size: 13px; 31 | line-height: 1.5; 32 | margin-top: 21px; } 33 | 34 | h5 { 35 | font-size: 1.1em; } 36 | 37 | h6 { 38 | font-size: 1.1em; 39 | color: #777; } 40 | 41 | .section-name { 42 | color: gray; 43 | display: block; 44 | font-family: Helvetica; 45 | font-size: 22px; 46 | font-weight: 100; 47 | margin-bottom: 15px; } 48 | 49 | pre, code { 50 | font: 0.95em Menlo, monospace; 51 | color: #777; 52 | word-wrap: normal; } 53 | 54 | p code, li code { 55 | background-color: #eee; 56 | padding: 2px 4px; 57 | border-radius: 4px; } 58 | 59 | a { 60 | color: #0088cc; 61 | text-decoration: none; } 62 | 63 | ul { 64 | padding-left: 15px; } 65 | 66 | li { 67 | line-height: 1.8em; } 68 | 69 | img { 70 | max-width: 100%; } 71 | 72 | blockquote { 73 | margin-left: 0; 74 | padding: 0 10px; 75 | border-left: 4px solid #ccc; } 76 | 77 | .content-wrapper { 78 | margin: 0 auto; 79 | width: 980px; } 80 | 81 | header { 82 | font-size: 0.85em; 83 | line-height: 26px; 84 | background-color: #414141; 85 | position: fixed; 86 | width: 100%; 87 | z-index: 2; } 88 | header img { 89 | padding-right: 6px; 90 | vertical-align: -4px; 91 | height: 16px; } 92 | header a { 93 | color: #fff; } 94 | header p { 95 | float: left; 96 | color: #999; } 97 | header .header-right { 98 | float: right; 99 | margin-left: 16px; } 100 | 101 | #breadcrumbs { 102 | background-color: #f2f2f2; 103 | height: 27px; 104 | padding-top: 17px; 105 | position: fixed; 106 | width: 100%; 107 | z-index: 2; 108 | margin-top: 26px; } 109 | #breadcrumbs #carat { 110 | height: 10px; 111 | margin: 0 5px; } 112 | 113 | .sidebar { 114 | background-color: #f9f9f9; 115 | border: 1px solid #e2e2e2; 116 | overflow-y: auto; 117 | overflow-x: hidden; 118 | position: fixed; 119 | top: 70px; 120 | bottom: 0; 121 | width: 230px; 122 | word-wrap: normal; } 123 | 124 | .nav-groups { 125 | list-style-type: none; 126 | background: #fff; 127 | padding-left: 0; } 128 | 129 | .nav-group-name { 130 | border-bottom: 1px solid #e2e2e2; 131 | font-size: 1.1em; 132 | font-weight: 100; 133 | padding: 15px 0 15px 20px; } 134 | .nav-group-name > a { 135 | color: #333; } 136 | 137 | .nav-group-tasks { 138 | margin-top: 5px; } 139 | 140 | .nav-group-task { 141 | font-size: 0.9em; 142 | list-style-type: none; 143 | white-space: nowrap; } 144 | .nav-group-task a { 145 | color: #888; } 146 | 147 | .main-content { 148 | background-color: #fff; 149 | border: 1px solid #e2e2e2; 150 | margin-left: 246px; 151 | position: absolute; 152 | overflow: hidden; 153 | padding-bottom: 20px; 154 | top: 70px; 155 | width: 734px; } 156 | .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { 157 | margin-bottom: 1em; } 158 | .main-content p { 159 | line-height: 1.8em; } 160 | .main-content section .section:first-child { 161 | margin-top: 0; 162 | padding-top: 0; } 163 | .main-content section .task-group-section .task-group:first-of-type { 164 | padding-top: 10px; } 165 | .main-content section .task-group-section .task-group:first-of-type .section-name { 166 | padding-top: 15px; } 167 | .main-content section .heading:before { 168 | content: ""; 169 | display: block; 170 | padding-top: 70px; 171 | margin: -70px 0 0; } 172 | .main-content .section-name p { 173 | margin-bottom: inherit; 174 | line-height: inherit; } 175 | .main-content .section-name code { 176 | background-color: inherit; 177 | padding: inherit; 178 | color: inherit; } 179 | 180 | .section { 181 | padding: 0 25px; } 182 | 183 | .highlight { 184 | background-color: #eee; 185 | padding: 10px 12px; 186 | border: 1px solid #e2e2e2; 187 | border-radius: 4px; 188 | overflow-x: auto; } 189 | 190 | .declaration .highlight { 191 | overflow-x: initial; 192 | padding: 0 40px 40px 0; 193 | margin-bottom: -25px; 194 | background-color: transparent; 195 | border: none; } 196 | 197 | .section-name { 198 | margin: 0; 199 | margin-left: 18px; } 200 | 201 | .task-group-section { 202 | padding-left: 6px; 203 | border-top: 1px solid #e2e2e2; } 204 | 205 | .task-group { 206 | padding-top: 0px; } 207 | 208 | .task-name-container a[name]:before { 209 | content: ""; 210 | display: block; 211 | padding-top: 70px; 212 | margin: -70px 0 0; } 213 | 214 | .section-name-container { 215 | position: relative; 216 | display: inline-block; } 217 | .section-name-container .section-name-link { 218 | position: absolute; 219 | top: 0; 220 | left: 0; 221 | bottom: 0; 222 | right: 0; 223 | margin-bottom: 0; } 224 | .section-name-container .section-name { 225 | position: relative; 226 | pointer-events: none; 227 | z-index: 1; } 228 | .section-name-container .section-name a { 229 | pointer-events: auto; } 230 | 231 | .item { 232 | padding-top: 8px; 233 | width: 100%; 234 | list-style-type: none; } 235 | .item a[name]:before { 236 | content: ""; 237 | display: block; 238 | padding-top: 70px; 239 | margin: -70px 0 0; } 240 | .item code { 241 | background-color: transparent; 242 | padding: 0; } 243 | .item .token, .item .direct-link { 244 | padding-left: 3px; 245 | margin-left: 15px; 246 | font-size: 11.9px; 247 | transition: all 300ms; } 248 | .item .token-open { 249 | margin-left: 0px; } 250 | .item .discouraged { 251 | text-decoration: line-through; } 252 | .item .declaration-note { 253 | font-size: .85em; 254 | color: gray; 255 | font-style: italic; } 256 | 257 | .pointer-container { 258 | border-bottom: 1px solid #e2e2e2; 259 | left: -23px; 260 | padding-bottom: 13px; 261 | position: relative; 262 | width: 110%; } 263 | 264 | .pointer { 265 | background: #f9f9f9; 266 | border-left: 1px solid #e2e2e2; 267 | border-top: 1px solid #e2e2e2; 268 | height: 12px; 269 | left: 21px; 270 | top: -7px; 271 | -webkit-transform: rotate(45deg); 272 | -moz-transform: rotate(45deg); 273 | -o-transform: rotate(45deg); 274 | transform: rotate(45deg); 275 | position: absolute; 276 | width: 12px; } 277 | 278 | .height-container { 279 | display: none; 280 | left: -25px; 281 | padding: 0 25px; 282 | position: relative; 283 | width: 100%; 284 | overflow: hidden; } 285 | .height-container .section { 286 | background: #f9f9f9; 287 | border-bottom: 1px solid #e2e2e2; 288 | left: -25px; 289 | position: relative; 290 | width: 100%; 291 | padding-top: 10px; 292 | padding-bottom: 5px; } 293 | 294 | .aside, .language { 295 | padding: 6px 12px; 296 | margin: 12px 0; 297 | border-left: 5px solid #dddddd; 298 | overflow-y: hidden; } 299 | .aside .aside-title, .language .aside-title { 300 | font-size: 9px; 301 | letter-spacing: 2px; 302 | text-transform: uppercase; 303 | padding-bottom: 0; 304 | margin: 0; 305 | color: #aaa; 306 | -webkit-user-select: none; } 307 | .aside p:last-child, .language p:last-child { 308 | margin-bottom: 0; } 309 | 310 | .language { 311 | border-left: 5px solid #cde9f4; } 312 | .language .aside-title { 313 | color: #4b8afb; } 314 | 315 | .aside-warning, .aside-deprecated, .aside-unavailable { 316 | border-left: 5px solid #ff6666; } 317 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { 318 | color: #ff0000; } 319 | 320 | .graybox { 321 | border-collapse: collapse; 322 | width: 100%; } 323 | .graybox p { 324 | margin: 0; 325 | word-break: break-word; 326 | min-width: 50px; } 327 | .graybox td { 328 | border: 1px solid #e2e2e2; 329 | padding: 5px 25px 5px 10px; 330 | vertical-align: middle; } 331 | .graybox tr td:first-of-type { 332 | text-align: right; 333 | padding: 7px; 334 | vertical-align: top; 335 | word-break: normal; 336 | width: 40px; } 337 | 338 | .slightly-smaller { 339 | font-size: 0.9em; } 340 | 341 | #footer { 342 | position: relative; 343 | top: 10px; 344 | bottom: 0px; 345 | margin-left: 25px; } 346 | #footer p { 347 | margin: 0; 348 | color: #aaa; 349 | font-size: 0.8em; } 350 | 351 | html.dash header, html.dash #breadcrumbs, html.dash .sidebar { 352 | display: none; } 353 | 354 | html.dash .main-content { 355 | width: 980px; 356 | margin-left: 0; 357 | border: none; 358 | width: 100%; 359 | top: 0; 360 | padding-bottom: 0; } 361 | 362 | html.dash .height-container { 363 | display: block; } 364 | 365 | html.dash .item .token { 366 | margin-left: 0; } 367 | 368 | html.dash .content-wrapper { 369 | width: auto; } 370 | 371 | html.dash #footer { 372 | position: static; } 373 | -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/docsets/CGLayout.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/docsets/CGLayout.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/docsets/CGLayout.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`.token[href="${location.hash}"]`); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /docs/docsets/CGLayout.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/docsets/CGLayout.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/CGLayout.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/docsets/CGLayout.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/CGLayout/62d3ab15e690bb56136e924a9be7115583f3a94b/docs/img/gh.png -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`.token[href="${location.hash}"]`); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /linux_example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /linux_example/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SDL", 6 | "repositoryURL": "https://github.com/PureSwift/SDL.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "d3c34516bdad3ab6902b46d00a33c2ac08f6b88e", 10 | "version": null 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /linux_example/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CGLSDL", 8 | platforms: [ 9 | .macOS(.v10_12) 10 | ], 11 | dependencies: [ 12 | // Dependencies declare other packages that this package depends on. 13 | .package(url: "https://github.com/PureSwift/SDL.git", .branch("master")), 14 | .package(path: "../") 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 18 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 19 | .target( 20 | name: "CGLSDL", 21 | dependencies: ["SDL", "CGLayout"]), 22 | .testTarget( 23 | name: "CGLSDLTests", 24 | dependencies: ["CGLSDL"]), 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /linux_example/README.md: -------------------------------------------------------------------------------- 1 | # CGLSDL 2 | 3 | Example based on SDL. 4 | 5 | ## Run 6 | 7 | ``` 8 | swift run 9 | ``` 10 | -------------------------------------------------------------------------------- /linux_example/Sources/CGLSDL/app.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CSDL2 3 | import SDL 4 | 5 | import CGLayout 6 | 7 | class Application { 8 | var window: Window? 9 | private(set) var isRunning: Bool = false 10 | private(set) var needsDisplay: Bool = true 11 | private var firstResponder: View? 12 | 13 | var container: ScrollLayoutGuide? 14 | 15 | init(arguments: Arguments) { 16 | 17 | } 18 | 19 | func boot(in systemWindow: SDLWindow) throws { 20 | let renderer = try SDLRenderer(window: systemWindow) 21 | let window = Window(renderer: renderer) 22 | window.backgroundColor = .white 23 | 24 | let redView = View() 25 | redView.backgroundColor = .red 26 | let greenView = View() 27 | greenView.backgroundColor = .green 28 | let blueView = View() 29 | blueView.backgroundColor = .blue 30 | let grayView = View() 31 | grayView.backgroundColor = Color(red: 230, green: 230, blue: 230, alpha: .max) 32 | 33 | let closeButton = Button({ [weak self] btn, point in 34 | self?.unboot() 35 | }) 36 | closeButton.backgroundColor = .black 37 | 38 | let textField = TextField() 39 | textField.backgroundColor = .blue 40 | 41 | let container = ScrollLayoutGuide(layout: LayoutScheme(blocks: [ 42 | grayView.layoutBlock(with: .equal), 43 | redView.layoutBlock(with: Layout(x: .left(), y: .top(), width: .fixed(200), height: .fixed(150))), 44 | blueView.layoutBlock(with: Layout(x: .center(), y: .center(), width: .fixed(200), height: .fixed(200))), 45 | greenView.layoutBlock(with: Layout(x: .left(), y: .bottom(), width: .fixed(150), height: .fixed(200))) 46 | ])) 47 | container.add(to: window) 48 | container.contentSize = CGSize(width: 800, height: 1500) 49 | 50 | window.addSubview(grayView) 51 | window.layoutScheme = LayoutScheme(blocks: [ 52 | closeButton.layoutBlock(with: Layout(x: .right(), width: .fixed(20), height: .fixed(20))), 53 | textField.layoutBlock(with: Layout(x: .center(), width: .scaled(0.3), height: .fixed(20))), 54 | container.layoutBlock(with: .equal) 55 | ]) 56 | window.addSubview(greenView) 57 | window.addSubview(blueView) 58 | window.addSubview(redView) 59 | window.addSubview(closeButton) 60 | window.addSubview(textField) 61 | 62 | self.window = window 63 | self.container = container 64 | self.isRunning = true 65 | } 66 | 67 | func unboot() { 68 | self.isRunning = false 69 | } 70 | 71 | func display(in rect: CGRect) throws { 72 | guard needsDisplay, let w = window else { return } 73 | 74 | w.frame = rect /// calls layoutSubviews 75 | try w.draw(in: rect) 76 | needsDisplay = false 77 | } 78 | 79 | func windowDidResize() { 80 | needsDisplay = true 81 | } 82 | 83 | func keyDidPress(_ event: SDL_KeyboardEvent) { 84 | let velocity: CGFloat = event.repeat > 0 ? 2 : 1 85 | let key = Int(event.keysym.sym) 86 | switch key { 87 | case SDLK_UP: 88 | container?.contentOffset.y += velocity 89 | case SDLK_DOWN: 90 | container?.contentOffset.y -= velocity 91 | case SDLK_RIGHT: 92 | container?.contentOffset.x -= velocity 93 | case SDLK_LEFT: 94 | container?.contentOffset.x += velocity 95 | default: 96 | if let fr = firstResponder as? TextInput, let scalar = Unicode.Scalar(key) { 97 | let char = String(scalar) 98 | print(char) 99 | fr.keyDidPressed(char) 100 | } 101 | } 102 | needsDisplay = true 103 | } 104 | 105 | var timer: Timer? 106 | func mouseWheel(_ event: SDL_MouseWheelEvent) { 107 | guard let c = container else { return } 108 | 109 | let x: CGFloat = CGFloat(event.x) 110 | let y: CGFloat = CGFloat(event.y) * (event.direction == SDL_MOUSEWHEEL_FLIPPED.rawValue ? -1 : 1) 111 | c.contentOffset.x += x 112 | c.contentOffset.y += y 113 | if let animation = c.decelerate(start: c.contentOffset, translation: nil, velocity: CGPoint(x: CGFloat(-event.x * 150), y: CGFloat(event.y * 250))) { 114 | timer?.invalidate() 115 | timer = Timer(timeInterval: 1/60, repeats: true, block: { [weak self] timer in 116 | if animation.step() { 117 | timer.invalidate() 118 | } 119 | self?.needsDisplay = true 120 | }) 121 | RunLoop.current.add(timer!, forMode: .default) 122 | } else { 123 | needsDisplay = true 124 | } 125 | } 126 | 127 | func mouseButton(_ event: SDL_MouseButtonEvent) { 128 | guard let w = window else { return } 129 | let point = CGPoint(x: CGFloat(event.x), y: CGFloat(event.y)) 130 | if let v = w.hitTest(point) { 131 | if v.isFirstResponder { 132 | firstResponder = v 133 | } 134 | } 135 | } 136 | } 137 | extension Application { 138 | struct Arguments { 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /linux_example/Sources/CGLSDL/main.swift: -------------------------------------------------------------------------------- 1 | import CSDL2 2 | import SDL 3 | import Foundation 4 | 5 | print("All Render Drivers:") 6 | let renderDrivers = SDLRenderer.Driver.all 7 | if renderDrivers.isEmpty == false { 8 | print("=======") 9 | for driver in renderDrivers { 10 | 11 | do { 12 | let info = try SDLRenderer.Info(driver: driver) 13 | print("Driver:", driver.rawValue) 14 | print("Name:", info.name) 15 | print("Options:") 16 | info.options.forEach { print(" \($0)") } 17 | print("Formats:") 18 | info.formats.forEach { print(" \($0)") } 19 | if info.maximumSize.width > 0 || info.maximumSize.height > 0 { 20 | print("Maximum Size:") 21 | print(" Width: \(info.maximumSize.width)") 22 | print(" Height: \(info.maximumSize.height)") 23 | } 24 | print("=======") 25 | } catch { 26 | print("Could not get information for driver \(driver.rawValue)") 27 | } 28 | } 29 | } 30 | 31 | 32 | func main() throws { 33 | try SDL.initialize(subSystems: [.video]) 34 | defer { SDL.quit() } 35 | 36 | let windowSize = (width: 600, height: 480) 37 | let window = try SDLWindow( 38 | title: "CGL+SDL", 39 | frame: (x: .centered, y: .centered, width: windowSize.width, height: windowSize.height), 40 | options: [.resizable, .shown] 41 | ) 42 | let application = Application(arguments: Application.Arguments()) 43 | 44 | let framesPerSecond = try window.displayMode().refreshRate 45 | print("Running at \(framesPerSecond) FPS") 46 | 47 | var event = SDL_Event() 48 | let frameInterval = 1000 / UInt32(framesPerSecond) 49 | var lastWheelEventTimestamp: UInt32 = 0 50 | 51 | try application.boot(in: window) 52 | 53 | while application.isRunning { 54 | #if os(Linux) 55 | RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.001)) 56 | #endif 57 | 58 | SDL_PollEvent(&event) 59 | 60 | let startTime = SDL_GetTicks() 61 | let eventType = SDL_EventType(rawValue: event.type) 62 | 63 | switch eventType { 64 | case SDL_QUIT, SDL_APP_TERMINATING: 65 | application.unboot() 66 | case SDL_KEYDOWN: 67 | application.keyDidPress(event.key) 68 | // print("key_down") 69 | case SDL_MOUSEWHEEL: 70 | guard event.wheel.timestamp != lastWheelEventTimestamp else { break } 71 | lastWheelEventTimestamp = event.wheel.timestamp 72 | application.mouseWheel(event.wheel) 73 | // print("mouse_wheel") 74 | case SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP: 75 | application.mouseButton(event.button) 76 | // print("mouse btn"/* , event.button */) 77 | case SDL_FINGERUP, SDL_FINGERDOWN, SDL_FINGERMOTION: 78 | print("finger"/*, event.tfinger*/) 79 | case SDL_WINDOWEVENT: 80 | switch SDL_WindowEventID(UInt32(event.window.event)) { 81 | case SDL_WINDOWEVENT_SHOWN: 82 | break; 83 | case SDL_WINDOWEVENT_HIDDEN: 84 | break; 85 | case SDL_WINDOWEVENT_EXPOSED: 86 | break; 87 | case SDL_WINDOWEVENT_MOVED: 88 | break; 89 | case SDL_WINDOWEVENT_RESIZED: 90 | application.windowDidResize() 91 | print("window_resized") 92 | case SDL_WINDOWEVENT_SIZE_CHANGED: 93 | application.windowDidResize() 94 | print("window_size_changed") 95 | case SDL_WINDOWEVENT_MINIMIZED: 96 | break; 97 | case SDL_WINDOWEVENT_MAXIMIZED: 98 | break; 99 | case SDL_WINDOWEVENT_RESTORED: 100 | break; 101 | case SDL_WINDOWEVENT_ENTER: 102 | break; 103 | case SDL_WINDOWEVENT_LEAVE: 104 | break; 105 | case SDL_WINDOWEVENT_FOCUS_GAINED: 106 | break; 107 | case SDL_WINDOWEVENT_FOCUS_LOST: 108 | break; 109 | case SDL_WINDOWEVENT_CLOSE: 110 | break; 111 | case SDL_WINDOWEVENT_TAKE_FOCUS: 112 | break; 113 | case SDL_WINDOWEVENT_HIT_TEST: 114 | print("clicked") 115 | break; 116 | default: 117 | break; 118 | } 119 | default: 120 | break 121 | } 122 | 123 | if application.needsDisplay { 124 | let size = window.size 125 | let rect = CGRect(x: 0, y: 0, width: CGFloat(size.width), height: CGFloat(size.height)) 126 | try application.display(in: rect) 127 | } 128 | 129 | // sleep to save energy 130 | let frameDuration = SDL_GetTicks() - startTime 131 | if frameDuration < frameInterval { 132 | SDL_Delay(frameInterval - frameDuration) 133 | } 134 | } 135 | } 136 | 137 | do { try main() } 138 | catch let error as SDLError { 139 | print("Error: \(error.debugDescription)") 140 | exit(EXIT_FAILURE) 141 | } 142 | catch { 143 | print("Error: \(error)") 144 | exit(EXIT_FAILURE) 145 | } -------------------------------------------------------------------------------- /linux_example/Sources/CGLSDL/view.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CGLayout 3 | import SDL 4 | import CSDL2 5 | 6 | struct Color { 7 | let red: UInt8 8 | let green: UInt8 9 | let blue: UInt8 10 | let alpha: UInt8 11 | } 12 | extension Color { 13 | static var white: Color { return Color(red: .max, green: .max, blue: .max, alpha: .max) } 14 | static var black: Color { return Color(red: 0, green: 0, blue: 0, alpha: .max) } 15 | static var red: Color { return Color(red: .max, green: 0, blue: 0, alpha: .max) } 16 | static var green: Color { return Color(red: 0, green: .max, blue: 0, alpha: .max) } 17 | static var blue: Color { return Color(red: 0, green: 0, blue: .max, alpha: .max) } 18 | 19 | func sdlColor(format: SDLPixelFormat) -> SDLColor { 20 | return SDLColor( 21 | format: format, 22 | red: red, green: green, blue: blue, alpha: alpha 23 | ) 24 | } 25 | } 26 | 27 | class View: LayoutElement { 28 | var frame: CGRect = .zero { 29 | didSet { bounds = CGRect(origin: .zero, size: frame.size) } 30 | } 31 | var bounds: CGRect = .zero { 32 | didSet(oldValue) { 33 | if oldValue != bounds { 34 | layoutSubviews() 35 | } 36 | } 37 | } 38 | weak var superElement: LayoutElement? 39 | var subviews: [View] = [] 40 | var isHidden: Bool = false 41 | var isFirstResponder: Bool { return false } 42 | var backgroundColor: Color? 43 | var layoutScheme: LayoutScheme? 44 | 45 | var _cachedTexture: SDLTexture? 46 | 47 | func draw(in rect: CGRect, use renderer: SDLRenderer) throws -> SDLTexture? { 48 | guard !isHidden else { return nil } 49 | guard let surfaceTexture = _cachedTexture else { 50 | let surface = try SDLSurface(rgb: (0, 0, 0, 0), size: (width: 1, height: 1), depth: 32) 51 | if let c = self.backgroundColor { 52 | let color = SDLColor( 53 | format: try SDLPixelFormat(format: .argb8888), 54 | red: c.red, green: c.green, blue: c.blue, alpha: c.alpha 55 | ) 56 | try surface.fill(color: color) 57 | } 58 | let surfaceTexture = try SDLTexture(renderer: renderer, surface: surface) 59 | try surfaceTexture.setBlendMode([.alpha]) 60 | 61 | self._cachedTexture = surfaceTexture 62 | 63 | return surfaceTexture 64 | } 65 | return surfaceTexture 66 | } 67 | 68 | func layoutSubviews() { 69 | layoutScheme?.layout(in: layoutBounds) 70 | } 71 | 72 | func addSubview(_ subview: View) { 73 | subviews.append(subview) 74 | subview.superElement = self 75 | } 76 | 77 | func _removeSubview(_ subview: View) { 78 | subview.superElement = nil 79 | subviews.removeAll(where: { $0 === subview }) 80 | } 81 | 82 | func hitTest(_ point: CGPoint) -> View? { 83 | guard bounds.contains(point) else { return nil } 84 | for view in subviews { 85 | if let v = view.hitTest(CGPoint(x: point.x - view.frame.minX, y: point.y - view.frame.minY)) { 86 | return v 87 | } 88 | } 89 | return interactionBegan(point) ? self : nil 90 | } 91 | 92 | func interactionBegan(_ point: CGPoint) -> Bool { 93 | return false 94 | } 95 | } 96 | extension View: ElementInLayoutTime { 97 | var inLayoutTime: ElementInLayoutTime { return self } 98 | var layoutBounds: CGRect { return bounds } 99 | var superLayoutBounds: CGRect { return superElement?.layoutBounds ?? .zero } 100 | 101 | func removeFromSuperElement() { 102 | (superElement as? View)?._removeSubview(self) 103 | } 104 | } 105 | 106 | class Button: View { 107 | let onInteraction: (Button, CGPoint) -> Void 108 | 109 | init(_ onInteraction: @escaping (Button, CGPoint) -> Void) { 110 | self.onInteraction = onInteraction 111 | } 112 | 113 | override func interactionBegan(_ point: CGPoint) -> Bool { 114 | onInteraction(self, point) 115 | return true 116 | } 117 | } 118 | 119 | protocol TextInput { 120 | func keyDidPressed(_ event: String) 121 | } 122 | 123 | class TextField: View, TextInput { 124 | private var symbols: [View] = [] 125 | private(set) var _isFirstResponder: Bool = false 126 | override var isFirstResponder: Bool { return _isFirstResponder } 127 | override var bounds: CGRect { 128 | didSet { 129 | if let c = cursor, oldValue.height != bounds.height { 130 | c.frame.size.height = bounds.height 131 | } 132 | } 133 | } 134 | private(set) var text: String = "" { 135 | didSet { _textDidChanged(with: text, in: (0.. SDLTexture? { 142 | try text.enumerated().forEach { i, char in 143 | let offset = CGFloat(i * 10) 144 | try renderer.fill( 145 | rect: SDL_Rect( 146 | x: Int32(frame.minX + offset), y: Int32(frame.minY), 147 | w: 10, h: Int32(bounds.height) 148 | ) 149 | ) 150 | } 151 | if let c = cursor { 152 | try renderer.setDrawColor(red: 0x00, green: 0x00, blue: 0x00, alpha: 0xFF) 153 | try renderer.fill( 154 | rect: SDL_Rect( 155 | x: Int32(c.frame.minX + frame.minX + CGFloat(text.count * 10)), y: Int32(c.frame.minY + frame.minY), 156 | w: Int32(1), h: Int32(bounds.height) 157 | ) 158 | ) 159 | } 160 | return nil//try super.draw(in: rect, use: renderer) 161 | } 162 | 163 | override func interactionBegan(_ point: CGPoint) -> Bool { 164 | guard isFirstResponder else { 165 | becomeFirstResponder() 166 | return true 167 | } 168 | if let c = cursor { 169 | cursorPosition = min(Int(point.x / 10), text.count) 170 | c.frame.origin.x = CGFloat(cursorPosition * 10) 171 | } 172 | return true 173 | } 174 | 175 | func becomeFirstResponder() { 176 | guard !_isFirstResponder else { return } 177 | _isFirstResponder = true 178 | 179 | let cursor = View() 180 | addSubview(cursor) 181 | self.cursor = cursor 182 | } 183 | 184 | func resignFirstResponder() { 185 | _isFirstResponder = false 186 | cursor?.removeFromSuperElement() 187 | } 188 | 189 | func keyDidPressed(_ event: String) { 190 | let index = text.index(text.startIndex, offsetBy: cursorPosition) 191 | text.insert(contentsOf: event, at: index) 192 | _textDidChanged(with: event, in: (cursorPosition..) { 197 | 198 | } 199 | } 200 | 201 | class Window: View { 202 | override weak var superElement: LayoutElement? { 203 | set {} 204 | get { return nil } 205 | } 206 | let renderer: SDLRenderer 207 | 208 | init(renderer: SDLRenderer) { 209 | self.renderer = renderer 210 | } 211 | 212 | func draw(in rect: CGRect) throws { 213 | _ = try draw(in: rect, use: renderer) 214 | } 215 | 216 | override func draw(in rect: CGRect, use renderer: SDLRenderer) throws -> SDLTexture? { 217 | try renderer.setDrawColor(red: 0xFF, green: 0xFF, blue: 0xFF, alpha: 0xFF) 218 | try renderer.clear() 219 | try subviews.forEach({ view in 220 | if let texture = try view.draw(in: frame, use: renderer) { 221 | try renderer.copy( 222 | texture, 223 | destination: SDL_Rect( 224 | x: Int32(view.frame.minX), y: Int32(view.frame.minY), 225 | w: Int32(view.frame.width), h: Int32(view.frame.height) 226 | ) 227 | ) 228 | } 229 | }) 230 | 231 | renderer.present() 232 | return nil 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /linux_example/Tests/CGLSDLTests/CGLSDLTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class CGLSDLTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("CGLSDL") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /linux_example/Tests/CGLSDLTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(CGLSDLTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /linux_example/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import CGLSDLTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += CGLSDLTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------