├── .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 | [](http://cocoapods.org/pods/CGLayout)
6 | [](http://cocoapods.org/pods/CGLayout)
7 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------