├── .gitignore ├── .ruby-version ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── LayoutFrameworkBenchmark.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── dionlu.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcshareddata │ └── xcschemes │ └── LayoutFrameworkBenchmark.xcscheme ├── LayoutFrameworkBenchmark.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LayoutFrameworkBenchmark ├── AppDelegate.swift ├── Assets.xcassets │ ├── 20x20.imageset │ │ ├── 20x20.png │ │ └── Contents.json │ ├── 350x200.imageset │ │ ├── 350x200.png │ │ └── Contents.json │ ├── 50x50.imageset │ │ ├── 50x50.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024.png │ │ ├── Icon-167.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ └── Icon-76@2x.png │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Benchmarks │ ├── AutoLayout │ │ └── FeedItemAutoLayoutView.swift │ ├── BenchmarkViewController.swift │ ├── CollectionViewController.swift │ ├── DataBinder.swift │ ├── FeedItemData.swift │ ├── FlexLayout │ │ └── FeedItemFlexLayoutView.swift │ ├── LayoutKit │ │ ├── FeedItemLayoutKitView.swift │ │ └── Layouts │ │ │ ├── CircleImagePileLayout.swift │ │ │ ├── DividerStackLayout.swift │ │ │ ├── FeedItemLayout.swift │ │ │ ├── FixedWidthCellCollectionViewLayout.swift │ │ │ ├── HelloWorldAutoLayoutView.swift │ │ │ ├── HelloWorldLayout.swift │ │ │ ├── Info.plist │ │ │ ├── MiniProfileLayout.swift │ │ │ ├── ProfileCardLayout.swift │ │ │ └── SkillsCardLayout.swift │ ├── ManualLayout │ │ └── FeedItemManualView.swift │ ├── NKFrameLayoutKit │ │ └── NKFrameLayoutKitView.swift │ ├── PinLayout │ │ └── FeedItemPinLayoutView.swift │ ├── Stopwatch.swift │ └── UIStackView │ │ └── FeedItemUIStackView.swift ├── FeedItemNotAutoLayoutView.swift ├── FeedItemTextureNode.swift ├── Info.plist └── TextureCollectionViewController.swift ├── Podfile ├── Podfile.lock ├── README.md ├── Tests ├── CellLayoutSpec.swift └── Info.plist └── docs_markdown ├── benchmark_comparison.png ├── benchmark_comparison_all.png ├── benchmark_comparison_all_small.png ├── benchmark_iphone5s.png ├── benchmark_iphone6.png ├── benchmark_iphone6s.png ├── benchmark_iphone7.png ├── benchmark_iphonex.png ├── benchmark_iphonexs.png ├── benchmark_result_LayoutKit.png ├── benchmark_result_ManualLayout.png ├── benchmark_result_NKFrameLayoutKit.png ├── benchmark_result_PinLayout.png ├── benchmark_result_Texture.png └── images └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Directory 2 | build/ 3 | DerivedData/ 4 | build.log 5 | 6 | # User Data 7 | *.xctimeline 8 | xcuserdata 9 | 10 | # mkdocs 11 | site/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | Pods/ 17 | data/ -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.7.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode11.6 3 | 4 | cache: 5 | - bundler 6 | - cocoapods 7 | 8 | before_install: 9 | - bundle install # --deployment # to cache vendor/bundle 10 | - bundle exec pod install --repo-update 11 | 12 | script: 13 | - set -o pipefail && xcodebuild -workspace LayoutFrameworkBenchmark.xcworkspace -scheme LayoutFrameworkBenchmark -sdk iphonesimulator13.6 -destination 'platform=iOS Simulator,name=iPhone 8' build | xcpretty 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'synx' 3 | gem 'cocoapods' 4 | gem 'xcpretty-travis-formatter' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.3) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.3) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | clamp (0.6.5) 16 | cocoapods (1.9.3) 17 | activesupport (>= 4.0.2, < 5) 18 | claide (>= 1.0.2, < 2.0) 19 | cocoapods-core (= 1.9.3) 20 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 21 | cocoapods-downloader (>= 1.2.2, < 2.0) 22 | cocoapods-plugins (>= 1.0.0, < 2.0) 23 | cocoapods-search (>= 1.0.0, < 2.0) 24 | cocoapods-stats (>= 1.0.0, < 2.0) 25 | cocoapods-trunk (>= 1.4.0, < 2.0) 26 | cocoapods-try (>= 1.1.0, < 2.0) 27 | colored2 (~> 3.1) 28 | escape (~> 0.0.4) 29 | fourflusher (>= 2.3.0, < 3.0) 30 | gh_inspector (~> 1.0) 31 | molinillo (~> 0.6.6) 32 | nap (~> 1.0) 33 | ruby-macho (~> 1.4) 34 | xcodeproj (>= 1.14.0, < 2.0) 35 | cocoapods-core (1.9.3) 36 | activesupport (>= 4.0.2, < 6) 37 | algoliasearch (~> 1.0) 38 | concurrent-ruby (~> 1.1) 39 | fuzzy_match (~> 2.0.4) 40 | nap (~> 1.0) 41 | netrc (~> 0.11) 42 | typhoeus (~> 1.0) 43 | cocoapods-deintegrate (1.0.4) 44 | cocoapods-downloader (1.4.0) 45 | cocoapods-plugins (1.0.0) 46 | nap 47 | cocoapods-search (1.0.0) 48 | cocoapods-stats (1.1.0) 49 | cocoapods-trunk (1.5.0) 50 | nap (>= 0.8, < 2.0) 51 | netrc (~> 0.11) 52 | cocoapods-try (1.2.0) 53 | colored2 (3.1.2) 54 | colorize (0.8.1) 55 | concurrent-ruby (1.1.7) 56 | escape (0.0.4) 57 | ethon (0.12.0) 58 | ffi (>= 1.3.0) 59 | ffi (1.13.1) 60 | fourflusher (2.3.1) 61 | fuzzy_match (2.0.4) 62 | gh_inspector (1.1.3) 63 | httpclient (2.8.3) 64 | i18n (0.9.5) 65 | concurrent-ruby (~> 1.0) 66 | json (2.3.1) 67 | minitest (5.14.1) 68 | molinillo (0.6.6) 69 | nanaimo (0.3.0) 70 | nap (1.1.0) 71 | netrc (0.11.0) 72 | rouge (2.0.7) 73 | ruby-macho (1.4.0) 74 | synx (0.2.1) 75 | clamp (~> 0.6) 76 | colorize (~> 0.7) 77 | xcodeproj (~> 1.0) 78 | thread_safe (0.3.6) 79 | typhoeus (1.4.0) 80 | ethon (>= 0.9.0) 81 | tzinfo (1.2.10) 82 | thread_safe (~> 0.1) 83 | xcodeproj (1.18.0) 84 | CFPropertyList (>= 2.3.3, < 4.0) 85 | atomos (~> 0.1.3) 86 | claide (>= 1.0.2, < 2.0) 87 | colored2 (~> 3.1) 88 | nanaimo (~> 0.3.0) 89 | xcpretty (0.3.0) 90 | rouge (~> 2.0.7) 91 | xcpretty-travis-formatter (1.0.0) 92 | xcpretty (~> 0.2, >= 0.0.7) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | cocoapods 99 | synx 100 | xcpretty-travis-formatter 101 | 102 | BUNDLED WITH 103 | 2.1.2 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Luc Dion 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1EA042AE86CAC947B8F37D51 /* Pods_LayoutFrameworkBenchmark.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F34489CA12B845548D7F17E /* Pods_LayoutFrameworkBenchmark.framework */; }; 11 | 2401BC811F4F018C00788998 /* BenchmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC751F4F018C00788998 /* BenchmarkViewController.swift */; }; 12 | 2401BC821F4F018C00788998 /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC761F4F018C00788998 /* CollectionViewController.swift */; }; 13 | 2401BC831F4F018C00788998 /* DataBinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC771F4F018C00788998 /* DataBinder.swift */; }; 14 | 2401BC851F4F018C00788998 /* FeedItemData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC791F4F018C00788998 /* FeedItemData.swift */; }; 15 | 2401BC8B1F4F018C00788998 /* Stopwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC7F1F4F018C00788998 /* Stopwatch.swift */; }; 16 | 2401BC8F1F4F01D600788998 /* FeedItemLayoutKitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC8E1F4F01D600788998 /* FeedItemLayoutKitView.swift */; }; 17 | 2401BC941F4F021F00788998 /* FeedItemFlexLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC931F4F021F00788998 /* FeedItemFlexLayoutView.swift */; }; 18 | 2401BC961F4F022900788998 /* FeedItemPinLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC951F4F022900788998 /* FeedItemPinLayoutView.swift */; }; 19 | 2401BC991F4F029900788998 /* FeedItemLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC981F4F029900788998 /* FeedItemLayout.swift */; }; 20 | 2401BC9B1F4F03B300788998 /* ProfileCardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC9A1F4F03B300788998 /* ProfileCardLayout.swift */; }; 21 | 2401BC9E1F4F042700788998 /* FeedItemManualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BC9D1F4F042700788998 /* FeedItemManualView.swift */; }; 22 | 2401BCA11F4F044000788998 /* FeedItemUIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BCA01F4F044000788998 /* FeedItemUIStackView.swift */; }; 23 | 2401BCA41F4F045F00788998 /* FeedItemAutoLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2401BCA31F4F045F00788998 /* FeedItemAutoLayoutView.swift */; }; 24 | 24661D001F4EFFF5002CB883 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24661CFF1F4EFFF5002CB883 /* AppDelegate.swift */; }; 25 | 24661D071F4EFFF5002CB883 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 24661D061F4EFFF5002CB883 /* Assets.xcassets */; }; 26 | 24661D0A1F4EFFF5002CB883 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 24661D081F4EFFF5002CB883 /* LaunchScreen.storyboard */; }; 27 | 639E5A2020DF62D700C6BCEA /* NKFrameLayoutKitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639E5A1F20DF62D700C6BCEA /* NKFrameLayoutKitView.swift */; }; 28 | BF3DC69820B560A400536177 /* FeedItemNotAutoLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3DC69720B560A400536177 /* FeedItemNotAutoLayoutView.swift */; }; 29 | E4A8A14124FE99B1009D872B /* FeedItemTextureNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A8A14024FE982E009D872B /* FeedItemTextureNode.swift */; }; 30 | E4A8A24F24FEDD14009D872B /* TextureCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A8A24E24FEDD14009D872B /* TextureCollectionViewController.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 1A3C4D1FF88A9395CE622CF2 /* Pods-LayoutFrameworkBenchmark.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LayoutFrameworkBenchmark.release.xcconfig"; path = "Pods/Target Support Files/Pods-LayoutFrameworkBenchmark/Pods-LayoutFrameworkBenchmark.release.xcconfig"; sourceTree = ""; }; 35 | 2401BC751F4F018C00788998 /* BenchmarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BenchmarkViewController.swift; path = Benchmarks/BenchmarkViewController.swift; sourceTree = ""; }; 36 | 2401BC761F4F018C00788998 /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CollectionViewController.swift; path = Benchmarks/CollectionViewController.swift; sourceTree = ""; }; 37 | 2401BC771F4F018C00788998 /* DataBinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DataBinder.swift; path = Benchmarks/DataBinder.swift; sourceTree = ""; }; 38 | 2401BC791F4F018C00788998 /* FeedItemData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemData.swift; path = Benchmarks/FeedItemData.swift; sourceTree = ""; }; 39 | 2401BC7F1F4F018C00788998 /* Stopwatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Stopwatch.swift; path = Benchmarks/Stopwatch.swift; sourceTree = ""; }; 40 | 2401BC8E1F4F01D600788998 /* FeedItemLayoutKitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemLayoutKitView.swift; path = Benchmarks/LayoutKit/FeedItemLayoutKitView.swift; sourceTree = ""; }; 41 | 2401BC931F4F021F00788998 /* FeedItemFlexLayoutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemFlexLayoutView.swift; path = Benchmarks/FlexLayout/FeedItemFlexLayoutView.swift; sourceTree = ""; }; 42 | 2401BC951F4F022900788998 /* FeedItemPinLayoutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemPinLayoutView.swift; path = Benchmarks/PinLayout/FeedItemPinLayoutView.swift; sourceTree = ""; }; 43 | 2401BC981F4F029900788998 /* FeedItemLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemLayout.swift; path = Benchmarks/LayoutKit/Layouts/FeedItemLayout.swift; sourceTree = ""; }; 44 | 2401BC9A1F4F03B300788998 /* ProfileCardLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfileCardLayout.swift; path = Benchmarks/LayoutKit/Layouts/ProfileCardLayout.swift; sourceTree = ""; }; 45 | 2401BC9D1F4F042700788998 /* FeedItemManualView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemManualView.swift; path = Benchmarks/ManualLayout/FeedItemManualView.swift; sourceTree = ""; }; 46 | 2401BCA01F4F044000788998 /* FeedItemUIStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemUIStackView.swift; path = Benchmarks/UIStackView/FeedItemUIStackView.swift; sourceTree = ""; }; 47 | 2401BCA31F4F045F00788998 /* FeedItemAutoLayoutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedItemAutoLayoutView.swift; path = Benchmarks/AutoLayout/FeedItemAutoLayoutView.swift; sourceTree = ""; }; 48 | 24661CFC1F4EFFF5002CB883 /* LayoutFrameworkBenchmark.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LayoutFrameworkBenchmark.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 24661CFF1F4EFFF5002CB883 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 24661D061F4EFFF5002CB883 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 24661D091F4EFFF5002CB883 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | 24661D0B1F4EFFF5002CB883 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 4F34489CA12B845548D7F17E /* Pods_LayoutFrameworkBenchmark.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LayoutFrameworkBenchmark.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 639E5A1F20DF62D700C6BCEA /* NKFrameLayoutKitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NKFrameLayoutKitView.swift; sourceTree = ""; }; 55 | 73BD901DE3512A23A7603899 /* Pods-LayoutFrameworkBenchmark.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LayoutFrameworkBenchmark.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LayoutFrameworkBenchmark/Pods-LayoutFrameworkBenchmark.debug.xcconfig"; sourceTree = ""; }; 56 | BF3DC69720B560A400536177 /* FeedItemNotAutoLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemNotAutoLayoutView.swift; sourceTree = ""; }; 57 | E4A8A14024FE982E009D872B /* FeedItemTextureNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemTextureNode.swift; sourceTree = ""; }; 58 | E4A8A24E24FEDD14009D872B /* TextureCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextureCollectionViewController.swift; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 24661CF91F4EFFF5002CB883 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 1EA042AE86CAC947B8F37D51 /* Pods_LayoutFrameworkBenchmark.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 2401BC741F4F017D00788998 /* Benchmarks */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | E4A8A13F24FE981F009D872B /* Texture */, 77 | 2401BCA21F4F045600788998 /* AutoLayout */, 78 | 2401BC921F4F020D00788998 /* FlexLayout */, 79 | 2401BC8D1F4F01CC00788998 /* LayoutKit */, 80 | 2401BC9C1F4F041400788998 /* ManualLayout */, 81 | BF3DC69620B5608000536177 /* NotAutoLayout */, 82 | 2401BC911F4F020600788998 /* PinLayout */, 83 | 2401BC9F1F4F043800788998 /* UIStackView */, 84 | 639E5A1E20DF620D00C6BCEA /* NKFrameLayoutKit */, 85 | 2401BC751F4F018C00788998 /* BenchmarkViewController.swift */, 86 | 2401BC761F4F018C00788998 /* CollectionViewController.swift */, 87 | 2401BC771F4F018C00788998 /* DataBinder.swift */, 88 | 2401BC791F4F018C00788998 /* FeedItemData.swift */, 89 | 2401BC7F1F4F018C00788998 /* Stopwatch.swift */, 90 | ); 91 | name = Benchmarks; 92 | sourceTree = ""; 93 | }; 94 | 2401BC8D1F4F01CC00788998 /* LayoutKit */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 2401BC901F4F01DB00788998 /* Layouts */, 98 | 2401BC8E1F4F01D600788998 /* FeedItemLayoutKitView.swift */, 99 | 2401BC9A1F4F03B300788998 /* ProfileCardLayout.swift */, 100 | ); 101 | name = LayoutKit; 102 | sourceTree = ""; 103 | }; 104 | 2401BC901F4F01DB00788998 /* Layouts */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 2401BC981F4F029900788998 /* FeedItemLayout.swift */, 108 | ); 109 | name = Layouts; 110 | sourceTree = ""; 111 | }; 112 | 2401BC911F4F020600788998 /* PinLayout */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 2401BC951F4F022900788998 /* FeedItemPinLayoutView.swift */, 116 | ); 117 | name = PinLayout; 118 | sourceTree = ""; 119 | }; 120 | 2401BC921F4F020D00788998 /* FlexLayout */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 2401BC931F4F021F00788998 /* FeedItemFlexLayoutView.swift */, 124 | ); 125 | name = FlexLayout; 126 | sourceTree = ""; 127 | }; 128 | 2401BC9C1F4F041400788998 /* ManualLayout */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 2401BC9D1F4F042700788998 /* FeedItemManualView.swift */, 132 | ); 133 | name = ManualLayout; 134 | sourceTree = ""; 135 | }; 136 | 2401BC9F1F4F043800788998 /* UIStackView */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 2401BCA01F4F044000788998 /* FeedItemUIStackView.swift */, 140 | ); 141 | name = UIStackView; 142 | sourceTree = ""; 143 | }; 144 | 2401BCA21F4F045600788998 /* AutoLayout */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 2401BCA31F4F045F00788998 /* FeedItemAutoLayoutView.swift */, 148 | ); 149 | name = AutoLayout; 150 | sourceTree = ""; 151 | }; 152 | 24661CF31F4EFFF5002CB883 = { 153 | isa = PBXGroup; 154 | children = ( 155 | 24661CFE1F4EFFF5002CB883 /* LayoutFrameworkBenchmark */, 156 | 24661CFD1F4EFFF5002CB883 /* Products */, 157 | 768A1E2833724B1530888DF2 /* Pods */, 158 | 8056A3D1484FA641948E8180 /* Frameworks */, 159 | ); 160 | sourceTree = ""; 161 | }; 162 | 24661CFD1F4EFFF5002CB883 /* Products */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 24661CFC1F4EFFF5002CB883 /* LayoutFrameworkBenchmark.app */, 166 | ); 167 | name = Products; 168 | sourceTree = ""; 169 | }; 170 | 24661CFE1F4EFFF5002CB883 /* LayoutFrameworkBenchmark */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 2401BC741F4F017D00788998 /* Benchmarks */, 174 | 24661CFF1F4EFFF5002CB883 /* AppDelegate.swift */, 175 | 24661D061F4EFFF5002CB883 /* Assets.xcassets */, 176 | 24661D081F4EFFF5002CB883 /* LaunchScreen.storyboard */, 177 | 24661D0B1F4EFFF5002CB883 /* Info.plist */, 178 | ); 179 | path = LayoutFrameworkBenchmark; 180 | sourceTree = ""; 181 | }; 182 | 639E5A1E20DF620D00C6BCEA /* NKFrameLayoutKit */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 639E5A1F20DF62D700C6BCEA /* NKFrameLayoutKitView.swift */, 186 | ); 187 | name = NKFrameLayoutKit; 188 | path = Benchmarks/NKFrameLayoutKit; 189 | sourceTree = ""; 190 | }; 191 | 768A1E2833724B1530888DF2 /* Pods */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 73BD901DE3512A23A7603899 /* Pods-LayoutFrameworkBenchmark.debug.xcconfig */, 195 | 1A3C4D1FF88A9395CE622CF2 /* Pods-LayoutFrameworkBenchmark.release.xcconfig */, 196 | ); 197 | name = Pods; 198 | sourceTree = ""; 199 | }; 200 | 8056A3D1484FA641948E8180 /* Frameworks */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 4F34489CA12B845548D7F17E /* Pods_LayoutFrameworkBenchmark.framework */, 204 | ); 205 | name = Frameworks; 206 | sourceTree = ""; 207 | }; 208 | BF3DC69620B5608000536177 /* NotAutoLayout */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | BF3DC69720B560A400536177 /* FeedItemNotAutoLayoutView.swift */, 212 | ); 213 | name = NotAutoLayout; 214 | sourceTree = ""; 215 | }; 216 | E4A8A13F24FE981F009D872B /* Texture */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | E4A8A14024FE982E009D872B /* FeedItemTextureNode.swift */, 220 | E4A8A24E24FEDD14009D872B /* TextureCollectionViewController.swift */, 221 | ); 222 | name = Texture; 223 | sourceTree = ""; 224 | }; 225 | /* End PBXGroup section */ 226 | 227 | /* Begin PBXNativeTarget section */ 228 | 24661CFB1F4EFFF5002CB883 /* LayoutFrameworkBenchmark */ = { 229 | isa = PBXNativeTarget; 230 | buildConfigurationList = 24661D0E1F4EFFF5002CB883 /* Build configuration list for PBXNativeTarget "LayoutFrameworkBenchmark" */; 231 | buildPhases = ( 232 | FB2DF4C2068AD4C560959816 /* [CP] Check Pods Manifest.lock */, 233 | 24661CF81F4EFFF5002CB883 /* Sources */, 234 | 24661CF91F4EFFF5002CB883 /* Frameworks */, 235 | 24661CFA1F4EFFF5002CB883 /* Resources */, 236 | 84A6AA5174E802B10D43854E /* [CP] Embed Pods Frameworks */, 237 | ); 238 | buildRules = ( 239 | ); 240 | dependencies = ( 241 | ); 242 | name = LayoutFrameworkBenchmark; 243 | productName = LayoutFrameworkBenchmark; 244 | productReference = 24661CFC1F4EFFF5002CB883 /* LayoutFrameworkBenchmark.app */; 245 | productType = "com.apple.product-type.application"; 246 | }; 247 | /* End PBXNativeTarget section */ 248 | 249 | /* Begin PBXProject section */ 250 | 24661CF41F4EFFF5002CB883 /* Project object */ = { 251 | isa = PBXProject; 252 | attributes = { 253 | LastSwiftUpdateCheck = 0830; 254 | LastUpgradeCheck = 0940; 255 | TargetAttributes = { 256 | 24661CFB1F4EFFF5002CB883 = { 257 | CreatedOnToolsVersion = 8.3.2; 258 | LastSwiftMigration = 1010; 259 | ProvisioningStyle = Automatic; 260 | }; 261 | }; 262 | }; 263 | buildConfigurationList = 24661CF71F4EFFF5002CB883 /* Build configuration list for PBXProject "LayoutFrameworkBenchmark" */; 264 | compatibilityVersion = "Xcode 3.2"; 265 | developmentRegion = English; 266 | hasScannedForEncodings = 0; 267 | knownRegions = ( 268 | English, 269 | en, 270 | Base, 271 | ); 272 | mainGroup = 24661CF31F4EFFF5002CB883; 273 | productRefGroup = 24661CFD1F4EFFF5002CB883 /* Products */; 274 | projectDirPath = ""; 275 | projectRoot = ""; 276 | targets = ( 277 | 24661CFB1F4EFFF5002CB883 /* LayoutFrameworkBenchmark */, 278 | ); 279 | }; 280 | /* End PBXProject section */ 281 | 282 | /* Begin PBXResourcesBuildPhase section */ 283 | 24661CFA1F4EFFF5002CB883 /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 24661D0A1F4EFFF5002CB883 /* LaunchScreen.storyboard in Resources */, 288 | 24661D071F4EFFF5002CB883 /* Assets.xcassets in Resources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXResourcesBuildPhase section */ 293 | 294 | /* Begin PBXShellScriptBuildPhase section */ 295 | 84A6AA5174E802B10D43854E /* [CP] Embed Pods Frameworks */ = { 296 | isa = PBXShellScriptBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | inputPaths = ( 301 | "${PODS_ROOT}/Target Support Files/Pods-LayoutFrameworkBenchmark/Pods-LayoutFrameworkBenchmark-frameworks.sh", 302 | "${BUILT_PRODUCTS_DIR}/FlexLayout/FlexLayout.framework", 303 | "${BUILT_PRODUCTS_DIR}/LayoutKit/LayoutKit.framework", 304 | "${BUILT_PRODUCTS_DIR}/NKFrameLayoutKit/NKFrameLayoutKit.framework", 305 | "${BUILT_PRODUCTS_DIR}/NotAutoLayout/NotAutoLayout.framework", 306 | "${BUILT_PRODUCTS_DIR}/PINCache/PINCache.framework", 307 | "${BUILT_PRODUCTS_DIR}/PINOperation/PINOperation.framework", 308 | "${BUILT_PRODUCTS_DIR}/PINRemoteImage/PINRemoteImage.framework", 309 | "${BUILT_PRODUCTS_DIR}/PinLayout/PinLayout.framework", 310 | "${PODS_ROOT}/Reveal-SDK/RevealServer/iOS/RevealServer.framework", 311 | "${BUILT_PRODUCTS_DIR}/Texture/AsyncDisplayKit.framework", 312 | ); 313 | name = "[CP] Embed Pods Frameworks"; 314 | outputPaths = ( 315 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FlexLayout.framework", 316 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LayoutKit.framework", 317 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NKFrameLayoutKit.framework", 318 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotAutoLayout.framework", 319 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINCache.framework", 320 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINOperation.framework", 321 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINRemoteImage.framework", 322 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PinLayout.framework", 323 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RevealServer.framework", 324 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AsyncDisplayKit.framework", 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | shellPath = /bin/sh; 328 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LayoutFrameworkBenchmark/Pods-LayoutFrameworkBenchmark-frameworks.sh\"\n"; 329 | showEnvVarsInLog = 0; 330 | }; 331 | FB2DF4C2068AD4C560959816 /* [CP] Check Pods Manifest.lock */ = { 332 | isa = PBXShellScriptBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | inputPaths = ( 337 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 338 | "${PODS_ROOT}/Manifest.lock", 339 | ); 340 | name = "[CP] Check Pods Manifest.lock"; 341 | outputPaths = ( 342 | "$(DERIVED_FILE_DIR)/Pods-LayoutFrameworkBenchmark-checkManifestLockResult.txt", 343 | ); 344 | runOnlyForDeploymentPostprocessing = 0; 345 | shellPath = /bin/sh; 346 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 347 | showEnvVarsInLog = 0; 348 | }; 349 | /* End PBXShellScriptBuildPhase section */ 350 | 351 | /* Begin PBXSourcesBuildPhase section */ 352 | 24661CF81F4EFFF5002CB883 /* Sources */ = { 353 | isa = PBXSourcesBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | E4A8A24F24FEDD14009D872B /* TextureCollectionViewController.swift in Sources */, 357 | 2401BCA11F4F044000788998 /* FeedItemUIStackView.swift in Sources */, 358 | 2401BC8B1F4F018C00788998 /* Stopwatch.swift in Sources */, 359 | 2401BC8F1F4F01D600788998 /* FeedItemLayoutKitView.swift in Sources */, 360 | 2401BC851F4F018C00788998 /* FeedItemData.swift in Sources */, 361 | 2401BC831F4F018C00788998 /* DataBinder.swift in Sources */, 362 | 2401BC961F4F022900788998 /* FeedItemPinLayoutView.swift in Sources */, 363 | 2401BC821F4F018C00788998 /* CollectionViewController.swift in Sources */, 364 | 2401BC9E1F4F042700788998 /* FeedItemManualView.swift in Sources */, 365 | 2401BC811F4F018C00788998 /* BenchmarkViewController.swift in Sources */, 366 | E4A8A14124FE99B1009D872B /* FeedItemTextureNode.swift in Sources */, 367 | BF3DC69820B560A400536177 /* FeedItemNotAutoLayoutView.swift in Sources */, 368 | 639E5A2020DF62D700C6BCEA /* NKFrameLayoutKitView.swift in Sources */, 369 | 2401BC9B1F4F03B300788998 /* ProfileCardLayout.swift in Sources */, 370 | 2401BC941F4F021F00788998 /* FeedItemFlexLayoutView.swift in Sources */, 371 | 2401BCA41F4F045F00788998 /* FeedItemAutoLayoutView.swift in Sources */, 372 | 24661D001F4EFFF5002CB883 /* AppDelegate.swift in Sources */, 373 | 2401BC991F4F029900788998 /* FeedItemLayout.swift in Sources */, 374 | ); 375 | runOnlyForDeploymentPostprocessing = 0; 376 | }; 377 | /* End PBXSourcesBuildPhase section */ 378 | 379 | /* Begin PBXVariantGroup section */ 380 | 24661D081F4EFFF5002CB883 /* LaunchScreen.storyboard */ = { 381 | isa = PBXVariantGroup; 382 | children = ( 383 | 24661D091F4EFFF5002CB883 /* Base */, 384 | ); 385 | name = LaunchScreen.storyboard; 386 | sourceTree = ""; 387 | }; 388 | /* End PBXVariantGroup section */ 389 | 390 | /* Begin XCBuildConfiguration section */ 391 | 24661D0C1F4EFFF5002CB883 /* Debug */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | CLANG_ANALYZER_NONNULL = YES; 396 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 397 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 398 | CLANG_CXX_LIBRARY = "libc++"; 399 | CLANG_ENABLE_MODULES = YES; 400 | CLANG_ENABLE_OBJC_ARC = YES; 401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 402 | CLANG_WARN_BOOL_CONVERSION = YES; 403 | CLANG_WARN_COMMA = YES; 404 | CLANG_WARN_CONSTANT_CONVERSION = YES; 405 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 417 | CLANG_WARN_STRICT_PROTOTYPES = YES; 418 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 419 | CLANG_WARN_UNREACHABLE_CODE = YES; 420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 421 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 422 | COPY_PHASE_STRIP = NO; 423 | DEBUG_INFORMATION_FORMAT = dwarf; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | ENABLE_TESTABILITY = YES; 426 | GCC_C_LANGUAGE_STANDARD = gnu99; 427 | GCC_DYNAMIC_NO_PIC = NO; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_OPTIMIZATION_LEVEL = 0; 430 | GCC_PREPROCESSOR_DEFINITIONS = ( 431 | "DEBUG=1", 432 | "$(inherited)", 433 | ); 434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 436 | GCC_WARN_UNDECLARED_SELECTOR = YES; 437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 438 | GCC_WARN_UNUSED_FUNCTION = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 441 | MTL_ENABLE_DEBUG_INFO = YES; 442 | ONLY_ACTIVE_ARCH = YES; 443 | SDKROOT = iphoneos; 444 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | TARGETED_DEVICE_FAMILY = "1,2"; 447 | }; 448 | name = Debug; 449 | }; 450 | 24661D0D1F4EFFF5002CB883 /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ALWAYS_SEARCH_USER_PATHS = NO; 454 | CLANG_ANALYZER_NONNULL = YES; 455 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 456 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 457 | CLANG_CXX_LIBRARY = "libc++"; 458 | CLANG_ENABLE_MODULES = YES; 459 | CLANG_ENABLE_OBJC_ARC = YES; 460 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 461 | CLANG_WARN_BOOL_CONVERSION = YES; 462 | CLANG_WARN_COMMA = YES; 463 | CLANG_WARN_CONSTANT_CONVERSION = YES; 464 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 465 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 466 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 467 | CLANG_WARN_EMPTY_BODY = YES; 468 | CLANG_WARN_ENUM_CONVERSION = YES; 469 | CLANG_WARN_INFINITE_RECURSION = YES; 470 | CLANG_WARN_INT_CONVERSION = YES; 471 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 473 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 476 | CLANG_WARN_STRICT_PROTOTYPES = YES; 477 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 478 | CLANG_WARN_UNREACHABLE_CODE = YES; 479 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 480 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 481 | COPY_PHASE_STRIP = NO; 482 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 483 | ENABLE_NS_ASSERTIONS = NO; 484 | ENABLE_STRICT_OBJC_MSGSEND = YES; 485 | GCC_C_LANGUAGE_STANDARD = gnu99; 486 | GCC_NO_COMMON_BLOCKS = YES; 487 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 488 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 489 | GCC_WARN_UNDECLARED_SELECTOR = YES; 490 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 491 | GCC_WARN_UNUSED_FUNCTION = YES; 492 | GCC_WARN_UNUSED_VARIABLE = YES; 493 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 494 | MTL_ENABLE_DEBUG_INFO = NO; 495 | SDKROOT = iphoneos; 496 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | VALIDATE_PRODUCT = YES; 499 | }; 500 | name = Release; 501 | }; 502 | 24661D0F1F4EFFF5002CB883 /* Debug */ = { 503 | isa = XCBuildConfiguration; 504 | baseConfigurationReference = 73BD901DE3512A23A7603899 /* Pods-LayoutFrameworkBenchmark.debug.xcconfig */; 505 | buildSettings = { 506 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 507 | CODE_SIGN_IDENTITY = "Apple Development"; 508 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 509 | CODE_SIGN_STYLE = Automatic; 510 | DEVELOPMENT_TEAM = ""; 511 | INFOPLIST_FILE = LayoutFrameworkBenchmark/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 513 | PRODUCT_BUNDLE_IDENTIFIER = com.layoutbox.LayoutFrameworkBenchmark; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | PROVISIONING_PROFILE_SPECIFIER = ""; 516 | SWIFT_VERSION = 5.0; 517 | }; 518 | name = Debug; 519 | }; 520 | 24661D101F4EFFF5002CB883 /* Release */ = { 521 | isa = XCBuildConfiguration; 522 | baseConfigurationReference = 1A3C4D1FF88A9395CE622CF2 /* Pods-LayoutFrameworkBenchmark.release.xcconfig */; 523 | buildSettings = { 524 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 525 | CODE_SIGN_IDENTITY = "Apple Development"; 526 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 527 | CODE_SIGN_STYLE = Automatic; 528 | DEVELOPMENT_TEAM = ""; 529 | INFOPLIST_FILE = LayoutFrameworkBenchmark/Info.plist; 530 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 531 | PRODUCT_BUNDLE_IDENTIFIER = com.layoutbox.LayoutFrameworkBenchmark; 532 | PRODUCT_NAME = "$(TARGET_NAME)"; 533 | PROVISIONING_PROFILE_SPECIFIER = ""; 534 | SWIFT_VERSION = 5.0; 535 | }; 536 | name = Release; 537 | }; 538 | /* End XCBuildConfiguration section */ 539 | 540 | /* Begin XCConfigurationList section */ 541 | 24661CF71F4EFFF5002CB883 /* Build configuration list for PBXProject "LayoutFrameworkBenchmark" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 24661D0C1F4EFFF5002CB883 /* Debug */, 545 | 24661D0D1F4EFFF5002CB883 /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | 24661D0E1F4EFFF5002CB883 /* Build configuration list for PBXNativeTarget "LayoutFrameworkBenchmark" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | 24661D0F1F4EFFF5002CB883 /* Debug */, 554 | 24661D101F4EFFF5002CB883 /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | /* End XCConfigurationList section */ 560 | }; 561 | rootObject = 24661CF41F4EFFF5002CB883 /* Project object */; 562 | } 563 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark.xcodeproj/project.xcworkspace/xcuserdata/dionlu.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark.xcodeproj/project.xcworkspace/xcuserdata/dionlu.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark.xcodeproj/xcshareddata/xcschemes/LayoutFrameworkBenchmark.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | window?.rootViewController = UINavigationController(rootViewController: BenchmarkViewController()) 20 | window?.makeKeyAndVisible() 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) { 25 | // 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. 26 | // 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. 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | // 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. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationDidBecomeActive(_ application: UIApplication) { 39 | // 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. 40 | } 41 | 42 | func applicationWillTerminate(_ application: UIApplication) { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/20x20.imageset/20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/20x20.imageset/20x20.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/20x20.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "20x20.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/350x200.imageset/350x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/350x200.imageset/350x200.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/350x200.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "350x200.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/50x50.imageset/50x50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/50x50.imageset/50x50.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/50x50.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "50x50.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.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 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "20x20", 48 | "scale" : "1x" 49 | }, 50 | { 51 | "idiom" : "ipad", 52 | "size" : "20x20", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "idiom" : "ipad", 57 | "size" : "29x29", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "idiom" : "ipad", 62 | "size" : "29x29", 63 | "scale" : "2x" 64 | }, 65 | { 66 | "idiom" : "ipad", 67 | "size" : "40x40", 68 | "scale" : "1x" 69 | }, 70 | { 71 | "idiom" : "ipad", 72 | "size" : "40x40", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "76x76", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-76.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "76x76", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-76@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "83.5x83.5", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-167.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "1024x1024", 95 | "idiom" : "ios-marketing", 96 | "filename" : "Icon-1024.png", 97 | "scale" : "1x" 98 | } 99 | ], 100 | "info" : { 101 | "version" : 1, 102 | "author" : "xcode" 103 | } 104 | } -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-167.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/LayoutFrameworkBenchmark/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/AutoLayout/FeedItemAutoLayoutView.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// A LinkedIn feed item that is implemented with Auto Layout. 12 | class FeedItemAutoLayoutView: UIView, DataBinder { 13 | 14 | let topBarView = TopBarView() 15 | let miniProfileView = MiniProfileView() 16 | let miniContentView = MiniContentView() 17 | let socialActionsView = SocialActionsView() 18 | let commentView = CommentView() 19 | 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | backgroundColor = UIColor.white 23 | let views: [String: UIView] = [ 24 | "topBarView": topBarView, 25 | "miniProfileView": miniProfileView, 26 | "miniContentView": miniContentView, 27 | "socialActionsView": socialActionsView, 28 | "commentView": commentView 29 | ] 30 | 31 | addAutoLayoutSubviews(views) 32 | addConstraints(withVisualFormat: "V:|-0-[topBarView]-0-[miniProfileView]-0-[miniContentView]-0-[socialActionsView]-0-[commentView]-0-|", views: views) 33 | addConstraints(withVisualFormat: "H:|-0-[topBarView]-0-|", views: views) 34 | addConstraints(withVisualFormat: "H:|-0-[miniProfileView]-0-|", views: views) 35 | addConstraints(withVisualFormat: "H:|-0-[miniContentView]-0-|", views: views) 36 | addConstraints(withVisualFormat: "H:|-0-[socialActionsView]-0-|", views: views) 37 | addConstraints(withVisualFormat: "H:|-0-[commentView]-0-|", views: views) 38 | } 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | func setData(_ data: FeedItemData) { 45 | topBarView.actionLabel.text = data.actionText 46 | miniProfileView.posterNameLabel.text = data.posterName 47 | miniProfileView.posterHeadlineLabel.text = data.posterHeadline 48 | miniProfileView.posterTimeLabel.text = data.posterTimestamp 49 | miniProfileView.posterCommentLabel.text = data.posterComment 50 | miniContentView.contentTitleLabel.text = data.contentTitle 51 | miniContentView.contentDomainLabel.text = data.contentDomain 52 | commentView.actorCommentLabel.text = data.actorComment 53 | } 54 | 55 | override func sizeThatFits(_ size: CGSize) -> CGSize { 56 | return systemLayoutSizeFitting(CGSize(width: size.width, height: 0)) 57 | } 58 | } 59 | 60 | class CommentView: UIView { 61 | let actorImageView: UIImageView = { 62 | let i = UIImageView() 63 | i.image = UIImage(named: "50x50.png") 64 | i.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 65 | i.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 66 | return i 67 | }() 68 | 69 | let actorCommentLabel: UILabel = UILabel() 70 | 71 | init() { 72 | super.init(frame: .zero) 73 | 74 | let views: [String: UIView] = [ 75 | "actorImageView": actorImageView, 76 | "actorCommentLabel": actorCommentLabel 77 | ] 78 | addAutoLayoutSubviews(views) 79 | 80 | addConstraints(withVisualFormat: "H:|-0-[actorImageView]-0-[actorCommentLabel]-0-|", views: views) 81 | addConstraints(withVisualFormat: "V:|-0-[actorImageView]-(>=0)-|", views: views) 82 | addConstraints(withVisualFormat: "V:|-0-[actorCommentLabel]-(>=0)-|", views: views) 83 | addConstraints(withVisualFormat: "V:[actorImageView]-(0@749)-|", views: views) 84 | } 85 | 86 | required init?(coder aDecoder: NSCoder) { 87 | fatalError("init(coder:) has not been implemented") 88 | } 89 | } 90 | 91 | class MiniContentView: UIView { 92 | let contentImageView: UIImageView = { 93 | let i = UIImageView() 94 | i.image = UIImage(named: "350x200.png") 95 | i.contentMode = .scaleAspectFit 96 | i.backgroundColor = UIColor.orange 97 | return i 98 | }() 99 | 100 | let contentTitleLabel: UILabel = UILabel() 101 | let contentDomainLabel: UILabel = UILabel() 102 | 103 | init() { 104 | super.init(frame: .zero) 105 | 106 | let views: [String: UIView] = [ 107 | "contentImageView": contentImageView, 108 | "contentTitleLabel": contentTitleLabel, 109 | "contentDomainLabel": contentDomainLabel 110 | ] 111 | 112 | addAutoLayoutSubviews(views) 113 | 114 | addConstraints(withVisualFormat: "V:|-0-[contentImageView]-0-[contentTitleLabel]-0-[contentDomainLabel]-0-|", views: views) 115 | 116 | addConstraints(withVisualFormat: "H:|-0-[contentImageView]-0-|", views: views) 117 | addConstraints(withVisualFormat: "H:|-0-[contentTitleLabel]-0-|", views: views) 118 | addConstraints(withVisualFormat: "H:|-0-[contentDomainLabel]-0-|", views: views) 119 | } 120 | 121 | required init?(coder aDecoder: NSCoder) { 122 | fatalError("init(coder:) has not been implemented") 123 | } 124 | } 125 | 126 | class MiniProfileView: UIView { 127 | 128 | let posterImageView: UIImageView = { 129 | let i = UIImageView() 130 | i.image = UIImage(named: "50x50.png") 131 | i.backgroundColor = UIColor.orange 132 | i.contentMode = .center 133 | i.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 134 | i.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 135 | return i 136 | }() 137 | 138 | let posterNameLabel: UILabel = { 139 | let l = UILabel() 140 | l.backgroundColor = UIColor.yellow 141 | return l 142 | }() 143 | 144 | let posterHeadlineLabel: UILabel = { 145 | let l = UILabel() 146 | l.numberOfLines = 3 147 | l.backgroundColor = UIColor.yellow 148 | return l 149 | }() 150 | 151 | let posterTimeLabel: UILabel = { 152 | let l = UILabel() 153 | l.backgroundColor = UIColor.yellow 154 | return l 155 | }() 156 | 157 | let posterCommentLabel: UILabel = UILabel() 158 | 159 | init() { 160 | super.init(frame: .zero) 161 | 162 | let views: [String: UIView] = [ 163 | "posterImageView": posterImageView, 164 | "posterNameLabel": posterNameLabel, 165 | "posterHeadlineLabel": posterHeadlineLabel, 166 | "posterTimeLabel": posterTimeLabel, 167 | "posterCommentLabel": posterCommentLabel 168 | ] 169 | addAutoLayoutSubviews(views) 170 | addConstraints(withVisualFormat: "V:|-0-[posterImageView]-(>=0)-[posterCommentLabel]-0-|", views: views) 171 | addConstraints(withVisualFormat: "V:|-1-[posterNameLabel]-1-[posterHeadlineLabel]-1-[posterTimeLabel]-(>=3)-[posterCommentLabel]", views: views) 172 | addConstraints(withVisualFormat: "V:[posterImageView]-(0@749)-[posterCommentLabel]", views: views) 173 | 174 | addConstraints(withVisualFormat: "H:|-0-[posterImageView]-2-[posterNameLabel]-4-|", views: views) 175 | addConstraints(withVisualFormat: "H:[posterImageView]-2-[posterHeadlineLabel]-4-|", views: views) 176 | addConstraints(withVisualFormat: "H:[posterImageView]-2-[posterTimeLabel]-4-|", views: views) 177 | addConstraints(withVisualFormat: "H:|-0-[posterCommentLabel]-0-|", views: views) 178 | } 179 | 180 | required init?(coder aDecoder: NSCoder) { 181 | fatalError("init(coder:) has not been implemented") 182 | } 183 | } 184 | 185 | class SocialActionsView: UIView { 186 | let likeLabel: UILabel = { 187 | let l = UILabel() 188 | l.text = "Like" 189 | l.backgroundColor = .green 190 | return l 191 | }() 192 | 193 | let commentLabel: UILabel = { 194 | let l = UILabel() 195 | l.text = "Comment" 196 | l.backgroundColor = .green 197 | l.textAlignment = .center 198 | return l 199 | }() 200 | 201 | let shareLabel: UILabel = { 202 | let l = UILabel() 203 | l.text = "Share" 204 | l.textAlignment = .right 205 | l.backgroundColor = .green 206 | return l 207 | }() 208 | 209 | init() { 210 | super.init(frame: .zero) 211 | 212 | let views: [String: UIView] = [ 213 | "likeLabel": likeLabel, 214 | "commentLabel": commentLabel, 215 | "shareLabel": shareLabel 216 | ] 217 | addAutoLayoutSubviews(views) 218 | 219 | addConstraints(withVisualFormat: "V:|-0-[likeLabel]-0-|", views: views) 220 | addConstraints(withVisualFormat: "V:|-0-[commentLabel]-0-|", views: views) 221 | addConstraints(withVisualFormat: "V:|-0-[shareLabel]-0-|", views: views) 222 | 223 | addConstraints([ 224 | NSLayoutConstraint(item: likeLabel, attribute: .leadingMargin, relatedBy: .equal, toItem: self, attribute: .leadingMargin, multiplier: 1, constant: 0), 225 | NSLayoutConstraint(item: commentLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0), 226 | NSLayoutConstraint(item: shareLabel, attribute: .trailingMargin, relatedBy: .equal, toItem: self, attribute: .trailingMargin, multiplier: 1, constant: 0) 227 | ]) 228 | } 229 | 230 | required init?(coder aDecoder: NSCoder) { 231 | fatalError("init(coder:) has not been implemented") 232 | } 233 | } 234 | 235 | class TopBarView: UIView { 236 | let actionLabel: UILabel = UILabel() 237 | 238 | let optionsLabel: UILabel = { 239 | let l = UILabel() 240 | l.text = "..." 241 | l.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 242 | l.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 243 | return l 244 | }() 245 | 246 | init() { 247 | super.init(frame: .zero) 248 | let views: [String: UIView] = ["actionLabel": actionLabel, "optionsLabel": optionsLabel] 249 | addAutoLayoutSubviews(views) 250 | addConstraints(withVisualFormat: "H:|-0-[actionLabel]-0-[optionsLabel]-0-|", views: views) 251 | addConstraints(withVisualFormat: "V:|-0-[actionLabel]-(>=0)-|", views: views) 252 | addConstraints(withVisualFormat: "V:|-0-[optionsLabel]-(>=0)-|", views: views) 253 | } 254 | 255 | required init?(coder aDecoder: NSCoder) { 256 | fatalError("init(coder:) has not been implemented") 257 | } 258 | } 259 | 260 | private extension UIView { 261 | 262 | func addAutoLayoutSubviews(_ subviews: [String: UIView]) { 263 | for (_, view) in subviews { 264 | addAutoLayoutSubview(view) 265 | } 266 | } 267 | 268 | func addAutoLayoutSubview(_ subview: UIView) { 269 | subview.translatesAutoresizingMaskIntoConstraints = false 270 | addSubview(subview) 271 | } 272 | 273 | func addConstraints(withVisualFormat visualFormat: String, views: [String: UIView]) { 274 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: visualFormat, options: [], metrics: nil, views: views)) 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/BenchmarkViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// Runs benchmarks for different kinds of layouts. 12 | class BenchmarkViewController: UITableViewController { 13 | 14 | private let reuseIdentifier = "cell" 15 | 16 | private let viewControllers: [ViewControllerData] = [ 17 | 18 | // 19 | // Ordered alphabetically 20 | // 21 | 22 | ViewControllerData(title: "Auto Layout", factoryBlock: { viewCount in 23 | let data = FeedItemData.generate(count: viewCount) 24 | return CollectionViewControllerFeedItemAutoLayoutView(data: data) 25 | }), 26 | 27 | ViewControllerData(title: "FlexLayout 1.3", factoryBlock: { viewCount in 28 | let data = FeedItemData.generate(count: viewCount) 29 | return CollectionViewControllerFeedItemFlexLayoutView(data: data) 30 | }), 31 | 32 | ViewControllerData(title: "LayoutKit 10.1", factoryBlock: { viewCount in 33 | let data = FeedItemData.generate(count: viewCount) 34 | return CollectionViewControllerFeedItemLayoutKitView(data: data) 35 | }), 36 | 37 | ViewControllerData(title: "Manual Layout", factoryBlock: { viewCount in 38 | let data = FeedItemData.generate(count: viewCount) 39 | return CollectionViewControllerFeedItemManualView(data: data) 40 | }), 41 | 42 | ViewControllerData(title: "NKFrameLayoutKit 2.5", factoryBlock: { viewCount in 43 | let data = FeedItemData.generate(count: viewCount) 44 | return CollectionViewControllerFeedItemNKFrameLayoutKitView(data: data) 45 | }), 46 | 47 | ViewControllerData(title: "NotAutoLayout 3.2", factoryBlock: { viewCount in 48 | let data = FeedItemData.generate(count: viewCount) 49 | return CollectionViewControllerFeedItemNotAutoLayoutView(data: data) 50 | }), 51 | 52 | ViewControllerData(title: "PinLayout 1.10", factoryBlock: { viewCount in 53 | let data = FeedItemData.generate(count: viewCount) 54 | return CollectionViewControllerFeedItemPinLayoutView(data: data) 55 | }), 56 | 57 | ViewControllerData(title: "Texture 3.1", factoryBlock: { viewCount in 58 | let data = FeedItemData.generate(count: viewCount) 59 | return TextureCollectionViewController(data: data) 60 | }), 61 | 62 | ViewControllerData(title: "UIStackView", factoryBlock: { viewCount in 63 | if #available(iOS 9.0, *) { 64 | let data = FeedItemData.generate(count: viewCount) 65 | return CollectionViewControllerFeedItemUIStackView(data: data) 66 | } else { 67 | NSLog("UIStackView only supported on iOS 9+") 68 | return nil 69 | } 70 | }) 71 | ] 72 | 73 | convenience init() { 74 | self.init(style: .grouped) 75 | title = "Benchmarks" 76 | } 77 | 78 | override func viewDidLoad() { 79 | super.viewDidLoad() 80 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseIdentifier) 81 | } 82 | 83 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 84 | return viewControllers.count + 1 85 | } 86 | 87 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 88 | if indexPath.row == 0 { 89 | let cell = UITableViewCell() 90 | cell.textLabel?.text = "Run all benchmarks" 91 | return cell 92 | } else { 93 | let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) 94 | cell.textLabel?.text = viewControllers[indexPath.row - 1].title 95 | return cell 96 | } 97 | } 98 | 99 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 100 | print("\nseconds/ops for each iterations (10, 20, ..., 100)") 101 | print("-------------------------------------") 102 | 103 | if indexPath.row == 0 { 104 | runAllBenchmarks() 105 | } else { 106 | let viewControllerData = viewControllers[indexPath.row - 1] 107 | runBenchmark(viewControllerData: viewControllerData, logResults: true, completed: { (results) in 108 | self.printResults(name: viewControllerData.title, results: results) 109 | }) 110 | } 111 | } 112 | 113 | private func runAllBenchmarks() { 114 | var benchmarkIndex = 0 115 | 116 | func benchmarkCompleted(_ results: [Result]) { 117 | printResults(name: viewControllers[benchmarkIndex].title, results: results) 118 | 119 | DispatchQueue.main.async { 120 | self.navigationController?.popViewController(animated: false) 121 | 122 | benchmarkIndex += 1 123 | if benchmarkIndex < self.viewControllers.count { 124 | self.runBenchmark(viewControllerData: self.viewControllers[benchmarkIndex], logResults: false, completed: benchmarkCompleted) 125 | } else { 126 | print("Completed!") 127 | } 128 | } 129 | } 130 | 131 | runBenchmark(viewControllerData: viewControllers[benchmarkIndex], logResults: false, completed: benchmarkCompleted) 132 | } 133 | 134 | private func printResults(name: String, results: [Result]) { 135 | var resultsString = "\(name)\t" 136 | results.forEach { (result) in 137 | let a = "\(result.secondsPerOperation)" 138 | resultsString += String(a.prefix(6)) 139 | resultsString += "," 140 | } 141 | print(resultsString) 142 | } 143 | 144 | private func runBenchmark(viewControllerData: ViewControllerData, logResults: Bool, completed: ((_ results: [Result]) -> Void)?) { 145 | guard let viewController = viewControllerData.factoryBlock(20) else { 146 | return 147 | } 148 | 149 | benchmark(viewControllerData, logResults: logResults, completed: completed) 150 | 151 | viewController.title = viewControllerData.title 152 | navigationController?.pushViewController(viewController, animated: logResults) 153 | } 154 | 155 | private func benchmark(_ viewControllerData: ViewControllerData, logResults: Bool, completed: ((_ results: [Result]) -> Void)?) { 156 | // let iterations = [1] 157 | let iterations = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] 158 | var results: [Result] = [] 159 | 160 | for i in iterations { 161 | let description = "\(i)\tsubviews\t\(viewControllerData.title)" 162 | let result = Stopwatch.benchmark(description, logResults: logResults, block: { (stopwatch: Stopwatch) -> Void in 163 | let vc = viewControllerData.factoryBlock(i) 164 | stopwatch.resume() 165 | vc?.view.layoutIfNeeded() 166 | stopwatch.pause() 167 | }) 168 | 169 | results.append(result) 170 | } 171 | 172 | completed?(results) 173 | } 174 | } 175 | 176 | private struct ViewControllerData { 177 | let title: String 178 | let factoryBlock: (_ viewCount: Int) -> UIViewController? 179 | } 180 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/CollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// Each view type of collection view should be a non-generic subclass from CollectionViewController 12 | /// If each view type uses the generic CollectionViewController, it will cause the crash. 13 | /// 14 | /// The crash reproduce steps: BenchmarkViewController -> UICollectionViewUIStackFeed 15 | /// -> Back -> Any other UICollectionView 16 | /// 17 | /// The reason behinds this is `DWURecyclingAlert` injects benchmark and label by using 18 | /// method swizzling. Method swizzling doesn't work well with generics. The second time 19 | /// of method sizzling would not work and bring into infinite loop if different view type 20 | /// uses `CollectionViewController` directly. 21 | /// 22 | @available(iOS 9, *) 23 | class CollectionViewControllerFeedItemUIStackView: CollectionViewController {} 24 | 25 | class CollectionViewControllerFeedItemAutoLayoutView: CollectionViewController {} 26 | class CollectionViewControllerFeedItemLayoutKitView: CollectionViewController {} 27 | class CollectionViewControllerFeedItemManualView: CollectionViewController {} 28 | class CollectionViewControllerFeedItemNotAutoLayoutView: CollectionViewController {} 29 | class CollectionViewControllerFeedItemPinLayoutView: CollectionViewController {} 30 | class CollectionViewControllerFeedItemFlexLayoutView: CollectionViewController {} 31 | class CollectionViewControllerFeedItemNKFrameLayoutKitView: CollectionViewController {} 32 | 33 | /// A UICollectionView controller where each cell's content view is a DataBinder. 34 | class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout where ContentViewType: DataBinder { 35 | 36 | typealias CellType = CollectionCell 37 | 38 | let reuseIdentifier = "cell" 39 | let data: [ContentViewType.DataType] 40 | let flowLayout: UICollectionViewFlowLayout 41 | let manequinCell: CellType 42 | let start: CFAbsoluteTime 43 | 44 | init(data: [ContentViewType.DataType]) { 45 | self.start = CFAbsoluteTimeGetCurrent() 46 | self.data = data 47 | self.flowLayout = UICollectionViewFlowLayout() 48 | self.manequinCell = CellType(frame: .zero) 49 | super.init(collectionViewLayout: flowLayout) 50 | } 51 | 52 | required init?(coder aDecoder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | collectionView?.register(CellType.self, forCellWithReuseIdentifier: reuseIdentifier) 59 | } 60 | 61 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 62 | return data.count 63 | } 64 | 65 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 66 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CellType 67 | cell.setData(data[indexPath.row]) 68 | return cell 69 | } 70 | 71 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 72 | manequinCell.setData(data[indexPath.row]) 73 | var size = manequinCell.sizeThatFits(CGSize(width: collectionView.bounds.width, height: CGFloat.greatestFiniteMagnitude)) 74 | size.width = collectionView.bounds.width 75 | return size 76 | } 77 | 78 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 79 | super.viewWillTransition(to: size, with: coordinator) 80 | flowLayout.invalidateLayout() 81 | } 82 | } 83 | 84 | /// A UICollectionView cell that adds a DataBinder to its content view. 85 | class CollectionCell: UICollectionViewCell, DataBinder where ContentView: DataBinder { 86 | 87 | let wrappedContentView: ContentView 88 | 89 | override init(frame: CGRect) { 90 | wrappedContentView = ContentView() 91 | super.init(frame: frame) 92 | wrappedContentView.translatesAutoresizingMaskIntoConstraints = false 93 | contentView.addSubview(wrappedContentView) 94 | let views = ["v": wrappedContentView] 95 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[v]-0-|", options: [], metrics: nil, views: views)) 96 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[v]-0-|", options: [], metrics: nil, views: views)) 97 | contentView.backgroundColor = UIColor.white 98 | } 99 | 100 | required init?(coder aDecoder: NSCoder) { 101 | fatalError("init(coder:) has not been implemented") 102 | } 103 | 104 | func setData(_ data: ContentView.DataType) { 105 | wrappedContentView.setData(data) 106 | } 107 | 108 | override func sizeThatFits(_ size: CGSize) -> CGSize { 109 | return wrappedContentView.sizeThatFits(size) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/DataBinder.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import Foundation 10 | 11 | protocol DataBinder { 12 | associatedtype DataType 13 | func setData(_ data: DataType) 14 | } 15 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/FeedItemData.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// Data to populate a feed item. 12 | struct FeedItemData { 13 | 14 | let actionText: String 15 | let posterName: String 16 | let posterHeadline: String 17 | let posterTimestamp: String 18 | let posterComment: String 19 | let contentTitle: String 20 | let contentDomain: String 21 | let actorComment: String 22 | 23 | static func generate(count: Int) -> [FeedItemData] { 24 | var datas = [FeedItemData]() 25 | for i in 0.. CGSize { 182 | layout(size: CGSize(width: size.width != .greatestFiniteMagnitude ? size.width : 10000, 183 | height: size.height != .greatestFiniteMagnitude ? size.height : 10000)) 184 | return CGSize(width: size.width, height: contentView.frame.height + 4) 185 | } 186 | 187 | override var intrinsicContentSize: CGSize { 188 | return sizeThatFits(CGSize(width: frame.width, height: .greatestFiniteMagnitude)) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/FeedItemLayoutKitView.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// A LinkedIn feed item that is implemented with LayoutKit. 12 | class FeedItemLayoutKitView: UIView, DataBinder { 13 | 14 | private var layout: FeedItemLayout? = nil 15 | 16 | private lazy var heightConstraint: NSLayoutConstraint = { 17 | let constraint = NSLayoutConstraint( 18 | item: self, attribute: .height, 19 | relatedBy: .equal, 20 | toItem: nil, 21 | attribute: .notAnAttribute, 22 | multiplier: 1, 23 | constant: self.bounds.height) 24 | constraint.isActive = true 25 | return constraint 26 | }() 27 | 28 | func setData(_ data: FeedItemData) { 29 | let posterProfile = ProfileCardLayout( 30 | name: data.posterName, 31 | headline: data.posterHeadline, 32 | timestamp: data.posterTimestamp, 33 | profileImageName: "50x50.png") 34 | 35 | let content = ContentLayout(title: data.contentTitle, domain: data.contentDomain) 36 | layout = FeedItemLayout( 37 | actionText: data.actionText, 38 | posterProfile: posterProfile, 39 | posterComment: data.posterComment, 40 | contentLayout: content, 41 | actorComment: data.actorComment) 42 | 43 | // Assure that `layoutSubviews` is called 44 | setNeedsLayout() 45 | 46 | // Only calculate height for valid width 47 | if bounds.width > 0 { 48 | heightConstraint.constant = sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude)).height 49 | } 50 | } 51 | 52 | override func sizeThatFits(_ size: CGSize) -> CGSize { 53 | return layout?.measurement(within: size).size ?? .zero 54 | } 55 | 56 | override var intrinsicContentSize: CGSize { 57 | return sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) 58 | } 59 | 60 | override func layoutSubviews() { 61 | layout?.measurement(within: bounds.size).arrangement(within: bounds).makeViews(in: self) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/CircleImagePileLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | /// Displays a pile of overlapping circular images. 13 | open class CircleImagePileLayout: StackLayout { 14 | 15 | public enum Mode { 16 | case leadingOnTop, trailingOnTop 17 | } 18 | 19 | open let mode: Mode 20 | 21 | public init(imageNames: [String], mode: Mode = .trailingOnTop, alignment: Alignment = .topLeading, viewReuseId: String? = nil) { 22 | self.mode = mode 23 | let sublayouts: [Layout] = imageNames.map { imageName in 24 | return SizeLayout(width: 50, height: 50, config: { imageView in 25 | imageView.image = UIImage(named: imageName) 26 | imageView.layer.cornerRadius = 25 27 | imageView.layer.masksToBounds = true 28 | imageView.layer.borderColor = UIColor.white.cgColor 29 | imageView.layer.borderWidth = 2 30 | }) 31 | } 32 | super.init( 33 | axis: .horizontal, 34 | spacing: -25, 35 | distribution: .leading, 36 | alignment: alignment, 37 | flexibility: .inflexible, 38 | viewReuseId: viewReuseId, 39 | sublayouts: sublayouts) 40 | } 41 | 42 | open override var needsView: Bool { 43 | return super.needsView || mode == .leadingOnTop 44 | } 45 | } 46 | 47 | open class CircleImagePileView: UIView { 48 | 49 | open override func addSubview(_ view: UIView) { 50 | // Make sure views are inserted below existing views so that the first image in the face pile is on top. 51 | if let lastSubview = subviews.last { 52 | insertSubview(view, belowSubview: lastSubview) 53 | } else { 54 | super.addSubview(view) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/DividerStackLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | /** 13 | A layout that places a divider view in the spacing between the stack's sublayouts. 14 | */ 15 | open class DividerStackLayout: StackLayout { 16 | 17 | public init(stack: StackLayout, dividerConfig: ((DividerView) -> Void)?) { 18 | let sublayouts: [Layout] 19 | if stack.spacing > 0 { 20 | var dividedSublayouts = [Layout]() 21 | let size = AxisSize(axis: stack.axis, axisLength: stack.spacing, crossLength: 0).size 22 | let divider = SizeLayout(size: size, alignment: .fill, flexibility: .flexible, config: dividerConfig) 23 | for (index, sublayout) in stack.sublayouts.enumerated() { 24 | dividedSublayouts.append(sublayout) 25 | if index != stack.sublayouts.count - 1 { 26 | dividedSublayouts.append(divider) 27 | } 28 | } 29 | sublayouts = dividedSublayouts 30 | } else { 31 | sublayouts = stack.sublayouts 32 | } 33 | 34 | super.init(axis: stack.axis, 35 | spacing: 0, 36 | distribution: stack.distribution, 37 | alignment: stack.alignment, 38 | flexibility: stack.flexibility, 39 | sublayouts: sublayouts, 40 | config: stack.config) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/FeedItemLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | /** 13 | A layout that is similar to an item in the LinkedIn feed. 14 | */ 15 | open class FeedItemLayout: InsetLayout { 16 | 17 | public init(actionText: String, 18 | posterProfile: ProfileCardLayout, 19 | posterComment: String, 20 | contentLayout: ContentLayout, 21 | actorComment: String) { 22 | 23 | let contentFilteringOptions = LabelLayout( 24 | text: "...", 25 | numberOfLines: 1, 26 | alignment: .topTrailing, 27 | flexibility: .inflexible, 28 | viewReuseId: "contentFilteringOptions" 29 | ) 30 | 31 | let actionStack = StackLayout(axis: .horizontal, sublayouts: [ 32 | LabelLayout(text: actionText, viewReuseId: "actionText"), 33 | contentFilteringOptions 34 | ]) 35 | 36 | let buttonConfig = { (label: UILabel) in 37 | label.backgroundColor = UIColor.green 38 | } 39 | let socialActions = StackLayout(axis: .horizontal, distribution: .fillEqualSize, sublayouts: [ 40 | LabelLayout(text: "Like", alignment: .centerLeading, viewReuseId: "like", config: buttonConfig), 41 | LabelLayout(text: "Comment", alignment: .center, viewReuseId: "comment", config: buttonConfig), 42 | LabelLayout(text: "Share", alignment: .centerTrailing, viewReuseId: "share", config: buttonConfig), 43 | ]) 44 | 45 | let actorCommentLayout = StackLayout(axis: .horizontal, sublayouts: [ 46 | SizeLayout(width: 50, height: 50, viewReuseId: "actorCommentPhoto", config: { imageView in 47 | imageView.image = UIImage(named: "50x50.png") 48 | }), 49 | LabelLayout(text: actorComment, alignment: .centerLeading, viewReuseId: "actorComment") 50 | ]) 51 | 52 | let feedItem = StackLayout( 53 | axis: .vertical, 54 | spacing: 4, 55 | sublayouts: [ 56 | actionStack, 57 | posterProfile, 58 | LabelLayout(text: posterComment, numberOfLines: 4, viewReuseId: "posterComment"), 59 | contentLayout, 60 | socialActions, 61 | actorCommentLayout 62 | ] 63 | ) 64 | super.init(insets: EdgeInsets(top: 8, left: 8, bottom: 8, right: 8), viewReuseId: "feedItem", sublayout: feedItem, config: { view in 65 | view.backgroundColor = UIColor.white 66 | }) 67 | } 68 | } 69 | 70 | open class ContentLayout: StackLayout { 71 | 72 | public init(title: String, domain: String) { 73 | super.init(axis: .vertical, sublayouts: [ 74 | SizeLayout( 75 | size: CGSize(width: 350, height: 200), 76 | alignment: Alignment(vertical: .top, horizontal: .fill), 77 | viewReuseId: "contentImage", 78 | config: { imageView in 79 | imageView.image = UIImage(named: "350x200.png") 80 | } 81 | ), 82 | LabelLayout(text: title, viewReuseId: "contentTitle"), 83 | LabelLayout(text: domain, viewReuseId: "contentDomain") 84 | ]) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/FixedWidthCellCollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | /** 13 | A layout for a collection view that has fixed width cells. 14 | The height of the collection view is the height of the tallest cell. 15 | */ 16 | open class FixedWidthCellCollectionViewLayout: BaseLayout, ConfigurableLayout where C.Iterator.Element == Layout { 17 | 18 | private let cellWidth: CGFloat 19 | private let sectionLayouts: [Section] 20 | public init(cellWidth: CGFloat, sectionLayouts: [Section], alignment: Alignment = .topFill, viewReuseId: String? = nil, config: ((V) -> Void)? = nil) { 21 | self.cellWidth = cellWidth 22 | self.sectionLayouts = sectionLayouts 23 | super.init(alignment: alignment, flexibility: Flexibility(horizontal: Flexibility.defaultFlex, vertical: nil), viewReuseId: viewReuseId, config: config) 24 | } 25 | 26 | // Measure the sections/items with the fixed width and unlimited height. 27 | private lazy var sectionMeasurements: [Section<[LayoutMeasurement]>] = { 28 | return self.sectionLayouts.map { sectionLayout in 29 | return sectionLayout.map({ (layout: Layout) -> LayoutMeasurement in 30 | return layout.measurement(within: CGSize(width: self.cellWidth, height: CGFloat.greatestFiniteMagnitude)) 31 | }) 32 | } 33 | }() 34 | 35 | open func measurement(within maxSize: CGSize) -> LayoutMeasurement { 36 | // Compute the max height of all sections/items so that we know the height of the collection view. 37 | let maxHeight = sectionMeasurements.reduce(0) { (maxHeight, measuredSection) -> CGFloat in 38 | let headerHeight = measuredSection.header?.size.height ?? 0 39 | let footerHeight = measuredSection.footer?.size.height ?? 0 40 | let maxItemHeight = measuredSection.items.map({ $0.size.height }).reduce(0, max) 41 | return max(maxHeight, headerHeight, maxItemHeight, footerHeight) 42 | } 43 | 44 | // No intrinsic width, but want to be tall enough for our tallest cell. 45 | let size = CGSize(width: 0, height: min(maxHeight, maxSize.height)) 46 | return LayoutMeasurement(layout: self, size: size, maxSize: maxSize, sublayouts: []) 47 | } 48 | 49 | private var sectionArrangements: [Section<[LayoutArrangement]>]? = nil 50 | 51 | open func arrangement(within rect: CGRect, measurement: LayoutMeasurement) -> LayoutArrangement { 52 | sectionArrangements = sectionMeasurements.map({ sectionMeasurement in 53 | return sectionMeasurement.map({ (measurement: LayoutMeasurement) -> LayoutArrangement in 54 | let rect = CGRect(x: 0, y: 0, width: self.cellWidth, height: rect.height) 55 | return measurement.arrangement(within: rect) 56 | }) 57 | }) 58 | 59 | let frame = Alignment.fill.position(size: measurement.size, in: rect) 60 | return LayoutArrangement(layout: self, frame: frame, sublayouts: []) 61 | } 62 | 63 | open override func configure(view: V) { 64 | super.configure(view: view) 65 | if let sectionArrangements = sectionArrangements { 66 | view.layoutAdapter.reload(arrangement: sectionArrangements) 67 | } 68 | } 69 | 70 | open override var needsView: Bool { 71 | return super.needsView || sectionArrangements != nil 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/HelloWorldAutoLayoutView.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /** 12 | A simple hello world layout that uses Auto Layout. 13 | Compare to HelloWorldLayout.swift 14 | */ 15 | open class HelloWorldAutoLayoutView: UIView { 16 | 17 | private lazy var imageView: UIImageView = { 18 | let imageView = UIImageView() 19 | imageView.translatesAutoresizingMaskIntoConstraints = false 20 | imageView.setContentHuggingPriority(UILayoutPriorityRequired, for: .vertical) 21 | imageView.setContentHuggingPriority(UILayoutPriorityRequired, for: .horizontal) 22 | imageView.setContentCompressionResistancePriority(UILayoutPriorityRequired, for: .vertical) 23 | imageView.setContentCompressionResistancePriority(UILayoutPriorityRequired, for: .horizontal) 24 | imageView.image = UIImage(named: "earth.png") 25 | return imageView 26 | }() 27 | 28 | private lazy var label: UILabel = { 29 | let label = UILabel() 30 | label.translatesAutoresizingMaskIntoConstraints = false 31 | label.text = "Hello World!" 32 | return label 33 | }() 34 | 35 | public override init(frame: CGRect) { 36 | super.init(frame: frame) 37 | addSubview(imageView) 38 | addSubview(label) 39 | 40 | let views: [String: UIView] = ["imageView": imageView, "label": label] 41 | var constraints = [NSLayoutConstraint]() 42 | constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-4-[imageView(==50)]-4-|", options: [], metrics: nil, views: views)) 43 | constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-4-[imageView(==50)]-4-[label]-8-|", options: [], metrics: nil, views: views)) 44 | constraints.append(NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)) 45 | NSLayoutConstraint.activate(constraints) 46 | } 47 | 48 | public required init?(coder aDecoder: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/HelloWorldLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | /** 13 | A simple hello world layout. 14 | 15 | ``` 16 | let helloView = HelloWorldLayout().arrangement().makeViews() 17 | ``` 18 | 19 | Compare to HelloWorldAutoLayout.swift 20 | */ 21 | open class HelloWorldLayout: InsetLayout { 22 | 23 | public init(text: String = "Hello World!") { 24 | super.init( 25 | insets: EdgeInsets(top: 4, left: 4, bottom: 4, right: 8), 26 | sublayout: StackLayout( 27 | axis: .horizontal, 28 | spacing: 4, 29 | sublayouts: [ 30 | SizeLayout(width: 50, height: 50, config: { imageView in 31 | imageView.image = UIImage(named: "earth.png") 32 | }), 33 | LabelLayout(text: text, alignment: .center) 34 | ] 35 | ) 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/MiniProfileLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | // ANY CHANGES TO THIS FILE SHOULD ALSO BE MADE IN `Documentation/docs/layouts.md` 10 | 11 | import UIKit 12 | import LayoutKit 13 | 14 | /// A small version of a LinkedIn profile. 15 | open class MiniProfileLayout: InsetLayout { 16 | 17 | public init(imageName: String, name: String, headline: String) { 18 | let image = SizeLayout( 19 | width: 80, 20 | height: 80, 21 | alignment: .center, 22 | config: { imageView in 23 | imageView.image = UIImage(named: imageName) 24 | 25 | // Not the most performant way to do a corner radius, but this is just a demo. 26 | imageView.layer.cornerRadius = 40 27 | imageView.layer.masksToBounds = true 28 | } 29 | ) 30 | 31 | let nameLayout = LabelLayout(text: name, font: UIFont.systemFont(ofSize: 40)) 32 | 33 | let headlineLayout = LabelLayout( 34 | text: headline, 35 | font: UIFont.systemFont(ofSize: 20), 36 | config: { label in 37 | label.textColor = UIColor.darkGray 38 | } 39 | ) 40 | 41 | super.init( 42 | insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), 43 | sublayout: StackLayout( 44 | axis: .horizontal, 45 | spacing: 8, 46 | sublayouts: [ 47 | image, 48 | StackLayout(axis: .vertical, spacing: 2, sublayouts: [nameLayout, headlineLayout]) 49 | ] 50 | ), 51 | config: { view in 52 | view.backgroundColor = UIColor.white 53 | } 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/ProfileCardLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | open class ProfileCardLayout: StackLayout { 13 | 14 | public init(name: String, headline: String, timestamp: String, profileImageName: String) { 15 | let labelConfig = { (label: UILabel) in 16 | label.backgroundColor = UIColor.yellow 17 | } 18 | 19 | let nameAndConnectionDegree = StackLayout( 20 | axis: .horizontal, 21 | spacing: 4, 22 | sublayouts: [ 23 | LabelLayout(text: name, viewReuseId: "name", config: labelConfig), 24 | ] 25 | ) 26 | 27 | let headline = LabelLayout(text: headline, numberOfLines: 2, viewReuseId: "headline", config: labelConfig) 28 | let timestamp = LabelLayout(text: timestamp, numberOfLines: 2, viewReuseId: "timestamp", config: labelConfig) 29 | 30 | let verticalLabelStack = StackLayout( 31 | axis: .vertical, 32 | spacing: 2, 33 | alignment: Alignment(vertical: .center, horizontal: .leading), 34 | sublayouts: [nameAndConnectionDegree, headline, timestamp] 35 | ) 36 | 37 | let profileImage = SizeLayout( 38 | size: CGSize(width: 50, height: 50), 39 | viewReuseId: "profileImage", 40 | config: { imageView in 41 | imageView.image = UIImage(named: profileImageName) 42 | } 43 | ) 44 | 45 | super.init( 46 | axis: .horizontal, 47 | spacing: 4, 48 | sublayouts: [ 49 | profileImage, 50 | verticalLabelStack 51 | ] 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/LayoutKit/Layouts/SkillsCardLayout.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import LayoutKit 11 | 12 | open class SkillsCardLayout: InsetLayout { 13 | 14 | public init(skill: String, endorsementCount: String, endorserProfileImageName: String) { 15 | let skillLabel = LabelLayout( 16 | text: skill, 17 | alignment: Alignment.center, 18 | flexibility: Flexibility.high, // Higher than default flexibility 19 | config: { label in 20 | label.backgroundColor = UIColor.yellow 21 | } 22 | ) 23 | let countLabel = LabelLayout( 24 | text: endorsementCount, 25 | alignment: Alignment.center, 26 | flexibility: Flexibility.low, 27 | config: { label in 28 | label.backgroundColor = UIColor.yellow 29 | } 30 | ) 31 | let endorserImage = SizeLayout( 32 | size: CGSize(width: 20, height: 20), 33 | alignment: Alignment.center, 34 | config: { imageView in 35 | imageView.image = UIImage(named: endorserProfileImageName) 36 | } 37 | ) 38 | let layout = StackLayout( 39 | axis: .horizontal, 40 | spacing: 8, 41 | distribution: .fillFlexing, 42 | alignment: Alignment(vertical: .center, horizontal: .fill), 43 | sublayouts: [ 44 | skillLabel, 45 | countLabel, 46 | endorserImage, 47 | ] 48 | ) 49 | super.init(insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), sublayout: layout) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/ManualLayout/FeedItemManualView.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// A LinkedIn feed item that is implemented with manual layout code. 12 | class FeedItemManualView: UIView, DataBinder { 13 | let hMargin: CGFloat = 8 14 | 15 | let actionLabel: UILabel = { 16 | let l = UILabel() 17 | return l 18 | }() 19 | 20 | let optionsLabel: UILabel = { 21 | let l = UILabel() 22 | l.text = "..." 23 | l.sizeToFit() 24 | return l 25 | }() 26 | 27 | let posterImageView: UIImageView = { 28 | let i = UIImageView() 29 | i.image = UIImage(named: "50x50.png") 30 | i.backgroundColor = UIColor.orange 31 | i.contentMode = .scaleToFill 32 | i.sizeToFit() 33 | return i 34 | }() 35 | 36 | let posterNameLabel: UILabel = { 37 | let l = UILabel() 38 | l.backgroundColor = UIColor.yellow 39 | return l 40 | }() 41 | 42 | let posterHeadlineLabel: UILabel = { 43 | let l = UILabel() 44 | l.backgroundColor = UIColor.yellow 45 | return l 46 | }() 47 | 48 | let posterTimeLabel: UILabel = { 49 | let l = UILabel() 50 | l.backgroundColor = UIColor.yellow 51 | return l 52 | }() 53 | 54 | let posterCommentLabel: UILabel = UILabel() 55 | 56 | let contentImageView: UIImageView = { 57 | let i = UIImageView() 58 | i.image = UIImage(named: "350x200.png") 59 | i.contentMode = .scaleToFill 60 | i.sizeToFit() 61 | return i 62 | }() 63 | 64 | let contentTitleLabel: UILabel = UILabel() 65 | let contentDomainLabel: UILabel = UILabel() 66 | 67 | let likeLabel: UILabel = { 68 | let l = UILabel() 69 | l.backgroundColor = .green 70 | l.text = "Like" 71 | return l 72 | }() 73 | 74 | let commentLabel: UILabel = { 75 | let l = UILabel() 76 | l.text = "Comment" 77 | l.backgroundColor = .green 78 | l.textAlignment = .center 79 | return l 80 | }() 81 | 82 | let shareLabel: UILabel = { 83 | let l = UILabel() 84 | l.text = "Share" 85 | l.backgroundColor = .green 86 | l.textAlignment = .right 87 | return l 88 | }() 89 | 90 | let actorImageView: UIImageView = { 91 | let i = UIImageView() 92 | i.image = UIImage(named: "50x50.png") 93 | return i 94 | }() 95 | 96 | let actorCommentLabel: UILabel = UILabel() 97 | 98 | override init(frame: CGRect) { 99 | super.init(frame: frame) 100 | addSubview(actionLabel) 101 | addSubview(optionsLabel) 102 | addSubview(posterImageView) 103 | addSubview(posterNameLabel) 104 | addSubview(posterHeadlineLabel) 105 | addSubview(posterTimeLabel) 106 | addSubview(posterCommentLabel) 107 | addSubview(contentImageView) 108 | addSubview(contentTitleLabel) 109 | addSubview(contentDomainLabel) 110 | addSubview(likeLabel) 111 | addSubview(commentLabel) 112 | addSubview(shareLabel) 113 | addSubview(actorImageView) 114 | addSubview(actorCommentLabel) 115 | backgroundColor = UIColor.white 116 | } 117 | 118 | required init?(coder aDecoder: NSCoder) { 119 | fatalError("init(coder:) has not been implemented") 120 | } 121 | 122 | func setData(_ data: FeedItemData) { 123 | actionLabel.text = data.actionText 124 | 125 | posterNameLabel.text = data.posterName 126 | posterNameLabel.sizeToFit() 127 | 128 | posterHeadlineLabel.text = data.posterHeadline 129 | posterHeadlineLabel.sizeToFit() 130 | 131 | posterTimeLabel.text = data.posterTimestamp 132 | posterTimeLabel.sizeToFit() 133 | 134 | posterCommentLabel.text = data.posterComment 135 | contentTitleLabel.text = data.contentTitle 136 | contentDomainLabel.text = data.contentDomain 137 | actorCommentLabel.text = data.actorComment 138 | setNeedsLayout() 139 | } 140 | 141 | override func layoutSubviews() { 142 | super.layoutSubviews() 143 | 144 | let vMargin: CGFloat = 4 145 | let spacing: CGFloat = 1 146 | 147 | optionsLabel.frame = CGRect(x: bounds.width-optionsLabel.frame.width - hMargin, y: hMargin, width: optionsLabel.frame.width, height: optionsLabel.frame.height) 148 | actionLabel.frame = CGRect(x: hMargin, y: hMargin, width: bounds.width-optionsLabel.frame.width, height: 0) 149 | actionLabel.sizeToFit() 150 | 151 | posterImageView.frame = CGRect(x: hMargin, y: actionLabel.frame.bottom + 10, width: posterImageView.frame.width, height: 0) 152 | posterImageView.sizeToFit() 153 | 154 | let contentInsets = UIEdgeInsets(top: -10, left: 2, bottom: 2, right: 3) 155 | posterNameLabel.frame = CGRect(x: posterImageView.frame.right + contentInsets.left, y: posterImageView.frame.origin.y + contentInsets.top, width: posterNameLabel.frame.width, height: posterNameLabel.frame.height) 156 | 157 | posterHeadlineLabel.frame = CGRect(x: posterImageView.frame.right + contentInsets.left, y: posterNameLabel.frame.bottom + spacing, width: posterHeadlineLabel.frame.width, height: posterHeadlineLabel.frame.height) 158 | 159 | posterTimeLabel.frame = CGRect(x: posterImageView.frame.right + contentInsets.left, y: posterHeadlineLabel.frame.bottom + spacing, width: posterTimeLabel.frame.width, height: posterTimeLabel.frame.height) 160 | 161 | posterCommentLabel.frame = CGRect(x: hMargin, y: max(posterImageView.frame.bottom, posterTimeLabel.frame.bottom + contentInsets.bottom), width: frame.width, height: 0) 162 | posterCommentLabel.sizeToFit() 163 | 164 | contentImageView.frame = CGRect(x: hMargin, y: posterCommentLabel.frame.bottom, width: frame.width, height: 0) 165 | contentImageView.sizeToFit() 166 | 167 | contentTitleLabel.frame = CGRect(x: hMargin, y: contentImageView.frame.bottom, width: frame.width, height: 0) 168 | contentTitleLabel.sizeToFit() 169 | 170 | contentDomainLabel.frame = CGRect(x: hMargin, y: contentTitleLabel.frame.bottom, width: frame.width, height: 0) 171 | contentDomainLabel.sizeToFit() 172 | 173 | likeLabel.frame = CGRect(x: hMargin, y: contentDomainLabel.frame.bottom + vMargin, width: 0, height: 0) 174 | likeLabel.sizeToFit() 175 | 176 | commentLabel.sizeToFit() 177 | commentLabel.frame = CGRect(x: frame.width / 2 - commentLabel.frame.width / 2, y: contentDomainLabel.frame.bottom + vMargin, width: commentLabel.frame.width, height: commentLabel.frame.height) 178 | 179 | shareLabel.sizeToFit() 180 | shareLabel.frame = CGRect(x: frame.width - shareLabel.frame.width - hMargin, y: contentDomainLabel.frame.bottom + vMargin, width: shareLabel.frame.width, height: shareLabel.frame.height) 181 | 182 | actorImageView.frame = CGRect(x: hMargin, y: likeLabel.frame.bottom + vMargin, width: 0, height: 0) 183 | actorImageView.sizeToFit() 184 | 185 | actorCommentLabel.frame = CGRect(x: actorImageView.frame.right + vMargin, 186 | y: actorImageView.frame.minY + (actorImageView.frame.height - actorCommentLabel.frame.height) / 2, 187 | width: frame.width-actorImageView.frame.width, 188 | height: 0) 189 | actorCommentLabel.sizeToFit() 190 | } 191 | 192 | override func sizeThatFits(_ size: CGSize) -> CGSize { 193 | frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 194 | layoutSubviews() 195 | return CGSize(width: size.width, height: max(actorImageView.frame.bottom, actorCommentLabel.frame.bottom) + hMargin) 196 | } 197 | 198 | override var intrinsicContentSize: CGSize { 199 | return sizeThatFits(CGSize(width: frame.width, height: CGFloat.greatestFiniteMagnitude)) 200 | } 201 | } 202 | 203 | extension CGRect { 204 | var bottom: CGFloat { 205 | return origin.y + size.height 206 | } 207 | 208 | var right: CGFloat { 209 | return origin.x + size.width 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/NKFrameLayoutKit/NKFrameLayoutKitView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NKFrameLayoutKitView.swift 3 | // LayoutFrameworkBenchmark 4 | // 5 | // Created by Nam Kennic on 6/24/18. 6 | // 7 | 8 | import UIKit 9 | import NKFrameLayoutKit 10 | 11 | /// A LinkedIn feed item that is implemented with NKFrameLayoutKit code. 12 | class NKFrameLayoutKitView: UIView, DataBinder { 13 | 14 | var mainFrameLayout: NKGridFrameLayout! 15 | 16 | let actionLabel: UILabel = { 17 | let l = UILabel() 18 | return l 19 | }() 20 | 21 | let optionsLabel: UILabel = { 22 | let l = UILabel() 23 | l.text = "..." 24 | return l 25 | }() 26 | 27 | let posterImageView: UIImageView = { 28 | let i = UIImageView() 29 | i.image = UIImage(named: "50x50.png") 30 | i.backgroundColor = UIColor.orange 31 | i.contentMode = .scaleToFill 32 | return i 33 | }() 34 | 35 | let posterNameLabel: UILabel = { 36 | let l = UILabel() 37 | l.backgroundColor = UIColor.yellow 38 | return l 39 | }() 40 | 41 | let posterHeadlineLabel: UILabel = { 42 | let l = UILabel() 43 | l.numberOfLines = 3 44 | return l 45 | }() 46 | 47 | let posterTimeLabel: UILabel = { 48 | let l = UILabel() 49 | l.backgroundColor = UIColor.yellow 50 | return l 51 | }() 52 | 53 | let posterCommentLabel: UILabel = UILabel() 54 | 55 | let contentImageView: UIImageView = { 56 | let i = UIImageView() 57 | i.image = UIImage(named: "350x200.png") 58 | i.contentMode = .scaleToFill 59 | return i 60 | }() 61 | 62 | let contentTitleLabel: UILabel = UILabel() 63 | let contentDomainLabel: UILabel = UILabel() 64 | 65 | let likeLabel: UILabel = { 66 | let l = UILabel() 67 | l.backgroundColor = .green 68 | l.text = "Like" 69 | return l 70 | }() 71 | 72 | let commentLabel: UILabel = { 73 | let l = UILabel() 74 | l.text = "Comment" 75 | l.backgroundColor = .green 76 | l.textAlignment = .center 77 | return l 78 | }() 79 | 80 | let shareLabel: UILabel = { 81 | let l = UILabel() 82 | l.text = "Share" 83 | l.backgroundColor = .green 84 | l.textAlignment = .right 85 | return l 86 | }() 87 | 88 | let actorImageView: UIImageView = { 89 | let i = UIImageView() 90 | i.image = UIImage(named: "50x50.png") 91 | return i 92 | }() 93 | 94 | let actorCommentLabel: UILabel = UILabel() 95 | 96 | lazy var topBar: NKDoubleFrameLayout = { 97 | let v = NKDoubleFrameLayout(direction: .horizontal, andViews: [self.actionLabel, self.optionsLabel])! 98 | v.rightFrameLayout.contentHorizontalAlignment = .right 99 | return v 100 | }() 101 | 102 | lazy var posters: NKDoubleFrameLayout = { 103 | let labels = NKGridFrameLayout(direction: .vertical, andViews: [self.posterNameLabel, self.posterHeadlineLabel, self.posterTimeLabel])! 104 | let v = NKDoubleFrameLayout(direction: .horizontal, andViews: [self.posterImageView, labels])! 105 | v.leftFrameLayout.contentVerticalAlignment = .center 106 | v.spacing = 5 107 | v.edgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0) 108 | return v 109 | }() 110 | 111 | lazy var actions: NKGridFrameLayout = { 112 | let v = NKGridFrameLayout(direction: .horizontal)! // andViews: [self.likeLabel, self.commentLabel, self.shareLabel] 113 | v.add(withTargetView: self.likeLabel).contentHorizontalAlignment = .left 114 | v.add(withTargetView: self.commentLabel).contentHorizontalAlignment = .center 115 | v.add(withTargetView: self.shareLabel).contentHorizontalAlignment = .right 116 | v.layoutAlignment = .split 117 | return v 118 | }() 119 | 120 | lazy var comment: NKDoubleFrameLayout = { 121 | let v = NKDoubleFrameLayout(direction: .horizontal, andViews: [self.actorImageView, self.actorCommentLabel])! 122 | v.edgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0) 123 | v.spacing = 5 124 | return v 125 | }() 126 | 127 | override init(frame: CGRect) { 128 | super.init(frame: frame) 129 | addSubview(actionLabel) 130 | addSubview(optionsLabel) 131 | addSubview(posterImageView) 132 | addSubview(posterNameLabel) 133 | addSubview(posterHeadlineLabel) 134 | addSubview(posterTimeLabel) 135 | addSubview(posterCommentLabel) 136 | addSubview(contentImageView) 137 | addSubview(contentTitleLabel) 138 | addSubview(contentDomainLabel) 139 | addSubview(likeLabel) 140 | addSubview(commentLabel) 141 | addSubview(shareLabel) 142 | addSubview(actorImageView) 143 | addSubview(actorCommentLabel) 144 | backgroundColor = UIColor.white 145 | 146 | mainFrameLayout = NKGridFrameLayout(direction: .vertical, andViews: [topBar, posters, posterCommentLabel, contentImageView, contentTitleLabel, contentDomainLabel, actions, comment]) 147 | mainFrameLayout.edgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) 148 | addSubview(mainFrameLayout) 149 | } 150 | 151 | required init?(coder aDecoder: NSCoder) { 152 | fatalError("init(coder:) has not been implemented") 153 | } 154 | 155 | func setData(_ data: FeedItemData) { 156 | actionLabel.text = data.actionText 157 | posterNameLabel.text = data.posterName 158 | posterHeadlineLabel.text = data.posterHeadline 159 | posterTimeLabel.text = data.posterTimestamp 160 | posterCommentLabel.text = data.posterComment 161 | contentTitleLabel.text = data.contentTitle 162 | contentDomainLabel.text = data.contentDomain 163 | actorCommentLabel.text = data.actorComment 164 | setNeedsLayout() 165 | } 166 | 167 | override func layoutSubviews() { 168 | super.layoutSubviews() 169 | mainFrameLayout.frame = self.bounds 170 | } 171 | 172 | override func sizeThatFits(_ size: CGSize) -> CGSize { 173 | return mainFrameLayout.sizeThatFits(size) 174 | } 175 | 176 | override var intrinsicContentSize: CGSize { 177 | return mainFrameLayout.sizeThatFits(CGSize(width: frame.width, height: CGFloat.greatestFiniteMagnitude)) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/PinLayout/FeedItemPinLayoutView.swift: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 2 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 3 | // 4 | // Unless required by applicable law or agreed to in writing, 5 | // software distributed under the License is distributed on an "AS IS" BASIS, 6 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7 | 8 | import UIKit 9 | import PinLayout 10 | 11 | /// A LinkedIn feed item that is implemented with PinLayout code. 12 | class FeedItemPinLayoutView: UIView, DataBinder { 13 | 14 | let actionLabel: UILabel = { 15 | let l = UILabel() 16 | l.font = UILabel().font ?? UIFont.systemFont(ofSize: 17) 17 | return l 18 | }() 19 | 20 | let optionsLabel: UILabel = { 21 | let l = UILabel() 22 | l.font = UILabel().font ?? UIFont.systemFont(ofSize: 17) 23 | l.text = "..." 24 | l.sizeToFit() 25 | return l 26 | }() 27 | 28 | let posterImageView: UIImageView = { 29 | let i = UIImageView() 30 | i.image = UIImage(named: "50x50.png") 31 | i.backgroundColor = UIColor.orange 32 | i.contentMode = .center 33 | i.sizeToFit() 34 | return i 35 | }() 36 | 37 | let posterNameLabel: UILabel = { 38 | let l = UILabel() 39 | l.backgroundColor = UIColor.yellow 40 | return l 41 | }() 42 | 43 | let posterHeadlineLabel: UILabel = { 44 | let l = UILabel() 45 | l.backgroundColor = UIColor.yellow 46 | l.numberOfLines = 3 47 | return l 48 | }() 49 | 50 | let posterTimeLabel: UILabel = { 51 | let l = UILabel() 52 | l.backgroundColor = UIColor.yellow 53 | return l 54 | }() 55 | 56 | let posterCommentLabel: UILabel = UILabel() 57 | 58 | let contentImageView: UIImageView = { 59 | let i = UIImageView() 60 | i.image = UIImage(named: "350x200.png") 61 | i.contentMode = .scaleToFill 62 | i.sizeToFit() 63 | return i 64 | }() 65 | 66 | let contentTitleLabel: UILabel = UILabel() 67 | let contentDomainLabel: UILabel = UILabel() 68 | 69 | let likeLabel: UILabel = { 70 | let l = UILabel() 71 | l.backgroundColor = .green 72 | l.text = "Like" 73 | l.sizeToFit() 74 | return l 75 | }() 76 | 77 | let commentLabel: UILabel = { 78 | let l = UILabel() 79 | l.text = "Comment" 80 | l.backgroundColor = .green 81 | l.textAlignment = .center 82 | l.sizeToFit() 83 | return l 84 | }() 85 | 86 | let shareLabel: UILabel = { 87 | let l = UILabel() 88 | l.text = "Share" 89 | l.backgroundColor = .green 90 | l.textAlignment = .right 91 | l.sizeToFit() 92 | return l 93 | }() 94 | 95 | let actorImageView: UIImageView = { 96 | let i = UIImageView() 97 | i.image = UIImage(named: "50x50.png") 98 | i.sizeToFit() 99 | return i 100 | }() 101 | 102 | let actorCommentLabel: UILabel = UILabel() 103 | 104 | override init(frame: CGRect) { 105 | super.init(frame: frame) 106 | addSubview(actionLabel) 107 | addSubview(optionsLabel) 108 | addSubview(posterImageView) 109 | addSubview(posterNameLabel) 110 | addSubview(posterHeadlineLabel) 111 | addSubview(posterTimeLabel) 112 | addSubview(posterCommentLabel) 113 | addSubview(contentImageView) 114 | addSubview(contentTitleLabel) 115 | addSubview(contentDomainLabel) 116 | addSubview(likeLabel) 117 | addSubview(commentLabel) 118 | addSubview(shareLabel) 119 | addSubview(actorImageView) 120 | addSubview(actorCommentLabel) 121 | backgroundColor = UIColor.white 122 | } 123 | 124 | required init?(coder aDecoder: NSCoder) { 125 | fatalError("init(coder:) has not been implemented") 126 | } 127 | 128 | func setData(_ data: FeedItemData) { 129 | actionLabel.text = data.actionText 130 | actionLabel.sizeToFit() 131 | 132 | posterNameLabel.text = data.posterName 133 | posterNameLabel.sizeToFit() 134 | 135 | posterHeadlineLabel.text = data.posterHeadline 136 | posterHeadlineLabel.sizeToFit() 137 | 138 | posterTimeLabel.text = data.posterTimestamp 139 | posterTimeLabel.sizeToFit() 140 | 141 | posterCommentLabel.text = data.posterComment 142 | 143 | contentTitleLabel.text = data.contentTitle 144 | contentTitleLabel.sizeToFit() 145 | 146 | contentDomainLabel.text = data.contentDomain 147 | contentDomainLabel.sizeToFit() 148 | 149 | actorCommentLabel.text = data.actorComment 150 | actorCommentLabel.sizeToFit() 151 | setNeedsLayout() 152 | } 153 | 154 | override func layoutSubviews() { 155 | super.layoutSubviews() 156 | 157 | let hMargin: CGFloat = 8 158 | let vMargin: CGFloat = 4 159 | 160 | optionsLabel.pin.topRight().margin(hMargin) 161 | actionLabel.pin.topLeft().margin(hMargin) 162 | 163 | posterImageView.pin.below(of: actionLabel, aligned: .left).marginTop(10) 164 | 165 | posterHeadlineLabel.pin.after(of: posterImageView, aligned: .center).marginLeft(4) 166 | posterNameLabel.pin.above(of: posterHeadlineLabel, aligned: .left).marginBottom(vMargin) 167 | posterTimeLabel.pin.below(of: posterHeadlineLabel, aligned: .left).marginTop(vMargin) 168 | 169 | posterCommentLabel.pin.below(of: posterTimeLabel).left(hMargin).marginTop(vMargin) 170 | 171 | contentImageView.pin.below(of: posterCommentLabel, aligned: .left).right().marginTop(vMargin).marginRight(hMargin) 172 | contentTitleLabel.pin.below(of: contentImageView).left().marginHorizontal(hMargin) 173 | contentDomainLabel.pin.below(of: contentTitleLabel, aligned: .left) 174 | 175 | likeLabel.pin.below(of: contentDomainLabel, aligned: .left).marginTop(vMargin) 176 | commentLabel.pin.top(to: likeLabel.edge.top).hCenter() 177 | shareLabel.pin.top(to: likeLabel.edge.top).right().marginRight(hMargin) 178 | 179 | actorImageView.pin.below(of: likeLabel, aligned: .left).marginTop(vMargin) 180 | actorCommentLabel.pin.after(of: actorImageView, aligned: .center).marginLeft(4) 181 | } 182 | 183 | override func sizeThatFits(_ size: CGSize) -> CGSize { 184 | frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 185 | layoutSubviews() 186 | return CGSize(width: size.width, height: max(actorImageView.frame.maxY, actorCommentLabel.frame.maxY) + 4) 187 | } 188 | 189 | override var intrinsicContentSize: CGSize { 190 | return sizeThatFits(CGSize(width: frame.width, height: .greatestFiniteMagnitude)) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/Stopwatch.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import Foundation 10 | 11 | struct Result { 12 | var iterationCount: Int 13 | var opsPerSecond: Double 14 | var secondsPerOperation: Double 15 | } 16 | 17 | /// A stopwatch that can record elapsed time and benchmark closures. 18 | class Stopwatch { 19 | 20 | let name: String 21 | private(set) var startTime: CFAbsoluteTime? = nil 22 | private(set) var elapsedTime: CFAbsoluteTime = 0 23 | 24 | init(name: String) { 25 | self.name = name 26 | } 27 | 28 | private func reset() { 29 | elapsedTime = 0 30 | startTime = nil 31 | } 32 | 33 | func resume() { 34 | if startTime == nil { 35 | startTime = CFAbsoluteTimeGetCurrent() 36 | } 37 | } 38 | 39 | func pause() { 40 | if let startTime = startTime { 41 | elapsedTime += CFAbsoluteTimeGetCurrent() - startTime 42 | self.startTime = nil 43 | } 44 | } 45 | 46 | /** 47 | Benchmarks the block and logs the result. 48 | The block is responsible for calling `resume()` and `pause()` on the stopwatch. 49 | */ 50 | static func benchmark(_ name: String, logResults: Bool, block: @escaping (_ stopwatch: Stopwatch) -> Void) -> Result { 51 | return autoreleasepool { () -> Result in 52 | let stopwatch = Stopwatch(name: name) 53 | 54 | // Make sure we collect enough samples. 55 | let minimumBenchmarkTime: CFAbsoluteTime = 1.0 56 | let minimumIterationCount = 5 57 | 58 | var iterationCount = 1 59 | while true { 60 | autoreleasepool { 61 | block(stopwatch) 62 | } 63 | if stopwatch.elapsedTime >= minimumBenchmarkTime && iterationCount >= minimumIterationCount { 64 | break 65 | } 66 | iterationCount += 1 67 | } 68 | stopwatch.pause() 69 | 70 | if logResults { 71 | let iterations = NSString(format: "%6d", iterationCount) 72 | let opsPerSecond = NSString(format: "%8.2f", Double(iterationCount)/stopwatch.elapsedTime) 73 | let secondsPerOperation = NSString(format: "%8.3f", stopwatch.elapsedTime / Double(iterationCount)) 74 | print("\(opsPerSecond)\tops/s\t\(secondsPerOperation)\tseconds/ops\t\(iterations)\titerations\t\(name)") 75 | } 76 | 77 | return Result(iterationCount: iterationCount, 78 | opsPerSecond: Double(iterationCount) / stopwatch.elapsedTime, 79 | secondsPerOperation: stopwatch.elapsedTime / Double(iterationCount)) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/Benchmarks/UIStackView/FeedItemUIStackView.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | 11 | /// A LinkedIn feed item that is implemented with UIStackView. 12 | @available(iOS 9.0, *) 13 | class FeedItemUIStackView: DebugStackView, DataBinder { 14 | 15 | let actionLabel: UILabel = UILabel() 16 | 17 | let optionsLabel: UILabel = { 18 | let l = UILabel() 19 | l.text = "..." 20 | l.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 21 | l.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 22 | return l 23 | }() 24 | 25 | lazy var topBar: UIStackView = { 26 | let v = DebugStackView(arrangedSubviews: [self.actionLabel, self.optionsLabel]) 27 | v.axis = .horizontal 28 | v.distribution = .fill 29 | v.alignment = .leading 30 | v.viewId = "topBar" 31 | v.backgroundColor = UIColor.blue 32 | return v 33 | }() 34 | 35 | let posterImageView: UIImageView = { 36 | let i = UIImageView() 37 | i.image = UIImage(named: "50x50.png") 38 | i.backgroundColor = UIColor.orange 39 | i.contentMode = .center 40 | i.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 41 | i.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 42 | return i 43 | }() 44 | 45 | let posterNameLabel: UILabel = { 46 | let l = UILabel() 47 | l.backgroundColor = UIColor.yellow 48 | return l 49 | }() 50 | 51 | let posterHeadlineLabel: UILabel = { 52 | let l = UILabel() 53 | l.numberOfLines = 3 54 | l.backgroundColor = UIColor.yellow 55 | return l 56 | }() 57 | 58 | let posterTimeLabel: UILabel = { 59 | let l = UILabel() 60 | l.backgroundColor = UIColor.yellow 61 | return l 62 | }() 63 | 64 | lazy var posterLabels: DebugStackView = { 65 | let v = DebugStackView(arrangedSubviews: [self.posterNameLabel, self.posterHeadlineLabel, self.posterTimeLabel]) 66 | v.axis = .vertical 67 | v.spacing = 1 68 | v.layoutMargins = UIEdgeInsets(top: 1, left: 2, bottom: 3, right: 4) 69 | v.isLayoutMarginsRelativeArrangement = true 70 | v.viewId = "posterLabels" 71 | return v 72 | }() 73 | 74 | lazy var posterCard: DebugStackView = { 75 | let v = DebugStackView(arrangedSubviews: [self.posterImageView, self.posterLabels]) 76 | v.axis = .horizontal 77 | v.alignment = .center 78 | v.viewId = "posterCard" 79 | return v 80 | }() 81 | 82 | let posterCommentLabel: UILabel = UILabel() 83 | 84 | let contentImageView: UIImageView = { 85 | let i = UIImageView() 86 | i.image = UIImage(named: "350x200.png") 87 | i.contentMode = .scaleAspectFit 88 | i.backgroundColor = UIColor.orange 89 | return i 90 | }() 91 | 92 | let contentTitleLabel: UILabel = UILabel() 93 | let contentDomainLabel: UILabel = UILabel() 94 | 95 | let likeLabel: UILabel = { 96 | let l = UILabel() 97 | l.backgroundColor = UIColor(red: 0, green: 0.9, blue: 0, alpha: 1) 98 | l.text = "Like" 99 | return l 100 | }() 101 | 102 | let commentLabel: UILabel = { 103 | let l = UILabel() 104 | l.text = "Comment" 105 | l.backgroundColor = UIColor(red: 0, green: 1.0, blue: 0, alpha: 1) 106 | l.textAlignment = .center 107 | return l 108 | }() 109 | 110 | let shareLabel: UILabel = { 111 | let l = UILabel() 112 | l.text = "Share" 113 | l.backgroundColor = UIColor(red: 0, green: 0.8, blue: 0, alpha: 1) 114 | l.textAlignment = .right 115 | return l 116 | }() 117 | 118 | lazy var actions: DebugStackView = { 119 | let v = DebugStackView(arrangedSubviews: [self.likeLabel, self.commentLabel, self.shareLabel]) 120 | v.axis = .horizontal 121 | v.distribution = .equalSpacing 122 | v.viewId = "actions" 123 | return v 124 | }() 125 | 126 | let actorImageView: UIImageView = { 127 | let i = UIImageView() 128 | i.image = UIImage(named: "50x50.png") 129 | i.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 130 | i.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 131 | return i 132 | }() 133 | 134 | let actorCommentLabel: UILabel = UILabel() 135 | 136 | lazy var comment: DebugStackView = { 137 | let v = DebugStackView(arrangedSubviews: [self.actorImageView, self.actorCommentLabel]) 138 | v.axis = .horizontal 139 | v.viewId = "comment" 140 | return v 141 | }() 142 | 143 | convenience init() { 144 | self.init(arrangedSubviews: []) 145 | axis = .vertical 146 | viewId = "ComplexStackView" 147 | let subviews = [ 148 | topBar, 149 | posterCard, 150 | posterCommentLabel, 151 | contentImageView, 152 | contentTitleLabel, 153 | contentDomainLabel, 154 | actions, 155 | comment 156 | ] 157 | for view in subviews { 158 | view.translatesAutoresizingMaskIntoConstraints = false 159 | addArrangedSubview(view) 160 | } 161 | } 162 | 163 | func setData(_ data: FeedItemData) { 164 | actionLabel.text = data.actionText 165 | posterNameLabel.text = data.posterName 166 | posterHeadlineLabel.text = data.posterHeadline 167 | posterTimeLabel.text = data.posterTimestamp 168 | posterCommentLabel.text = data.posterComment 169 | contentTitleLabel.text = data.contentTitle 170 | contentDomainLabel.text = data.contentDomain 171 | actorCommentLabel.text = data.actorComment 172 | } 173 | 174 | override func sizeThatFits(_ size: CGSize) -> CGSize { 175 | return systemLayoutSizeFitting(CGSize(width: size.width, height: 0)) 176 | } 177 | } 178 | 179 | @available(iOS 9.0, *) 180 | class DebugStackView: UIStackView { 181 | var viewId: String = "" 182 | } 183 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/FeedItemNotAutoLayoutView.swift: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 2 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 3 | // 4 | // Unless required by applicable law or agreed to in writing, 5 | // software distributed under the License is distributed on an "AS IS" BASIS, 6 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7 | 8 | import Foundation 9 | import NotAutoLayout 10 | 11 | /// A LinkedIn feed item that is implemented with NotAutoLayout code. 12 | class FeedItemNotAutoLayoutView: UIView, DataBinder { 13 | 14 | private typealias Float = NotAutoLayout.Float 15 | 16 | private let actionTitleView = ActionTitleView() 17 | var actionLabel: UILabel { 18 | return actionTitleView.actionLabel 19 | } 20 | var optionsLabel: UILabel { 21 | return actionTitleView.optionsLabel 22 | } 23 | 24 | private let posterView = PosterView() 25 | var posterImageView: UIImageView { 26 | return posterView.posterImageView 27 | } 28 | var posterNameLabel: UILabel { 29 | return posterView.posterNameLabel 30 | } 31 | var posterHeadlineLabel: UILabel { 32 | return posterView.posterHeadlineLabel 33 | } 34 | var posterTimeLabel: UILabel { 35 | return posterView.posterTimeLabel 36 | } 37 | var posterCommentLabel: UILabel { 38 | return posterView.posterCommentLabel 39 | } 40 | 41 | private let contentView = ContentView() 42 | var contentImageView: UIImageView { 43 | return contentView.contentImageView 44 | } 45 | var contentTitleLabel: UILabel { 46 | return contentView.contentTitleLabel 47 | } 48 | var contentDomainLabel: UILabel { 49 | return contentView.contentDomainLabel 50 | } 51 | var likeLabel: UILabel { 52 | return contentView.likeLabel 53 | } 54 | var commentLabel: UILabel { 55 | return contentView.commentLabel 56 | } 57 | var shareLabel: UILabel { 58 | return contentView.shareLabel 59 | } 60 | 61 | private let actorView = ActorView() 62 | var actorImageView: UIImageView { 63 | return actorView.actorImageView 64 | } 65 | var actorCommentLabel: UILabel { 66 | return actorView.actorCommentLabel 67 | } 68 | 69 | override init(frame: CGRect) { 70 | super.init(frame: frame) 71 | addSubview(actionTitleView) 72 | addSubview(posterView) 73 | addSubview(contentView) 74 | addSubview(actorView) 75 | backgroundColor = UIColor.white 76 | } 77 | 78 | required init?(coder aDecoder: NSCoder) { 79 | fatalError("init(coder:) has not been implemented") 80 | } 81 | 82 | func setData(_ data: FeedItemData) { 83 | actionLabel.text = data.actionText 84 | posterNameLabel.text = data.posterName 85 | posterHeadlineLabel.text = data.posterHeadline 86 | posterTimeLabel.text = data.posterTimestamp 87 | posterCommentLabel.text = data.posterComment 88 | contentTitleLabel.text = data.contentTitle 89 | contentDomainLabel.text = data.contentDomain 90 | actorCommentLabel.text = data.actorComment 91 | setNeedsLayout() 92 | } 93 | 94 | override func layoutSubviews() { 95 | super.layoutSubviews() 96 | 97 | nal.layout(actionTitleView, by: { $0 98 | .setTopCenter(by: { $0.topCenter }) 99 | .setWidth(by: { $0.layoutMarginsGuide.width }) 100 | .fitHeight() 101 | }) 102 | 103 | nal.layout(posterView, by: { $0 104 | .pinTopCenter(to: actionTitleView, with: { $0.bottomCenter + .init(x: 0, y: 1) }) 105 | .setWidth(by: { $0.layoutMarginsGuide.width }) 106 | .fitHeight() 107 | }) 108 | 109 | nal.layout(contentView, by: { $0 110 | .pinTopCenter(to: posterView, with: { $0.bottomCenter + .init(x: 0, y: 1) }) 111 | .setWidth(by: { $0.layoutMarginsGuide.width }) 112 | .fitHeight() 113 | }) 114 | 115 | nal.layout(actorView, by: { $0 116 | .pinTopCenter(to: contentView, with: { $0.bottomCenter + .init(x: 0, y: 1) }) 117 | .setWidth(by: { $0.layoutMarginsGuide.width }) 118 | .fitHeight() 119 | }) 120 | 121 | } 122 | 123 | override func sizeThatFits(_ size: CGSize) -> CGSize { 124 | let fittingSize = CGSize(width: size.width, height: .greatestFiniteMagnitude) 125 | let actionHeight = actionTitleView.sizeThatFits(fittingSize).height 126 | let posterHeight = posterView.sizeThatFits(fittingSize).height 127 | let contentHeight = contentView.sizeThatFits(fittingSize).height 128 | let actorHeight = actorView.sizeThatFits(fittingSize).height 129 | let totalHeight = actionHeight + posterHeight + contentHeight + actorHeight + 3 130 | return .init(width: size.width, height: totalHeight) 131 | } 132 | 133 | override var intrinsicContentSize: CGSize { 134 | return sizeThatFits(CGSize(width: frame.width, height: CGFloat.greatestFiniteMagnitude)) 135 | } 136 | 137 | } 138 | 139 | private class ActionTitleView: UIView { 140 | 141 | let actionLabel: UILabel = { 142 | let l = UILabel() 143 | return l 144 | }() 145 | 146 | let optionsLabel: UILabel = { 147 | let l = UILabel() 148 | l.text = "..." 149 | l.sizeToFit() 150 | return l 151 | }() 152 | 153 | override init(frame: CGRect) { 154 | super.init(frame: frame) 155 | addSubview(actionLabel) 156 | addSubview(optionsLabel) 157 | } 158 | 159 | required init?(coder aDecoder: NSCoder) { 160 | fatalError("init(coder:) has not been implemented") 161 | } 162 | 163 | override func layoutSubviews() { 164 | super.layoutSubviews() 165 | 166 | nal.layout(optionsLabel, by: { $0 167 | .setTopRight(by: { $0.topRight }) 168 | .fitSize() 169 | }) 170 | 171 | nal.layout(actionLabel, by: { $0 172 | .setTopLeft(by: { $0.topLeft }) 173 | .fitSize() 174 | }) 175 | 176 | } 177 | 178 | override func sizeThatFits(_ size: CGSize) -> CGSize { 179 | let optionsSize = optionsLabel.sizeThatFits(size) 180 | let actionWidth = size.width - optionsSize.width 181 | let actionHeight = actionLabel.sizeThatFits(.init(width: actionWidth, height: size.height)).height 182 | return .init(width: size.width, height: max(optionsSize.height, actionHeight)) 183 | } 184 | 185 | } 186 | 187 | private class PosterView: UIView { 188 | 189 | let posterImageView: UIImageView = { 190 | let i = UIImageView() 191 | i.image = UIImage(named: "50x50.png") 192 | i.backgroundColor = UIColor.orange 193 | i.contentMode = .scaleToFill 194 | i.sizeToFit() 195 | return i 196 | }() 197 | 198 | let posterNameLabel: UILabel = { 199 | let l = UILabel() 200 | l.backgroundColor = UIColor.yellow 201 | return l 202 | }() 203 | 204 | let posterHeadlineLabel: UILabel = { 205 | let l = UILabel() 206 | l.backgroundColor = UIColor.yellow 207 | l.numberOfLines = 3 208 | return l 209 | }() 210 | 211 | let posterTimeLabel: UILabel = { 212 | let l = UILabel() 213 | l.backgroundColor = UIColor.yellow 214 | return l 215 | }() 216 | 217 | let posterCommentLabel: UILabel = UILabel() 218 | 219 | private let imageSize = Size(width: 50, height: 50) 220 | private let imageLabelMargin = NotAutoLayout.Float(8) 221 | 222 | override init(frame: CGRect) { 223 | super.init(frame: frame) 224 | addSubview(posterImageView) 225 | addSubview(posterNameLabel) 226 | addSubview(posterHeadlineLabel) 227 | addSubview(posterTimeLabel) 228 | addSubview(posterCommentLabel) 229 | } 230 | 231 | required init?(coder aDecoder: NSCoder) { 232 | fatalError("init(coder:) has not been implemented") 233 | } 234 | 235 | override func layoutSubviews() { 236 | super.layoutSubviews() 237 | 238 | let labelsMaxSize = Size(width: Float(bounds.width) - imageSize.width, height: .greatestFiniteMagnitude) 239 | 240 | nal.layout(posterNameLabel, by: { $0 241 | .setLeft(by: { $0.left + self.imageSize.width + self.imageLabelMargin }) 242 | .setTop(by: { $0.top }) 243 | .fitSize(by: labelsMaxSize) 244 | }) 245 | 246 | nal.layout(posterHeadlineLabel, by: { $0 247 | .setLeft(by: { $0.left + self.imageSize.width + self.imageLabelMargin }) 248 | .pinTop(to: posterNameLabel, with: { $0.bottom + 1 }) 249 | .fitSize(by: labelsMaxSize) 250 | }) 251 | 252 | nal.layout(posterTimeLabel, by: { $0 253 | .setLeft(by: { $0.left + self.imageSize.width + self.imageLabelMargin }) 254 | .pinTop(to: posterHeadlineLabel, with: { $0.bottom + 1 }) 255 | .fitSize(by: labelsMaxSize) 256 | }) 257 | 258 | nal.layout(posterImageView, by: { $0 259 | .setLeft(by: { $0.left }) 260 | .setMiddle(by: { _ in Float(self.posterTimeLabel.frame.maxY - self.posterNameLabel.frame.minY) / 2 }) 261 | .setSize(to: imageSize) 262 | }) 263 | 264 | nal.layout(posterCommentLabel, by: { $0 265 | .setBottomCenter(by: { $0.bottomCenter }) 266 | .setWidth(by: { $0.width }) 267 | .fitHeight() 268 | }) 269 | 270 | } 271 | 272 | override func sizeThatFits(_ size: CGSize) -> CGSize { 273 | let labelsMaxSize = CGSize(width: size.width - imageSize.width.cgValue - imageLabelMargin.cgValue, height: .greatestFiniteMagnitude) 274 | let nameHeight = posterNameLabel.sizeThatFits(labelsMaxSize).height 275 | let headlineHeight = posterHeadlineLabel.sizeThatFits(labelsMaxSize).height 276 | let timeHeight = posterTimeLabel.sizeThatFits(labelsMaxSize).height 277 | let rightLabelsHeight = nameHeight + headlineHeight + timeHeight + 2 278 | 279 | let commentMaxSize = CGSize(width: size.width, height: .greatestFiniteMagnitude) 280 | let commentHeight = posterCommentLabel.sizeThatFits(commentMaxSize).height 281 | 282 | return .init(width: size.width, height: rightLabelsHeight + commentHeight + 1) 283 | 284 | } 285 | 286 | } 287 | 288 | private class ContentView: UIView { 289 | 290 | let contentImageView: UIImageView = { 291 | let i = UIImageView() 292 | i.image = UIImage(named: "350x200.png") 293 | i.contentMode = .scaleToFill 294 | i.sizeToFit() 295 | return i 296 | }() 297 | 298 | let contentTitleLabel: UILabel = UILabel() 299 | let contentDomainLabel: UILabel = UILabel() 300 | 301 | let likeLabel: UILabel = { 302 | let l = UILabel() 303 | l.backgroundColor = .green 304 | l.text = "Like" 305 | return l 306 | }() 307 | 308 | let commentLabel: UILabel = { 309 | let l = UILabel() 310 | l.text = "Comment" 311 | l.backgroundColor = .green 312 | l.textAlignment = .center 313 | return l 314 | }() 315 | 316 | let shareLabel: UILabel = { 317 | let l = UILabel() 318 | l.text = "Share" 319 | l.backgroundColor = .green 320 | l.textAlignment = .right 321 | return l 322 | }() 323 | 324 | private let imageAspectRatio: NotAutoLayout.Float = 350 / 200 325 | 326 | override init(frame: CGRect) { 327 | super.init(frame: frame) 328 | addSubview(contentImageView) 329 | addSubview(contentTitleLabel) 330 | addSubview(contentDomainLabel) 331 | addSubview(likeLabel) 332 | addSubview(commentLabel) 333 | addSubview(shareLabel) 334 | } 335 | 336 | required init?(coder aDecoder: NSCoder) { 337 | fatalError("init(coder:) has not been implemented") 338 | } 339 | 340 | override func layoutSubviews() { 341 | super.layoutSubviews() 342 | 343 | nal.layout(contentImageView, by: { $0 344 | .setTopCenter(by: { $0.topCenter }) 345 | .aspectFit(ratio: imageAspectRatio) 346 | }) 347 | 348 | nal.layout(contentTitleLabel, by: { $0 349 | .pinTopLeft(to: contentImageView, with: { $0.bottomLeft + .init(x: 0, y: 1) }) 350 | .fitSize() 351 | }) 352 | 353 | nal.layout(contentDomainLabel, by: { $0 354 | .pinTopLeft(to: contentTitleLabel, with: { $0.bottomLeft + .init(x: 0, y: 1) }) 355 | .fitSize() 356 | }) 357 | 358 | nal.layout(likeLabel, by: { $0 359 | .setBottomLeft(by: { $0.bottomLeft }) 360 | .fitSize() 361 | }) 362 | 363 | nal.layout(commentLabel, by: { $0 364 | .setBottomCenter(by: { $0.bottomCenter }) 365 | .fitSize() 366 | }) 367 | 368 | nal.layout(shareLabel, by: { $0 369 | .setBottomRight(by: { $0.bottomRight }) 370 | .fitSize() 371 | }) 372 | 373 | } 374 | 375 | override func sizeThatFits(_ size: CGSize) -> CGSize { 376 | let imageHeight = size.width / imageAspectRatio.cgValue 377 | 378 | let labelMaxSize = CGSize(width: size.width, height: .greatestFiniteMagnitude) 379 | let titleHeight = contentTitleLabel.sizeThatFits(labelMaxSize).height 380 | let domainHeight = contentTitleLabel.sizeThatFits(labelMaxSize).height 381 | 382 | let likeHeight = likeLabel.sizeThatFits(labelMaxSize).height 383 | 384 | let totalHeight = imageHeight + titleHeight + domainHeight + likeHeight + 3 385 | 386 | return .init(width: size.width, height: totalHeight) 387 | } 388 | 389 | } 390 | 391 | private class ActorView: UIView { 392 | 393 | let actorImageView: UIImageView = { 394 | let i = UIImageView() 395 | i.image = UIImage(named: "50x50.png") 396 | return i 397 | }() 398 | 399 | let actorCommentLabel: UILabel = UILabel() 400 | 401 | private let imageSize = Size(width: 50, height: 50) 402 | 403 | override init(frame: CGRect) { 404 | super.init(frame: frame) 405 | addSubview(actorImageView) 406 | addSubview(actorCommentLabel) 407 | } 408 | 409 | required init?(coder aDecoder: NSCoder) { 410 | fatalError("init(coder:) has not been implemented") 411 | } 412 | 413 | override func layoutSubviews() { 414 | super.layoutSubviews() 415 | 416 | nal.layout(actorImageView, by: { $0 417 | .setMiddleLeft(by: { $0.middleLeft }) 418 | .setSize(to: self.imageSize) 419 | }) 420 | 421 | nal.layout(actorCommentLabel, by: { $0 422 | .pinMiddleLeft(to: actorImageView, with: { $0.middleRight + .init(x: 8, y: 0) }) 423 | .setRight(by: { $0.right }) 424 | .fitHeight() 425 | }) 426 | 427 | } 428 | 429 | override func sizeThatFits(_ size: CGSize) -> CGSize { 430 | let labelMaxSize = CGSize(width: size.width - imageSize.width.cgValue - 8, height: .greatestFiniteMagnitude) 431 | let labelHeight = actorCommentLabel.sizeThatFits(labelMaxSize).height 432 | let maxHeight = max(labelHeight, imageSize.height.cgValue) 433 | return .init(width: size.width, height: maxHeight) 434 | } 435 | 436 | } 437 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/FeedItemTextureNode.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import UIKit 10 | import AsyncDisplayKit 11 | 12 | /// A LinkedIn feed item that is implemented with Texture. 13 | class FeedItemTextureNode: ASCellNode { 14 | let topBarView = TopBarNode() 15 | let miniProfileView = MiniProfileNode() 16 | let miniContentView = MiniContentNode() 17 | let socialActionsView = SocialActionsNode() 18 | let commentView = CommentNode() 19 | 20 | init(data: FeedItemData) { 21 | topBarView.actionLabel.attributedText = data.actionText.makeAttributedString() 22 | miniProfileView.posterNameLabel.attributedText = data.posterName.makeAttributedString() 23 | miniProfileView.posterHeadlineLabel.attributedText = data.posterHeadline.makeAttributedString() 24 | miniProfileView.posterTimeLabel.attributedText = data.posterTimestamp.makeAttributedString() 25 | miniProfileView.posterCommentLabel.attributedText = data.posterComment.makeAttributedString() 26 | miniContentView.contentTitleLabel.attributedText = data.contentTitle.makeAttributedString() 27 | miniContentView.contentDomainLabel.attributedText = data.contentDomain.makeAttributedString() 28 | commentView.actorCommentLabel.attributedText = data.actorComment.makeAttributedString() 29 | super.init() 30 | [topBarView, miniProfileView, miniContentView, socialActionsView, commentView].forEach { addSubnode($0) } 31 | } 32 | 33 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 34 | let mainStack = ASStackLayoutSpec(direction: .vertical, 35 | spacing: 4.0, 36 | justifyContent: .spaceBetween, 37 | alignItems: .stretch, 38 | children: [topBarView, miniProfileView, miniContentView, socialActionsView, commentView]) 39 | return mainStack 40 | } 41 | } 42 | 43 | class CommentNode: ASDisplayNode { 44 | let actorImageView: ASImageNode = { 45 | let i = ASImageNode() 46 | i.image = UIImage(named: "50x50.png") 47 | return i 48 | }() 49 | 50 | let actorCommentLabel: ASTextNode = ASTextNode() 51 | 52 | override init() { 53 | super.init() 54 | [actorImageView, actorCommentLabel].forEach { addSubnode($0) } 55 | } 56 | 57 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 58 | actorCommentLabel.style.flexGrow = 1.0 59 | let mainStack = ASStackLayoutSpec(direction: .horizontal, 60 | spacing: 4.0, 61 | justifyContent: .spaceBetween, 62 | alignItems: .center, 63 | children: [actorImageView, actorCommentLabel]) 64 | return mainStack 65 | } 66 | } 67 | 68 | class MiniContentNode: ASDisplayNode { 69 | let contentImageView: ASImageNode = { 70 | let i = ASImageNode() 71 | i.image = UIImage(named: "350x200.png") 72 | i.contentMode = .scaleAspectFit 73 | i.backgroundColor = UIColor.orange 74 | return i 75 | }() 76 | 77 | let contentTitleLabel: ASTextNode = ASTextNode() 78 | let contentDomainLabel: ASTextNode = ASTextNode() 79 | 80 | override init() { 81 | super.init() 82 | [contentImageView, contentTitleLabel, contentDomainLabel].forEach { addSubnode($0) } 83 | } 84 | 85 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 86 | let mainStack = ASStackLayoutSpec(direction: .vertical, 87 | spacing: 0.0, 88 | justifyContent: .spaceBetween, 89 | alignItems: .stretch, 90 | children: [contentImageView, contentTitleLabel, contentDomainLabel]) 91 | return mainStack 92 | } 93 | } 94 | 95 | class MiniProfileNode: ASDisplayNode { 96 | let posterImageView: ASImageNode = { 97 | let i = ASImageNode() 98 | i.image = UIImage(named: "50x50.png") 99 | return i 100 | }() 101 | 102 | let posterNameLabel: ASTextNode = { 103 | let l = ASTextNode() 104 | l.backgroundColor = UIColor.yellow 105 | return l 106 | }() 107 | 108 | let posterHeadlineLabel: ASTextNode = { 109 | let l = ASTextNode() 110 | l.maximumNumberOfLines = 3 111 | l.backgroundColor = UIColor.yellow 112 | return l 113 | }() 114 | 115 | let posterTimeLabel: ASTextNode = { 116 | let l = ASTextNode() 117 | l.backgroundColor = UIColor.yellow 118 | return l 119 | }() 120 | 121 | let posterCommentLabel: ASTextNode = ASTextNode() 122 | 123 | override init() { 124 | super.init() 125 | [posterNameLabel, posterHeadlineLabel, posterTimeLabel, posterImageView, posterCommentLabel].forEach { addSubnode($0) } 126 | } 127 | 128 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 129 | let textStack = ASStackLayoutSpec(direction: .vertical, spacing: 2.0, justifyContent: .spaceBetween, alignItems: .stretch, children: [posterNameLabel, posterHeadlineLabel, posterTimeLabel]) 130 | textStack.style.flexGrow = 1.0 131 | 132 | let imageTextStack = ASStackLayoutSpec(direction: .horizontal, 133 | spacing: 4.0, 134 | justifyContent: .spaceBetween, 135 | alignItems: .center, 136 | children: [posterImageView, 137 | textStack]) 138 | 139 | let mainStack = ASStackLayoutSpec(direction: .vertical, 140 | spacing: 3.0, 141 | justifyContent: .start, 142 | alignItems: .stretch, 143 | children: [imageTextStack, 144 | posterCommentLabel]) 145 | 146 | let insetStack = ASInsetLayoutSpec(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 4.0), 147 | child: mainStack) 148 | 149 | return insetStack 150 | } 151 | } 152 | 153 | class SocialActionsNode: ASDisplayNode { 154 | let likeLabel: ASTextNode = { 155 | let l = ASTextNode() 156 | l.attributedText = "Like".makeAttributedString() 157 | l.backgroundColor = .green 158 | return l 159 | }() 160 | 161 | let commentLabel: ASTextNode = { 162 | let l = ASTextNode() 163 | l.attributedText = "Comment".makeAttributedString() 164 | l.backgroundColor = .green 165 | return l 166 | }() 167 | 168 | let shareLabel: ASTextNode = { 169 | let l = ASTextNode() 170 | l.attributedText = "Share".makeAttributedString() 171 | l.backgroundColor = .green 172 | return l 173 | }() 174 | 175 | override init() { 176 | super.init() 177 | [likeLabel, commentLabel, shareLabel].forEach { addSubnode($0) } 178 | } 179 | 180 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 181 | let mainStack = ASStackLayoutSpec(direction: .horizontal, 182 | spacing: 0.0, 183 | justifyContent: .spaceBetween, 184 | alignItems: .start, 185 | children: [likeLabel, commentLabel, shareLabel]) 186 | return mainStack 187 | } 188 | } 189 | 190 | class TopBarNode: ASDisplayNode { 191 | let actionLabel: ASTextNode = ASTextNode() 192 | 193 | let optionsLabel: ASTextNode = { 194 | let l = ASTextNode() 195 | l.attributedText = "...".makeAttributedString() 196 | return l 197 | }() 198 | 199 | override init() { 200 | super.init() 201 | [actionLabel, optionsLabel].forEach { addSubnode($0) } 202 | } 203 | 204 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 205 | let mainStack = ASStackLayoutSpec(direction: .horizontal, 206 | spacing: 0.0, 207 | justifyContent: .start, 208 | alignItems: .start, 209 | children: [actionLabel, optionsLabel]) 210 | actionLabel.style.flexGrow = 1.0 211 | return mainStack 212 | } 213 | } 214 | 215 | private extension String { 216 | func makeAttributedString() -> NSAttributedString { 217 | return NSAttributedString(string: self, attributes: [.font: UIFont.systemFont(ofSize: 17.0)]) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/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 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /LayoutFrameworkBenchmark/TextureCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2016 LinkedIn Corp. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | // 5 | // Unless required by applicable law or agreed to in writing, 6 | // software distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | import AsyncDisplayKit 10 | 11 | class TextureCollectionViewController: ASDKViewController, ASCollectionDataSource, ASCollectionDelegate { 12 | 13 | let collectionNode: ASCollectionNode 14 | let reuseIdentifier = "cell" 15 | let data: [FeedItemData] 16 | let flowLayout: UICollectionViewFlowLayout 17 | 18 | init(data: [FeedItemData]) { 19 | self.data = data 20 | self.flowLayout = UICollectionViewFlowLayout() 21 | self.collectionNode = ASCollectionNode(collectionViewLayout: self.flowLayout) 22 | super.init(node: collectionNode) 23 | collectionNode.delegate = self 24 | collectionNode.dataSource = self 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | collectionNode.reloadData() 34 | } 35 | 36 | func numberOfSections(in collectionNode: ASCollectionNode) -> Int { 37 | return 1 38 | } 39 | 40 | func collectionNode(_ collectionNode: ASCollectionNode, numberOfItemsInSection section: Int) -> Int { 41 | return data.count 42 | } 43 | 44 | func collectionNode(_ collectionNode: ASCollectionNode, nodeForItemAt indexPath: IndexPath) -> ASCellNode { 45 | let cell = FeedItemTextureNode(data: data[indexPath.row]) 46 | 47 | return cell 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://cdn.cocoapods.org/' 2 | 3 | use_frameworks! 4 | 5 | project 'LayoutFrameworkBenchmark.xcodeproj' 6 | 7 | platform :ios, '10.0' 8 | 9 | inhibit_all_warnings! 10 | 11 | target 'LayoutFrameworkBenchmark' do 12 | pod 'FlexLayout' 13 | pod 'LayoutKit', :git => 'https://github.com/LinkedInAttic/LayoutKit.git' 14 | pod 'NotAutoLayout' 15 | pod 'NKFrameLayoutKit' 16 | pod 'PinLayout' 17 | pod 'Texture' 18 | 19 | pod 'Reveal-SDK' 20 | end 21 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FlexLayout (1.3.23) 3 | - LayoutKit (10.1.0) 4 | - NKFrameLayoutKit (2.5) 5 | - NotAutoLayout (3.2.0) 6 | - PINCache (3.0.3): 7 | - PINCache/Arc-exception-safe (= 3.0.3) 8 | - PINCache/Core (= 3.0.3) 9 | - PINCache/Arc-exception-safe (3.0.3): 10 | - PINCache/Core 11 | - PINCache/Core (3.0.3): 12 | - PINOperation (~> 1.2.1) 13 | - PinLayout (1.10.0) 14 | - PINOperation (1.2.1) 15 | - PINRemoteImage/Core (3.0.3): 16 | - PINOperation 17 | - PINRemoteImage/iOS (3.0.3): 18 | - PINRemoteImage/Core 19 | - PINRemoteImage/PINCache (3.0.3): 20 | - PINCache (~> 3.0.3) 21 | - PINRemoteImage/Core 22 | - Reveal-SDK (33) 23 | - Texture (3.1.0): 24 | - Texture/AssetsLibrary (= 3.1.0) 25 | - Texture/Core (= 3.1.0) 26 | - Texture/MapKit (= 3.1.0) 27 | - Texture/Photos (= 3.1.0) 28 | - Texture/PINRemoteImage (= 3.1.0) 29 | - Texture/Video (= 3.1.0) 30 | - Texture/AssetsLibrary (3.1.0): 31 | - Texture/Core 32 | - Texture/Core (3.1.0) 33 | - Texture/MapKit (3.1.0): 34 | - Texture/Core 35 | - Texture/Photos (3.1.0): 36 | - Texture/Core 37 | - Texture/PINRemoteImage (3.1.0): 38 | - PINRemoteImage/iOS (~> 3.0.0) 39 | - PINRemoteImage/PINCache 40 | - Texture/Core 41 | - Texture/Video (3.1.0): 42 | - Texture/Core 43 | 44 | DEPENDENCIES: 45 | - FlexLayout 46 | - LayoutKit (from `https://github.com/LinkedInAttic/LayoutKit.git`) 47 | - NKFrameLayoutKit 48 | - NotAutoLayout 49 | - PinLayout 50 | - Reveal-SDK 51 | - Texture 52 | 53 | SPEC REPOS: 54 | trunk: 55 | - FlexLayout 56 | - NKFrameLayoutKit 57 | - NotAutoLayout 58 | - PINCache 59 | - PinLayout 60 | - PINOperation 61 | - PINRemoteImage 62 | - Reveal-SDK 63 | - Texture 64 | 65 | EXTERNAL SOURCES: 66 | LayoutKit: 67 | :git: https://github.com/LinkedInAttic/LayoutKit.git 68 | 69 | CHECKOUT OPTIONS: 70 | LayoutKit: 71 | :commit: 1f1b067b6b11a0779ea6b6ccd976585a1d7bb79b 72 | :git: https://github.com/LinkedInAttic/LayoutKit.git 73 | 74 | SPEC CHECKSUMS: 75 | FlexLayout: 5b44c0538e7eaa2110e3160dfdfb74611676fb91 76 | LayoutKit: 66478bb3af5ce4519a056d9465bc4c7585d2350b 77 | NKFrameLayoutKit: 8485fd4fb04c2c99e38e4dfd4b65a99ce10689b9 78 | NotAutoLayout: 4723ad82a21a7c6c60c823fff7f7c29467811b92 79 | PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 80 | PinLayout: e50e9a748b632905fca6e67043ea4b05d6c92186 81 | PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 82 | PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 83 | Reveal-SDK: effba1c940b8337195563c425a6b5862ec875caa 84 | Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 85 | 86 | PODFILE CHECKSUM: dca07728abcbc052a30cef591b8cd349297c894b 87 | 88 | COCOAPODS: 1.11.2 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | FlexLayout 3 |

4 | 5 |

Layout Framework Benchmark

6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 |
14 | 15 | Benchmark the performances of various Swift layout frameworks. 16 | 17 | ### Requirements 18 | * iOS 8.0+ 19 | * Xcode 8.0+ 20 | * Swift 3.0+ 21 | 22 | # History 23 | This project is a spin-off of the excellent [LayoutKit benchmark](https://github.com/linkedin/LayoutKit). The benchmark has been extracted to add other iOS layout frameworks and to compare them. 24 | 25 |
26 | 27 | # Why? 28 | Choosing the right layout framework for your project is an important decision. The frameworks API is quite important, but its performance is also important. To help you with that decision, this benchmark compare different layout frameworks. 29 | 30 |
31 | 32 | ## Benchmark charts 33 | 34 | 35 | ##### General comparison 36 | 37 | This chart display a general comparison between device performance using each layout frameworks. 38 | It displays the performance when layouting UICollectionView cells. This graph shows performance when layouting 100 UICollectionView cells. 39 | 40 | The **Y axis** indicates the **number of seconds** to render all cells. 41 | 42 | 43 |
44 | 45 | 46 | # Layout frameworks 47 | 48 | **The benchmark currently includes the following layout frameworks:** 49 | (ordered alphabetically and use the framework GitHub's description): 50 | 51 | * **Auto layout** 52 | Apple's auto layout constraints. 53 | [Auto layout benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/AutoLayout/FeedItemAutoLayoutView.swift) 54 | 55 | * [**FlexLayout**](https://github.com/layoutBox/FlexLayout) 56 | FlexLayout adds a nice Swift interface to the highly optimized [Yoga](https://github.com/facebook/yoga) flexbox implementation. Concise, intuitive & chainable syntax. 57 | [FlexLayout benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/FlexLayout/FeedItemFlexLayoutView.swift) 58 | 59 | * [**LayoutKit**](https://github.com/linkedin/LayoutKit) 60 | LayoutKit is a fast view layout library for iOS, macOS, and tvOS. 61 | [LayoutKit benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/tree/master/LayoutFrameworkBenchmark/Benchmarks/LayoutKit) 62 | 63 | * **Manual layout** 64 | Layout is done by setting UIView's frame property directly. This implementation comes directly from the LayoutKit benchmark. 65 | [Manual layout benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/ManualLayout/FeedItemManualView.swift) 66 | 67 | * [**NKFrameLayoutKit**](https://github.com/kennic/NKFrameLayoutKit) 68 | NKFrameLayoutKit is a fast and easy to use layout library 69 | [NKFrameLayoutKit benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/NKFrameLayoutKit/NKFrameLayoutKitView.swift) 70 | 71 | * [**NotAutoLayout**](https://github.com/el-hoshino/NotAutoLayout) 72 | Layout your views without Auto Layout constraints, in a much more swifty way. 73 | [NotAutoLayout benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/NotAutoLayout/FeedItemNotAutoLayoutView.swift) 74 | 75 | * [**PinLayout**](https://github.com/layoutBox/PinLayout) 76 | Fast Swift UIViews layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainable. 77 | [PinLayout benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/PinLayout/FeedItemPinLayoutView.swift) 78 | 79 | * [**Texture**](https://github.com/TextureGroup/Texture) 80 | Optimize your app by making user interfaces thread safe, which means that you will be able to shift all expensive views into background threads. 81 | [Texture benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/FeedItemTextureNode.swift) 82 | 83 | * **UIStackViews** 84 | Apple's UIStackViews. 85 | [UIStackViews benchmark's source code](https://github.com/layoutBox/LayoutFrameworkBenchmark/blob/master/LayoutFrameworkBenchmark/Benchmarks/UIStackView/FeedItemUIStackView.swift) 86 | 87 | * [**Yoga**](https://github.com/facebook/yoga) 88 | Yoga's performance hasn't been tested directly, but FlexLayout has. FlexLayout is a light Swift interface for Yoga. 89 | 90 | 91 | :pushpin: Anyone who would like to integrate any other layout frameworks to this GitHub project is welcome. 92 | 93 |
94 | 95 | ## Benchmark details 96 | The benchmark layout UICollectionView cells in multiple pass, each pass contains more cells than the previous one. 97 | 98 | ## Benchmark cell's layout 99 | Here are the benchmark rendering results to compare visual results: 100 | 101 | * [LayoutKit rendering result](docs_markdown/benchmark_result_LayoutKit.png) 102 | * [Manual layout rendering result](docs_markdown/benchmark_result_ManualLayout.png) 103 | * [NKFrameLayoutKit rendering result](docs_markdown/benchmark_result_NKFrameLayoutKit.png) 104 | * [PinLayout rendering result](docs_markdown/benchmark_result_PinLayout.png) 105 | * [Texture rendering result](docs_markdown/benchmark_result_Texture.png) 106 | 107 | :pushpin: Some work would be required to adjust the layout so that they all match perfectly. 108 | 109 |
110 | 111 | ## Benchmark data 112 | You can see the benchmark's data and charts in this **[Google Spreadsheet Document](https://docs.google.com/spreadsheets/d/1sUNdGWBM-d_W13yC7VcfkRXC3owCVsnIublnfW-4xn4/edit#gid=1032991425)**. 113 | 114 |
115 | 116 | ##### Details for different devices 117 | 118 | The **X axis** in following charts indicates the **number of cells** contained for each pass. The **Y axis** indicates the **number of seconds** to render all cells from one pass. 119 | 120 | 121 |
122 | 123 | 124 | 125 |
126 | 127 | 128 |
129 | 130 | 131 |
132 | 133 | 134 |
135 | 136 | 137 |
138 | 139 | 140 | ## Project's TODO list 141 | 142 | * Create a reference layout and update layout codes to match that reference. 143 | * Display benchmark charts inside the app and being to able to export them. 144 | * Export benchmark data to a spreadsheet. 145 | * Add more layout frameworks. 146 | * OSX support. 147 | * tvOS support. 148 | * ... 149 | 150 |
151 | 152 | ## Contributing, comments, ideas, suggestions, issues, .... 153 | For any **comments**, **ideas**, **suggestions**, simply open an [issue](https://github.com/layoutBox/LayoutFrameworkBenchmark/issues). 154 | 155 | If you'd like to contribute by adding other layout framework, you're welcomed! 156 | 157 |
158 | 159 | ## Adding another layout framework 160 | 161 | The process is currently tedious... 162 | 163 | * Add an implementation of the reference view. 164 | * Run on all reference devices (or at least the two latest generation) in **Release mode**. 165 | * Select the new layout from the Benchmark App. 166 | * Update the XLSX document. 167 | * Add a new chart 168 | * Save the chart using "Save as Picture..." 169 | * Update the README.md 170 | * Create a pull request. 171 | 172 | ## License 173 | BSD 3-Clause License 174 | -------------------------------------------------------------------------------- /Tests/CellLayoutSpec.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Luc Dion 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | import Quick 21 | import Nimble 22 | @testable import LayoutFrameworkBenchmark 23 | 24 | class CellLayoutSpec: QuickSpec { 25 | override func spec() { 26 | var viewController: UIViewController! 27 | let data = FeedItemData.generate(count: 1)[0] 28 | 29 | beforeSuite { 30 | } 31 | 32 | beforeEach { 33 | viewController = UIViewController() 34 | viewController.view = UIView() 35 | } 36 | 37 | describe("layout all cells with a width of 600 pixels") { 38 | it("test LayoutKit") { 39 | let feedItemView = FeedItemLayoutKitView(frame: .zero) 40 | feedItemView.setData(data) 41 | feedItemView.layoutIfNeeded() 42 | 43 | let size = feedItemView.sizeThatFits(CGSize(width: 600, height: CGFloat.greatestFiniteMagnitude)) 44 | feedItemView.frame = CGRect(origin: .zero, size: size) 45 | 46 | expect(feedItemView.frame).to(equal(CGRect(x: 0.0, y: 0.0, width: 600, height: 600.0))) 47 | } 48 | 49 | it("test PinLayout") { 50 | let feedItemView = FeedItemPinLayoutView(frame: .zero) 51 | let size = feedItemView.sizeThatFits(CGSize(width: 600, height: CGFloat.greatestFiniteMagnitude)) 52 | 53 | feedItemView.frame = CGRect(origin: .zero, size: size) 54 | expect(feedItemView.frame).to(equal(CGRect(x: 0.0, y: 0.0, width: 600, height: 600.0))) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs_markdown/benchmark_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_comparison.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_comparison_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_comparison_all.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_comparison_all_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_comparison_all_small.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_iphone5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_iphone5s.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_iphone6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_iphone6.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_iphone6s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_iphone6s.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_iphone7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_iphone7.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_iphonex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_iphonex.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_iphonexs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_iphonexs.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_result_LayoutKit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_result_LayoutKit.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_result_ManualLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_result_ManualLayout.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_result_NKFrameLayoutKit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_result_NKFrameLayoutKit.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_result_PinLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_result_PinLayout.png -------------------------------------------------------------------------------- /docs_markdown/benchmark_result_Texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/benchmark_result_Texture.png -------------------------------------------------------------------------------- /docs_markdown/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layoutBox/LayoutFrameworkBenchmark/98505961170431440778da9c2f5dd128eb203240/docs_markdown/images/logo.png --------------------------------------------------------------------------------