├── .github
├── CODEOWNERS
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ └── SharkStackKit.xcscheme
├── Assets
├── example-1.png
├── example-2.png
├── hstack-example.png
├── margin-example.png
├── sizing-example.png
├── spacing-example.png
├── stack-kit-logo.png
├── stackception-example.png
├── stackwithinastack.png
└── vstack-example.png
├── Example
├── Example.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Example.xcscheme
└── Example
│ ├── Flow
│ ├── App Delegate
│ │ └── AppDelegate.swift
│ └── Feed
│ │ ├── Cells
│ │ ├── Feed Item
│ │ │ ├── FeedItemCell.swift
│ │ │ └── FeedItemViewModel.swift
│ │ ├── Header
│ │ │ ├── HeaderCell.swift
│ │ │ └── HeaderCellViewModel.swift
│ │ └── Order
│ │ │ ├── OrderCell.swift
│ │ │ └── OrderCellViewModel.swift
│ │ └── Controller
│ │ ├── FeedViewController.swift
│ │ └── FeedViewModel.swift
│ ├── Helpers
│ └── CollectionExtension.swift
│ ├── Models
│ ├── FeedModel.swift
│ ├── OrderModel.swift
│ └── ProductModel.swift
│ ├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Assets
│ │ │ ├── Contents.json
│ │ │ ├── button-icon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── button-icon.pdf
│ │ │ ├── more-icon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── more-icon.pdf
│ │ │ └── search-icon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── search-icon.pdf
│ │ ├── Contents.json
│ │ ├── Feed
│ │ │ ├── Contents.json
│ │ │ ├── flex.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── reset.jpg
│ │ │ ├── functional-female.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── IMG_5650.jpg
│ │ │ ├── functional-male.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── IMG_5651.jpg
│ │ │ ├── must-have.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── IMG_5663.jpg
│ │ │ ├── raise.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── IMG_5662.jpg
│ │ │ └── reset.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── IMG_5661.jpg
│ │ └── Products
│ │ │ ├── Contents.json
│ │ │ ├── hoodie.imageset
│ │ │ ├── Contents.json
│ │ │ └── hoodie.jpg
│ │ │ ├── shorts.imageset
│ │ │ ├── Contents.json
│ │ │ └── shorts.jpg
│ │ │ ├── top.imageset
│ │ │ ├── Contents.json
│ │ │ └── top.jpg
│ │ │ └── tshirt.imageset
│ │ │ ├── Contents.json
│ │ │ └── tshirt.jpg
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Constants.swift
│ └── Info.plist
│ └── UI Components
│ ├── Order Image Gallery
│ ├── OrderGalleryImage.swift
│ └── OrderGalleryImageView.swift
│ └── Tag View
│ └── TagView.swift
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── StackKit
│ ├── Stacks
│ ├── Helpers
│ │ ├── Spacer.swift
│ │ └── UIViewHelpers.swift
│ ├── Scroll
│ │ ├── ScrollStacks.swift
│ │ └── ScrollViewBuilder.swift
│ └── Stacks
│ │ ├── StackBuilder.swift
│ │ └── Stacks.swift
│ └── UIViewBuilder.swift
├── StackKit.podspec
└── Tests
├── LinuxMain.swift
└── StackKitTests
├── StackKitTests.swift
└── XCTestManifests.swift
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This is a comment.
2 | # Each line is a file pattern followed by one or more owners.
3 |
4 | # These owners will be the default owners for everything in
5 | # the repo. Unless a later match takes precedence,
6 | # @global-owner1 and @global-owner2 will be requested for
7 | # review when someone opens a pull request.
8 | * @russshark @markdarling @TheiOSDude @Christopher1613 @vrutings @bolshedvorsky
9 |
10 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## How to contribute to StackKit
2 |
3 | - Write Code.
4 | - Merge Requests should go to MASTER.
5 |
6 | #### **Automation**
7 |
8 | On each Pull Request, Github Actions will;
9 |
10 | - Check it builds.
11 | - Check the unit tests run.
12 |
13 | Questions; Ask any of the Gymshark iOS Engineering Team.
14 | Twitter: @TheiOSDude, @vrutin, @russelltwarwick
15 |
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Expected Behavior
4 |
5 |
6 | ## Current Behavior
7 |
8 |
9 | ## Possible Solution
10 |
11 |
12 | ## Steps to Reproduce
13 |
14 |
15 | 1.
16 | 2.
17 | 3.
18 | 4.
19 |
20 | ## Context (Environment)
21 |
22 |
23 |
24 |
25 |
26 | ## Detailed Description
27 |
28 |
29 | ## Possible Implementation
30 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request Template
2 |
3 | ## Description
4 |
5 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
6 |
7 | Fixes # (issue)
8 |
9 | ## Type of change
10 |
11 | Please delete options that are not relevant.
12 |
13 | - [ ] Bug fix (non-breaking change which fixes an issue)
14 | - [ ] New feature (non-breaking change which adds functionality)
15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
16 | - [ ] This change requires a documentation update
17 |
18 | ## How Has This Been Tested?
19 |
20 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
21 |
22 | - [ ] Unit Tests
23 |
24 | ## Checklist:
25 |
26 | - [ ] My code follows the style guidelines of this project
27 | - [ ] I have performed a self-review of my own code
28 | - [ ] I have commented my code, particularly in hard-to-understand areas
29 | - [ ] I have made corresponding changes to the documentation
30 | - [ ] My changes generate no new warnings
31 | - [ ] I have added tests that prove my fix is effective or that my feature works
32 | - [ ] New and existing unit tests pass locally with my changes
33 | - [ ] Any dependent changes have been merged and published in downstream modules
34 | - [ ] I have checked my code and corrected any misspellings
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Build for iOS
17 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build-for-testing -scheme StackKit -destination "platform=iOS Simulator,OS=latest,name=iPhone 12" | xcpretty
18 | - name: Run iOS tests
19 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild test-without-building -scheme StackKit -destination "platform=iOS Simulator,OS=latest,name=iPhone 12" | xcpretty
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SharkStackKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Assets/example-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/example-1.png
--------------------------------------------------------------------------------
/Assets/example-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/example-2.png
--------------------------------------------------------------------------------
/Assets/hstack-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/hstack-example.png
--------------------------------------------------------------------------------
/Assets/margin-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/margin-example.png
--------------------------------------------------------------------------------
/Assets/sizing-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/sizing-example.png
--------------------------------------------------------------------------------
/Assets/spacing-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/spacing-example.png
--------------------------------------------------------------------------------
/Assets/stack-kit-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/stack-kit-logo.png
--------------------------------------------------------------------------------
/Assets/stackception-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/stackception-example.png
--------------------------------------------------------------------------------
/Assets/stackwithinastack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/stackwithinastack.png
--------------------------------------------------------------------------------
/Assets/vstack-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Assets/vstack-example.png
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | BC4F374726A7540100B41D71 /* StackKit in Frameworks */ = {isa = PBXBuildFile; productRef = BC4F374626A7540100B41D71 /* StackKit */; };
11 | BC5020E72645EAE500E7E234 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC5020E62645EAE500E7E234 /* Assets.xcassets */; };
12 | BC5020EA2645EAE500E7E234 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC5020E82645EAE500E7E234 /* LaunchScreen.storyboard */; };
13 | BC6E946226A471AA00D91640 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6E946126A471AA00D91640 /* TagView.swift */; };
14 | BC6E946426A5B41A00D91640 /* OrderGalleryImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6E946326A5B41A00D91640 /* OrderGalleryImageView.swift */; };
15 | BC6E946726A5CECF00D91640 /* OrderGalleryImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6E946626A5CECF00D91640 /* OrderGalleryImage.swift */; };
16 | BC6E947226A6036D00D91640 /* HeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6E947126A6036D00D91640 /* HeaderCell.swift */; };
17 | BC6E947426A604B200D91640 /* HeaderCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6E947326A604B200D91640 /* HeaderCellViewModel.swift */; };
18 | BCAEB916269DCCFD00DB84A8 /* FeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB915269DCCFD00DB84A8 /* FeedViewController.swift */; };
19 | BCAEB919269DCD6D00DB84A8 /* FeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB918269DCD6D00DB84A8 /* FeedViewModel.swift */; };
20 | BCAEB91B269DCD7D00DB84A8 /* FeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB91A269DCD7D00DB84A8 /* FeedModel.swift */; };
21 | BCAEB920269DD14300DB84A8 /* OrderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB91F269DD14300DB84A8 /* OrderModel.swift */; };
22 | BCAEB922269DD1C200DB84A8 /* ProductModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB921269DD1C200DB84A8 /* ProductModel.swift */; };
23 | BCAEB926269DD6E000DB84A8 /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB925269DD6E000DB84A8 /* FeedItemCell.swift */; };
24 | BCAEB928269DD6EB00DB84A8 /* FeedItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB927269DD6EB00DB84A8 /* FeedItemViewModel.swift */; };
25 | BCAEB92C269DD87800DB84A8 /* OrderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB92B269DD87800DB84A8 /* OrderCell.swift */; };
26 | BCAEB92E269DD88B00DB84A8 /* OrderCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB92D269DD88B00DB84A8 /* OrderCellViewModel.swift */; };
27 | BCAEB931269DDE3700DB84A8 /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCAEB930269DDE3700DB84A8 /* CollectionExtension.swift */; };
28 | BCB97F4A268234D700AE0968 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB97F42268234D700AE0968 /* AppDelegate.swift */; };
29 | BCD6DCE626966F0B00D2E481 /* SharkUtils in Frameworks */ = {isa = PBXBuildFile; productRef = BCD6DCE526966F0B00D2E481 /* SharkUtils */; };
30 | BCE7C57B26489DA200515A05 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7C57A26489DA200515A05 /* Constants.swift */; };
31 | /* End PBXBuildFile section */
32 |
33 | /* Begin PBXFileReference section */
34 | BC4F374526A753F900B41D71 /* StackKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = StackKit; path = ..; sourceTree = ""; };
35 | BC5020DA2645EAE400E7E234 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
36 | BC5020E62645EAE500E7E234 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
37 | BC5020E92645EAE500E7E234 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
38 | BC5020EB2645EAE500E7E234 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
39 | BC6E946126A471AA00D91640 /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = ""; };
40 | BC6E946326A5B41A00D91640 /* OrderGalleryImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderGalleryImageView.swift; sourceTree = ""; };
41 | BC6E946626A5CECF00D91640 /* OrderGalleryImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderGalleryImage.swift; sourceTree = ""; };
42 | BC6E947126A6036D00D91640 /* HeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderCell.swift; sourceTree = ""; };
43 | BC6E947326A604B200D91640 /* HeaderCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderCellViewModel.swift; sourceTree = ""; };
44 | BCAEB915269DCCFD00DB84A8 /* FeedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewController.swift; sourceTree = ""; };
45 | BCAEB918269DCD6D00DB84A8 /* FeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModel.swift; sourceTree = ""; };
46 | BCAEB91A269DCD7D00DB84A8 /* FeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedModel.swift; sourceTree = ""; };
47 | BCAEB91F269DD14300DB84A8 /* OrderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderModel.swift; sourceTree = ""; };
48 | BCAEB921269DD1C200DB84A8 /* ProductModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductModel.swift; sourceTree = ""; };
49 | BCAEB925269DD6E000DB84A8 /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; };
50 | BCAEB927269DD6EB00DB84A8 /* FeedItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemViewModel.swift; sourceTree = ""; };
51 | BCAEB92B269DD87800DB84A8 /* OrderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCell.swift; sourceTree = ""; };
52 | BCAEB92D269DD88B00DB84A8 /* OrderCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCellViewModel.swift; sourceTree = ""; };
53 | BCAEB930269DDE3700DB84A8 /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = ""; };
54 | BCB97F42268234D700AE0968 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
55 | BCE7C57A26489DA200515A05 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
56 | /* End PBXFileReference section */
57 |
58 | /* Begin PBXFrameworksBuildPhase section */
59 | BC5020D72645EAE400E7E234 /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | BC4F374726A7540100B41D71 /* StackKit in Frameworks */,
64 | BCD6DCE626966F0B00D2E481 /* SharkUtils in Frameworks */,
65 | );
66 | runOnlyForDeploymentPostprocessing = 0;
67 | };
68 | /* End PBXFrameworksBuildPhase section */
69 |
70 | /* Begin PBXGroup section */
71 | BC4F374326A7524300B41D71 /* Tag View */ = {
72 | isa = PBXGroup;
73 | children = (
74 | BC6E946126A471AA00D91640 /* TagView.swift */,
75 | );
76 | path = "Tag View";
77 | sourceTree = "";
78 | };
79 | BC5020D12645EAE400E7E234 = {
80 | isa = PBXGroup;
81 | children = (
82 | BC4F374526A753F900B41D71 /* StackKit */,
83 | BC5020DC2645EAE400E7E234 /* Example */,
84 | BC5020DB2645EAE400E7E234 /* Products */,
85 | BC5020F32645EF5900E7E234 /* Frameworks */,
86 | );
87 | sourceTree = "";
88 | };
89 | BC5020DB2645EAE400E7E234 /* Products */ = {
90 | isa = PBXGroup;
91 | children = (
92 | BC5020DA2645EAE400E7E234 /* Example.app */,
93 | );
94 | name = Products;
95 | sourceTree = "";
96 | };
97 | BC5020DC2645EAE400E7E234 /* Example */ = {
98 | isa = PBXGroup;
99 | children = (
100 | BCB97F40268234D700AE0968 /* Flow */,
101 | BC6E946026A4719A00D91640 /* UI Components */,
102 | BCAEB91E269DD12300DB84A8 /* Models */,
103 | BCAEB92F269DDE2300DB84A8 /* Helpers */,
104 | BCE7C57826489C9D00515A05 /* Resources */,
105 | );
106 | path = Example;
107 | sourceTree = "";
108 | };
109 | BC5020F32645EF5900E7E234 /* Frameworks */ = {
110 | isa = PBXGroup;
111 | children = (
112 | );
113 | name = Frameworks;
114 | sourceTree = "";
115 | };
116 | BC6E946026A4719A00D91640 /* UI Components */ = {
117 | isa = PBXGroup;
118 | children = (
119 | BC4F374326A7524300B41D71 /* Tag View */,
120 | BC6E946526A5CEBD00D91640 /* Order Image Gallery */,
121 | );
122 | path = "UI Components";
123 | sourceTree = "";
124 | };
125 | BC6E946526A5CEBD00D91640 /* Order Image Gallery */ = {
126 | isa = PBXGroup;
127 | children = (
128 | BC6E946326A5B41A00D91640 /* OrderGalleryImageView.swift */,
129 | BC6E946626A5CECF00D91640 /* OrderGalleryImage.swift */,
130 | );
131 | path = "Order Image Gallery";
132 | sourceTree = "";
133 | };
134 | BC6E947026A6035C00D91640 /* Header */ = {
135 | isa = PBXGroup;
136 | children = (
137 | BC6E947126A6036D00D91640 /* HeaderCell.swift */,
138 | BC6E947326A604B200D91640 /* HeaderCellViewModel.swift */,
139 | );
140 | path = "Header ";
141 | sourceTree = "";
142 | };
143 | BCAEB917269DCD0000DB84A8 /* Feed */ = {
144 | isa = PBXGroup;
145 | children = (
146 | BCAEB91D269DD11D00DB84A8 /* Controller */,
147 | BCAEB91C269DD11500DB84A8 /* Cells */,
148 | );
149 | path = Feed;
150 | sourceTree = "";
151 | };
152 | BCAEB91C269DD11500DB84A8 /* Cells */ = {
153 | isa = PBXGroup;
154 | children = (
155 | BCAEB929269DD7B900DB84A8 /* Feed Item */,
156 | BC6E947026A6035C00D91640 /* Header */,
157 | BCAEB92A269DD86A00DB84A8 /* Order */,
158 | );
159 | path = Cells;
160 | sourceTree = "";
161 | };
162 | BCAEB91D269DD11D00DB84A8 /* Controller */ = {
163 | isa = PBXGroup;
164 | children = (
165 | BCAEB915269DCCFD00DB84A8 /* FeedViewController.swift */,
166 | BCAEB918269DCD6D00DB84A8 /* FeedViewModel.swift */,
167 | );
168 | path = Controller;
169 | sourceTree = "";
170 | };
171 | BCAEB91E269DD12300DB84A8 /* Models */ = {
172 | isa = PBXGroup;
173 | children = (
174 | BCAEB91A269DCD7D00DB84A8 /* FeedModel.swift */,
175 | BCAEB91F269DD14300DB84A8 /* OrderModel.swift */,
176 | BCAEB921269DD1C200DB84A8 /* ProductModel.swift */,
177 | );
178 | path = Models;
179 | sourceTree = "";
180 | };
181 | BCAEB929269DD7B900DB84A8 /* Feed Item */ = {
182 | isa = PBXGroup;
183 | children = (
184 | BCAEB925269DD6E000DB84A8 /* FeedItemCell.swift */,
185 | BCAEB927269DD6EB00DB84A8 /* FeedItemViewModel.swift */,
186 | );
187 | path = "Feed Item";
188 | sourceTree = "";
189 | };
190 | BCAEB92A269DD86A00DB84A8 /* Order */ = {
191 | isa = PBXGroup;
192 | children = (
193 | BCAEB92B269DD87800DB84A8 /* OrderCell.swift */,
194 | BCAEB92D269DD88B00DB84A8 /* OrderCellViewModel.swift */,
195 | );
196 | path = "Order ";
197 | sourceTree = "";
198 | };
199 | BCAEB92F269DDE2300DB84A8 /* Helpers */ = {
200 | isa = PBXGroup;
201 | children = (
202 | BCAEB930269DDE3700DB84A8 /* CollectionExtension.swift */,
203 | );
204 | path = Helpers;
205 | sourceTree = "";
206 | };
207 | BCB97F40268234D700AE0968 /* Flow */ = {
208 | isa = PBXGroup;
209 | children = (
210 | BCB97F41268234D700AE0968 /* App Delegate */,
211 | BCAEB917269DCD0000DB84A8 /* Feed */,
212 | );
213 | path = Flow;
214 | sourceTree = "";
215 | };
216 | BCB97F41268234D700AE0968 /* App Delegate */ = {
217 | isa = PBXGroup;
218 | children = (
219 | BCB97F42268234D700AE0968 /* AppDelegate.swift */,
220 | );
221 | path = "App Delegate";
222 | sourceTree = "";
223 | };
224 | BCE7C57826489C9D00515A05 /* Resources */ = {
225 | isa = PBXGroup;
226 | children = (
227 | BCE7C57A26489DA200515A05 /* Constants.swift */,
228 | BC5020E62645EAE500E7E234 /* Assets.xcassets */,
229 | BC5020E82645EAE500E7E234 /* LaunchScreen.storyboard */,
230 | BC5020EB2645EAE500E7E234 /* Info.plist */,
231 | );
232 | path = Resources;
233 | sourceTree = "";
234 | };
235 | /* End PBXGroup section */
236 |
237 | /* Begin PBXNativeTarget section */
238 | BC5020D92645EAE400E7E234 /* Example */ = {
239 | isa = PBXNativeTarget;
240 | buildConfigurationList = BC5020EE2645EAE500E7E234 /* Build configuration list for PBXNativeTarget "Example" */;
241 | buildPhases = (
242 | BC5020D62645EAE400E7E234 /* Sources */,
243 | BC5020D72645EAE400E7E234 /* Frameworks */,
244 | BC5020D82645EAE400E7E234 /* Resources */,
245 | );
246 | buildRules = (
247 | );
248 | dependencies = (
249 | );
250 | name = Example;
251 | packageProductDependencies = (
252 | BCD6DCE526966F0B00D2E481 /* SharkUtils */,
253 | BC4F374626A7540100B41D71 /* StackKit */,
254 | );
255 | productName = Example;
256 | productReference = BC5020DA2645EAE400E7E234 /* Example.app */;
257 | productType = "com.apple.product-type.application";
258 | };
259 | /* End PBXNativeTarget section */
260 |
261 | /* Begin PBXProject section */
262 | BC5020D22645EAE400E7E234 /* Project object */ = {
263 | isa = PBXProject;
264 | attributes = {
265 | LastSwiftUpdateCheck = 1250;
266 | LastUpgradeCheck = 1250;
267 | TargetAttributes = {
268 | BC5020D92645EAE400E7E234 = {
269 | CreatedOnToolsVersion = 12.5;
270 | };
271 | };
272 | };
273 | buildConfigurationList = BC5020D52645EAE400E7E234 /* Build configuration list for PBXProject "Example" */;
274 | compatibilityVersion = "Xcode 9.3";
275 | developmentRegion = en;
276 | hasScannedForEncodings = 0;
277 | knownRegions = (
278 | en,
279 | Base,
280 | );
281 | mainGroup = BC5020D12645EAE400E7E234;
282 | packageReferences = (
283 | BCD6DCE426966F0B00D2E481 /* XCRemoteSwiftPackageReference "ios-shark-utils" */,
284 | );
285 | productRefGroup = BC5020DB2645EAE400E7E234 /* Products */;
286 | projectDirPath = "";
287 | projectRoot = "";
288 | targets = (
289 | BC5020D92645EAE400E7E234 /* Example */,
290 | );
291 | };
292 | /* End PBXProject section */
293 |
294 | /* Begin PBXResourcesBuildPhase section */
295 | BC5020D82645EAE400E7E234 /* Resources */ = {
296 | isa = PBXResourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | BC5020EA2645EAE500E7E234 /* LaunchScreen.storyboard in Resources */,
300 | BC5020E72645EAE500E7E234 /* Assets.xcassets in Resources */,
301 | );
302 | runOnlyForDeploymentPostprocessing = 0;
303 | };
304 | /* End PBXResourcesBuildPhase section */
305 |
306 | /* Begin PBXSourcesBuildPhase section */
307 | BC5020D62645EAE400E7E234 /* Sources */ = {
308 | isa = PBXSourcesBuildPhase;
309 | buildActionMask = 2147483647;
310 | files = (
311 | BCAEB928269DD6EB00DB84A8 /* FeedItemViewModel.swift in Sources */,
312 | BC6E946726A5CECF00D91640 /* OrderGalleryImage.swift in Sources */,
313 | BCAEB92E269DD88B00DB84A8 /* OrderCellViewModel.swift in Sources */,
314 | BCAEB916269DCCFD00DB84A8 /* FeedViewController.swift in Sources */,
315 | BC6E947226A6036D00D91640 /* HeaderCell.swift in Sources */,
316 | BCAEB920269DD14300DB84A8 /* OrderModel.swift in Sources */,
317 | BCAEB92C269DD87800DB84A8 /* OrderCell.swift in Sources */,
318 | BCAEB931269DDE3700DB84A8 /* CollectionExtension.swift in Sources */,
319 | BC6E946226A471AA00D91640 /* TagView.swift in Sources */,
320 | BC6E947426A604B200D91640 /* HeaderCellViewModel.swift in Sources */,
321 | BCAEB922269DD1C200DB84A8 /* ProductModel.swift in Sources */,
322 | BCB97F4A268234D700AE0968 /* AppDelegate.swift in Sources */,
323 | BCAEB919269DCD6D00DB84A8 /* FeedViewModel.swift in Sources */,
324 | BCAEB926269DD6E000DB84A8 /* FeedItemCell.swift in Sources */,
325 | BCAEB91B269DCD7D00DB84A8 /* FeedModel.swift in Sources */,
326 | BCE7C57B26489DA200515A05 /* Constants.swift in Sources */,
327 | BC6E946426A5B41A00D91640 /* OrderGalleryImageView.swift in Sources */,
328 | );
329 | runOnlyForDeploymentPostprocessing = 0;
330 | };
331 | /* End PBXSourcesBuildPhase section */
332 |
333 | /* Begin PBXVariantGroup section */
334 | BC5020E82645EAE500E7E234 /* LaunchScreen.storyboard */ = {
335 | isa = PBXVariantGroup;
336 | children = (
337 | BC5020E92645EAE500E7E234 /* Base */,
338 | );
339 | name = LaunchScreen.storyboard;
340 | sourceTree = "";
341 | };
342 | /* End PBXVariantGroup section */
343 |
344 | /* Begin XCBuildConfiguration section */
345 | BC5020EC2645EAE500E7E234 /* Debug */ = {
346 | isa = XCBuildConfiguration;
347 | buildSettings = {
348 | ALWAYS_SEARCH_USER_PATHS = NO;
349 | CLANG_ANALYZER_NONNULL = YES;
350 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
352 | CLANG_CXX_LIBRARY = "libc++";
353 | CLANG_ENABLE_MODULES = YES;
354 | CLANG_ENABLE_OBJC_ARC = YES;
355 | CLANG_ENABLE_OBJC_WEAK = YES;
356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
357 | CLANG_WARN_BOOL_CONVERSION = YES;
358 | CLANG_WARN_COMMA = YES;
359 | CLANG_WARN_CONSTANT_CONVERSION = YES;
360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
371 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
373 | CLANG_WARN_STRICT_PROTOTYPES = YES;
374 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
375 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
376 | CLANG_WARN_UNREACHABLE_CODE = YES;
377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
378 | COPY_PHASE_STRIP = NO;
379 | DEBUG_INFORMATION_FORMAT = dwarf;
380 | ENABLE_STRICT_OBJC_MSGSEND = YES;
381 | ENABLE_TESTABILITY = YES;
382 | GCC_C_LANGUAGE_STANDARD = gnu11;
383 | GCC_DYNAMIC_NO_PIC = NO;
384 | GCC_NO_COMMON_BLOCKS = YES;
385 | GCC_OPTIMIZATION_LEVEL = 0;
386 | GCC_PREPROCESSOR_DEFINITIONS = (
387 | "DEBUG=1",
388 | "$(inherited)",
389 | );
390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
392 | GCC_WARN_UNDECLARED_SELECTOR = YES;
393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
394 | GCC_WARN_UNUSED_FUNCTION = YES;
395 | GCC_WARN_UNUSED_VARIABLE = YES;
396 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
397 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
398 | MTL_FAST_MATH = YES;
399 | ONLY_ACTIVE_ARCH = YES;
400 | SDKROOT = iphoneos;
401 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
402 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
403 | };
404 | name = Debug;
405 | };
406 | BC5020ED2645EAE500E7E234 /* Release */ = {
407 | isa = XCBuildConfiguration;
408 | buildSettings = {
409 | ALWAYS_SEARCH_USER_PATHS = NO;
410 | CLANG_ANALYZER_NONNULL = YES;
411 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
412 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
413 | CLANG_CXX_LIBRARY = "libc++";
414 | CLANG_ENABLE_MODULES = YES;
415 | CLANG_ENABLE_OBJC_ARC = YES;
416 | CLANG_ENABLE_OBJC_WEAK = YES;
417 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
418 | CLANG_WARN_BOOL_CONVERSION = YES;
419 | CLANG_WARN_COMMA = YES;
420 | CLANG_WARN_CONSTANT_CONVERSION = YES;
421 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
422 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
423 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
424 | CLANG_WARN_EMPTY_BODY = YES;
425 | CLANG_WARN_ENUM_CONVERSION = YES;
426 | CLANG_WARN_INFINITE_RECURSION = YES;
427 | CLANG_WARN_INT_CONVERSION = YES;
428 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
429 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
430 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
431 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
432 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
433 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
434 | CLANG_WARN_STRICT_PROTOTYPES = YES;
435 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
436 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
437 | CLANG_WARN_UNREACHABLE_CODE = YES;
438 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
439 | COPY_PHASE_STRIP = NO;
440 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
441 | ENABLE_NS_ASSERTIONS = NO;
442 | ENABLE_STRICT_OBJC_MSGSEND = YES;
443 | GCC_C_LANGUAGE_STANDARD = gnu11;
444 | GCC_NO_COMMON_BLOCKS = YES;
445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
447 | GCC_WARN_UNDECLARED_SELECTOR = YES;
448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
449 | GCC_WARN_UNUSED_FUNCTION = YES;
450 | GCC_WARN_UNUSED_VARIABLE = YES;
451 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
452 | MTL_ENABLE_DEBUG_INFO = NO;
453 | MTL_FAST_MATH = YES;
454 | SDKROOT = iphoneos;
455 | SWIFT_COMPILATION_MODE = wholemodule;
456 | SWIFT_OPTIMIZATION_LEVEL = "-O";
457 | VALIDATE_PRODUCT = YES;
458 | };
459 | name = Release;
460 | };
461 | BC5020EF2645EAE500E7E234 /* Debug */ = {
462 | isa = XCBuildConfiguration;
463 | buildSettings = {
464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
465 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
466 | CODE_SIGN_STYLE = Automatic;
467 | DEVELOPMENT_TEAM = X52QJY6S22;
468 | INFOPLIST_FILE = Example/Resources/Info.plist;
469 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
470 | LD_RUNPATH_SEARCH_PATHS = (
471 | "$(inherited)",
472 | "@executable_path/Frameworks",
473 | );
474 | PRODUCT_BUNDLE_IDENTIFIER = com.russ.testing;
475 | PRODUCT_NAME = "$(TARGET_NAME)";
476 | SWIFT_VERSION = 5.0;
477 | TARGETED_DEVICE_FAMILY = "1,2";
478 | };
479 | name = Debug;
480 | };
481 | BC5020F02645EAE500E7E234 /* Release */ = {
482 | isa = XCBuildConfiguration;
483 | buildSettings = {
484 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
485 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
486 | CODE_SIGN_STYLE = Automatic;
487 | DEVELOPMENT_TEAM = X52QJY6S22;
488 | INFOPLIST_FILE = Example/Resources/Info.plist;
489 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
490 | LD_RUNPATH_SEARCH_PATHS = (
491 | "$(inherited)",
492 | "@executable_path/Frameworks",
493 | );
494 | PRODUCT_BUNDLE_IDENTIFIER = com.russ.testing;
495 | PRODUCT_NAME = "$(TARGET_NAME)";
496 | SWIFT_VERSION = 5.0;
497 | TARGETED_DEVICE_FAMILY = "1,2";
498 | };
499 | name = Release;
500 | };
501 | /* End XCBuildConfiguration section */
502 |
503 | /* Begin XCConfigurationList section */
504 | BC5020D52645EAE400E7E234 /* Build configuration list for PBXProject "Example" */ = {
505 | isa = XCConfigurationList;
506 | buildConfigurations = (
507 | BC5020EC2645EAE500E7E234 /* Debug */,
508 | BC5020ED2645EAE500E7E234 /* Release */,
509 | );
510 | defaultConfigurationIsVisible = 0;
511 | defaultConfigurationName = Release;
512 | };
513 | BC5020EE2645EAE500E7E234 /* Build configuration list for PBXNativeTarget "Example" */ = {
514 | isa = XCConfigurationList;
515 | buildConfigurations = (
516 | BC5020EF2645EAE500E7E234 /* Debug */,
517 | BC5020F02645EAE500E7E234 /* Release */,
518 | );
519 | defaultConfigurationIsVisible = 0;
520 | defaultConfigurationName = Release;
521 | };
522 | /* End XCConfigurationList section */
523 |
524 | /* Begin XCRemoteSwiftPackageReference section */
525 | BCD6DCE426966F0B00D2E481 /* XCRemoteSwiftPackageReference "ios-shark-utils" */ = {
526 | isa = XCRemoteSwiftPackageReference;
527 | repositoryURL = "git@github.com:gymshark/ios-shark-utils.git";
528 | requirement = {
529 | kind = exactVersion;
530 | version = 1.0.6;
531 | };
532 | };
533 | /* End XCRemoteSwiftPackageReference section */
534 |
535 | /* Begin XCSwiftPackageProductDependency section */
536 | BC4F374626A7540100B41D71 /* StackKit */ = {
537 | isa = XCSwiftPackageProductDependency;
538 | productName = StackKit;
539 | };
540 | BCD6DCE526966F0B00D2E481 /* SharkUtils */ = {
541 | isa = XCSwiftPackageProductDependency;
542 | package = BCD6DCE426966F0B00D2E481 /* XCRemoteSwiftPackageReference "ios-shark-utils" */;
543 | productName = SharkUtils;
544 | };
545 | /* End XCSwiftPackageProductDependency section */
546 | };
547 | rootObject = BC5020D22645EAE400E7E234 /* Project object */;
548 | }
549 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "ConstraintKit",
6 | "repositoryURL": "https://github.com/gymshark/ios-constraint-kit.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "404baf28a47a5cbbb9fcb59ef8c7be605e394c4a",
10 | "version": "1.0.0"
11 | }
12 | },
13 | {
14 | "package": "SharkUtils",
15 | "repositoryURL": "git@github.com:gymshark/ios-shark-utils.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "aef1ed76f2d69b8be1c0a0ed664dfad2fd55aa1f",
19 | "version": "1.0.6"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Example/Example/Flow/App Delegate/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 07/05/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 |
17 | window = UIWindow(frame: UIScreen.main.bounds)
18 | window?.rootViewController = FeedViewController(viewModel: FeedViewModel())
19 | window?.makeKeyAndVisible()
20 | return true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Cells/Feed Item/FeedItemCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedItemCell.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 | import ConstraintKit
11 |
12 | final class FeedItemCell: UITableViewCell {
13 |
14 | // MARK: - UI
15 |
16 | private let cardImage = UIImageView().with {
17 | $0.setCornerRadius(12)
18 | $0.backgroundColor = .gray
19 | $0.contentMode = .scaleAspectFill
20 | $0.clipsToBounds = true
21 | }
22 |
23 | private let titleLabel = UILabel().with {
24 | $0.font = .systemFont(ofSize: 18, weight: .heavy)
25 | $0.textColor = .white
26 | $0.numberOfLines = 2
27 | }
28 |
29 | private let subtitleLabel = UILabel().with {
30 | $0.font = .systemFont(ofSize: 15, weight: .regular)
31 | $0.textColor = .white
32 | }
33 |
34 | private let button = UIImageView(image: UIImage(named: "button-icon"))
35 |
36 | private let tagView = TagView()
37 |
38 | private lazy var tagStack = HStack {
39 | tagView
40 | Spacer()
41 | }
42 |
43 | // MARK: - Init
44 |
45 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
46 | super.init(style: style, reuseIdentifier: reuseIdentifier)
47 | setConstraints()
48 | configure()
49 | }
50 |
51 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
52 |
53 | // MARK: - ViewModel
54 |
55 | var viewModel: FeedItemViewModel? {
56 | didSet {
57 | guard let viewModel = viewModel else { return }
58 | titleLabel.text = viewModel.titleText
59 | subtitleLabel.text = viewModel.subtitleText
60 | tagView.text = viewModel.tagText
61 | tagStack.isHidden = viewModel.isTagHidden
62 | cardImage.image = UIImage(named: viewModel.imageName)
63 | }
64 | }
65 |
66 | // MARK: - Configure
67 |
68 | private func configure(){
69 | backgroundColor = .clear
70 | contentView.backgroundColor = .clear
71 | selectionStyle = .none
72 | }
73 |
74 | private func setConstraints(){
75 | contentView.ZStack {
76 | cardImage
77 | VStack {
78 | tagStack
79 | Spacer()
80 |
81 | HStack {
82 | VStack {
83 | titleLabel
84 | Spacer(h: 6)
85 | subtitleLabel
86 | }
87 | Spacer(w: 10)
88 | button.withSquare(50)
89 | }.alignment(.center)
90 |
91 | }.margin(.all(Constants.layout.contentMargin))
92 |
93 | }.margin(.insets(top: Constants.layout.spacing, left: Constants.layout.tableMargin, right: Constants.layout.tableMargin))
94 | .withAspectRatio(0.9, priority: .init(999))
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Cells/Feed Item/FeedItemViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedItemViewModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | final class FeedItemViewModel {
11 |
12 | // MARK: - Dependencies
13 |
14 | private let feedItem: FeedModel
15 |
16 | // MARK: - Init
17 |
18 | init(feedItem: FeedModel){
19 | self.feedItem = feedItem
20 | }
21 |
22 | // MARK: - Interface
23 |
24 | var titleText: String {
25 | return feedItem.title.uppercased()
26 | }
27 |
28 | var subtitleText: String? {
29 | return feedItem.subtitle
30 | }
31 |
32 | var imageName: String {
33 | return feedItem.image ?? ""
34 | }
35 |
36 | var tagText: String? {
37 | return feedItem.tag?.uppercased()
38 | }
39 |
40 | var isTagHidden: Bool {
41 | return tagText == nil
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Cells/Header /HeaderCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderCell.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 19/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 | import SharkUtils
11 | import ConstraintKit
12 |
13 | final class HeaderCell: UITableViewCell {
14 |
15 | // MARK: - UI
16 |
17 | private let headlineLabel = UILabel().with {
18 | $0.font = .systemFont(ofSize: 17, weight: .heavy)
19 | $0.textColor = .black
20 | }
21 |
22 | private let initialsLabel = UILabel().with {
23 | $0.font = .systemFont(ofSize: 15, weight: .heavy)
24 | $0.textColor = .white
25 | $0.textAlignment = .center
26 | }
27 |
28 | private let initialsView = UIView().with {
29 | $0.backgroundColor = .darkGray
30 | $0.setCornerRadius(18)
31 | $0.clipsToBounds = true
32 | }
33 |
34 | private let searchButton = UIImageView().with({
35 | $0.image = UIImage(named: "search-icon")
36 | $0.contentMode = .scaleAspectFit
37 | })
38 |
39 | // MARK: - Init
40 |
41 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
42 | super.init(style: style, reuseIdentifier: reuseIdentifier)
43 | setConstraints()
44 | configure()
45 | }
46 |
47 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
48 |
49 | // MARK: - ViewModel
50 |
51 | var viewModel: HeaderCellViewModel? {
52 | didSet {
53 | headlineLabel.text = viewModel?.headlineText
54 | initialsLabel.text = viewModel?.initialsText
55 | }
56 | }
57 |
58 | // MARK: - Configure
59 |
60 | private func configure(){
61 | backgroundColor = .clear
62 | contentView.backgroundColor = .clear
63 | selectionStyle = .none
64 | }
65 |
66 | private func setConstraints(){
67 |
68 | initialsView.VStack {
69 | initialsLabel
70 | }
71 |
72 | contentView.HStack {
73 | initialsView.withSquare(36)
74 | Spacer(w: 10)
75 | headlineLabel
76 | searchButton.withSquare(20)
77 | }.margin(.insets(top: Constants.layout.spacing, left: Constants.layout.tableMargin, right: Constants.layout.tableMargin))
78 | .alignment(.center)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Cells/Header /HeaderCellViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderCellViewModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 19/07/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | struct HeaderCellViewModel {
11 |
12 | var headlineText: String {
13 | return "HI, RUSSELL"
14 | }
15 |
16 | var initialsText: String {
17 | return "RW"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Cells/Order /OrderCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderCell.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 | import ConstraintKit
11 |
12 | final class OrderCell: UITableViewCell {
13 |
14 | // MARK: - UI
15 |
16 | private let card = UIView().with {
17 | $0.setCornerRadius(12)
18 | $0.backgroundColor = .white
19 | }
20 |
21 | private let titleLabel = UILabel().with {
22 | $0.font = .systemFont(ofSize: 18, weight: .heavy)
23 | $0.textColor = .black
24 | $0.numberOfLines = 2
25 | }
26 |
27 | private let subtitleLabel = UILabel().with {
28 | $0.font = .systemFont(ofSize: 15, weight: .regular)
29 | $0.textColor = .black
30 | }
31 |
32 | private let statusBar = UIView().with {
33 | $0.backgroundColor = Constants.colors.tint
34 | $0.setCornerRadius(3)
35 | }
36 |
37 | private let gallery = OrderGalleryImageView()
38 |
39 | private let moreButton = UIImageView().with({
40 | $0.image = UIImage(named: "more-icon")
41 | $0.contentMode = .scaleAspectFit
42 | })
43 |
44 | // MARK: - Init
45 |
46 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
47 | super.init(style: style, reuseIdentifier: reuseIdentifier)
48 | setConstraints()
49 | configure()
50 | }
51 |
52 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
53 |
54 | // MARK: - ViewModel
55 |
56 | var viewModel: OrderCellViewModel? {
57 | didSet {
58 | guard let viewModel = viewModel else { return }
59 | titleLabel.text = viewModel.titleText
60 | subtitleLabel.text = viewModel.subtitleText
61 | gallery.images = viewModel.images
62 | }
63 | }
64 |
65 | // MARK: - Configure
66 |
67 | private func configure(){
68 | backgroundColor = .clear
69 | contentView.backgroundColor = .clear
70 | selectionStyle = .none
71 | }
72 |
73 | private func setConstraints(){
74 | contentView.ZStack {
75 | card
76 | VStack(spacing: 5) {
77 |
78 | HStack {
79 | titleLabel
80 | moreButton.withWidth(26)
81 | }.alignment(.center)
82 |
83 | subtitleLabel
84 | Spacer(h: 3)
85 | statusBar.withHeight(6)
86 | Spacer(h: 5)
87 | gallery
88 | }.margin(.all(Constants.layout.contentMargin))
89 |
90 | }.margin(.insets(top: Constants.layout.spacing, left: Constants.layout.tableMargin, right: Constants.layout.tableMargin))
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Cells/Order /OrderCellViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderCellViewModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | struct OrderCellViewModel {
11 |
12 | // MARK: - Dependenies
13 |
14 | private let order: OrderModel
15 |
16 | // MARK: - Init
17 |
18 | init(order: OrderModel){
19 | self.order = order
20 | }
21 |
22 | // MARK: - Interface
23 |
24 | var titleText: String {
25 | return ("IT'S " + order.status).uppercased()
26 | }
27 |
28 | var subtitleText: String {
29 | let df = DateFormatter()
30 | df.dateFormat = "dd MMM yy"
31 | return "Ordered on " + df.string(from: order.orderDate)
32 | }
33 |
34 | var images: [String]? {
35 | return ["tshirt", "top", "hoodie", "shorts", "shorts", "shorts"]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Controller/FeedViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedViewController.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 |
11 | final class FeedViewController: UIViewController {
12 |
13 | // MARK: - UI
14 |
15 | private lazy var tableView = UITableView().with {
16 | $0.register(FeedItemCell.self, forCellReuseIdentifier: FeedItemCell.identifier)
17 | $0.register(OrderCell.self, forCellReuseIdentifier: OrderCell.identifier)
18 | $0.register(HeaderCell.self, forCellReuseIdentifier: HeaderCell.identifier)
19 | $0.rowHeight = UITableView.automaticDimension
20 | $0.delegate = self
21 | $0.dataSource = self
22 | $0.backgroundColor = .clear
23 | $0.separatorStyle = .none
24 | }
25 |
26 | // MARK: - Dependencies
27 |
28 | private let viewModel: FeedViewModel
29 |
30 | // MARK: - Init
31 |
32 | init(viewModel: FeedViewModel){
33 | self.viewModel = viewModel
34 | super.init(nibName: nil, bundle: nil)
35 | setConstraints()
36 | configure()
37 | }
38 |
39 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
40 |
41 | // MARK: - Configure
42 |
43 | private func setConstraints(){
44 | view.VStack(useSafeArea: false) {
45 | tableView
46 | }
47 | }
48 |
49 | private func configure(){
50 | view.backgroundColor = Constants.colors.background
51 | }
52 | }
53 |
54 | extension FeedViewController: UITableViewDelegate, UITableViewDataSource {
55 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
56 | guard let item = viewModel.cellItem(for: indexPath) else { return UITableViewCell() }
57 |
58 | switch item {
59 | case .header(let vm):
60 | let cell = tableView.dequeueReusableCell(withIdentifier: HeaderCell.identifier, for: indexPath) as! HeaderCell
61 | cell.viewModel = vm
62 | return cell
63 |
64 | case .feedItem(let vm):
65 | let cell = tableView.dequeueReusableCell(withIdentifier: FeedItemCell.identifier, for: indexPath) as! FeedItemCell
66 | cell.viewModel = vm
67 | return cell
68 |
69 | case .order(let vm):
70 | let cell = tableView.dequeueReusableCell(withIdentifier: OrderCell.identifier, for: indexPath) as! OrderCell
71 | cell.viewModel = vm
72 | return cell
73 | }
74 | }
75 |
76 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
77 | return viewModel.numberOfRows
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Example/Example/Flow/Feed/Controller/FeedViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedViewModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import Foundation
9 | import SharkUtils
10 |
11 | enum Feeditem {
12 | case header(HeaderCellViewModel)
13 | case feedItem(FeedItemViewModel)
14 | case order(OrderCellViewModel)
15 | }
16 |
17 | final class FeedViewModel {
18 |
19 | // MARK: - Interface
20 |
21 | func cellItem(for indexPath: IndexPath) -> Feeditem? {
22 | return items[safe: indexPath.row]
23 | }
24 |
25 | var numberOfRows: Int {
26 | return items.count
27 | }
28 |
29 | var numberOfSections: Int {
30 | return 1
31 | }
32 | }
33 |
34 | extension FeedViewModel {
35 |
36 | // MARK: - Items
37 |
38 | private var items: [Feeditem] {
39 | var items: [Feeditem] = [.header(HeaderCellViewModel())]
40 |
41 | var feedItems: [Feeditem] = feedVMs.compactMap({ .feedItem($0) })
42 |
43 | if let orderVM = orderViewModel {
44 | feedItems.insert(.order(orderVM), at: (feedItems.isEmpty) ? 0 : 1)
45 | }
46 |
47 | items.append(contentsOf: feedItems)
48 |
49 | return items
50 | }
51 |
52 | // MARK: - View Models
53 |
54 | private var orderViewModel: OrderCellViewModel? {
55 | guard let order = myOrder else { return nil }
56 | return OrderCellViewModel(order: order)
57 | }
58 |
59 | private var feedVMs: [FeedItemViewModel] {
60 | return feedModels.compactMap({ FeedItemViewModel(feedItem: $0) })
61 | }
62 | }
63 |
64 | extension FeedViewModel {
65 |
66 | // MARK: - Stub data
67 |
68 | private var feedModels: [FeedModel] {
69 | return [
70 | FeedModel(title: "The future is functional", subtitle: "Get after your goals in new Apex",
71 | tag: "new releases", image: "functional-male"),
72 | FeedModel(title: "Press reset", subtitle: "The newest collection is here", image: "reset"),
73 | FeedModel(title: "The future is functional", subtitle: "Get after your goals in new Apex",
74 | tag: "new releases", image: "functional-female"),
75 | FeedModel(title: "Raise the bar", subtitle: "Level up in Vital Rise", image: "raise"),
76 | FeedModel(title: "Must haves", subtitle: "Your key piece inspo", image: "must-have"),
77 | FeedModel(title: "Take on the heat", subtitle: "In summer activewear", image: "flex")
78 | ]
79 | }
80 |
81 | private var myOrder: OrderModel? {
82 | return OrderModel(status: "FUFILLED",
83 | items: [ProductModel(productTitle: "Arrival Shorts", image: "shorts"),
84 | ProductModel(productTitle: "Arrival T-Shirt", image: "tshirt")])
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Example/Example/Helpers/CollectionExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionExtension.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | extension Collection {
11 | // Return element if within the bounds, else rerturn nil
12 | subscript (safe index: Index) -> Element? {
13 | return indices.contains(index) ? self[index] : nil
14 | }
15 | }
16 |
17 | extension UITableViewCell {
18 | static var identifier: String {
19 | return String(describing: self)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Example/Example/Models/FeedModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | struct FeedModel {
11 |
12 | let id: String
13 | let title: String
14 | let subtitle: String?
15 | let tag: String?
16 | let image: String?
17 |
18 | init(id: String = UUID().uuidString, title: String, subtitle: String? = nil, tag: String? = nil, image: String?) {
19 | self.id = id
20 | self.title = title
21 | self.subtitle = subtitle
22 | self.tag = tag
23 | self.image = image
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Example/Models/OrderModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | struct OrderModel {
11 |
12 | let id: String
13 | let status: String
14 | let orderDate: Date
15 | let items: [ProductModel]
16 |
17 | init(id: String = UUID().uuidString,
18 | status: String,
19 | orderDate: Date = Calendar.current.date(byAdding: .day, value: -7, to: Date()) ?? Date(),
20 | items: [ProductModel]) {
21 |
22 | self.id = id
23 | self.status = status
24 | self.orderDate = orderDate
25 | self.items = items
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Example/Example/Models/ProductModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProductModel.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 13/07/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ProductModel {
11 |
12 | let id: String
13 | let productTitle: String
14 | let image: String
15 |
16 | init(id: String = UUID().uuidString, productTitle: String, image: String) {
17 | self.id = id
18 | self.productTitle = productTitle
19 | self.image = image
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/button-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "button-icon.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/button-icon.imageset/button-icon.pdf:
--------------------------------------------------------------------------------
1 | %PDF-1.7
2 |
3 | 1 0 obj
4 | << /ExtGState << /E1 << /ca 0.170000 >> >> >>
5 | endobj
6 |
7 | 2 0 obj
8 | << /Length 3 0 R >>
9 | stream
10 | /DeviceRGB CS
11 | /DeviceRGB cs
12 | q
13 | q
14 | /E1 gs
15 | 1.000000 0.000000 -0.000000 1.000000 11.000000 -7.000000 cm
16 | 1.000000 1.000000 1.000000 scn
17 | 44.000000 30.000000 m
18 | 25.222319 30.000000 10.000000 45.222321 10.000000 64.000000 c
19 | -10.000000 64.000000 l
20 | -10.000000 34.176620 14.176624 10.000000 44.000000 10.000000 c
21 | 44.000000 30.000000 l
22 | h
23 | 78.000000 64.000000 m
24 | 78.000000 45.222321 62.777679 30.000000 44.000000 30.000000 c
25 | 44.000000 10.000000 l
26 | 73.823380 10.000000 98.000000 34.176620 98.000000 64.000000 c
27 | 78.000000 64.000000 l
28 | h
29 | 44.000000 98.000000 m
30 | 62.777679 98.000000 78.000000 82.777679 78.000000 64.000000 c
31 | 98.000000 64.000000 l
32 | 98.000000 93.823380 73.823380 118.000000 44.000000 118.000000 c
33 | 44.000000 98.000000 l
34 | h
35 | 44.000000 118.000000 m
36 | 14.176624 118.000000 -10.000000 93.823380 -10.000000 64.000000 c
37 | 10.000000 64.000000 l
38 | 10.000000 82.777679 25.222319 98.000000 44.000000 98.000000 c
39 | 44.000000 118.000000 l
40 | h
41 | f
42 | n
43 | Q
44 | q
45 | 1.000000 0.000000 -0.000000 1.000000 11.000000 -7.000000 cm
46 | 1.000000 1.000000 1.000000 scn
47 | 0.000000 64.000000 m
48 | 0.000000 88.300529 19.699472 108.000000 44.000000 108.000000 c
49 | 44.000000 108.000000 l
50 | 68.300529 108.000000 88.000000 88.300529 88.000000 64.000000 c
51 | 88.000000 64.000000 l
52 | 88.000000 39.699471 68.300529 20.000000 44.000000 20.000000 c
53 | 44.000000 20.000000 l
54 | 19.699472 20.000000 0.000000 39.699471 0.000000 64.000000 c
55 | 0.000000 64.000000 l
56 | h
57 | f
58 | n
59 | Q
60 | Q
61 | q
62 | 0.697444 0.716640 -0.697444 0.716640 60.303665 37.165039 cm
63 | 0.304167 0.286424 0.286424 scn
64 | 16.738838 27.677734 m
65 | 16.738838 29.058447 15.619550 30.177734 14.238838 30.177734 c
66 | 12.858126 30.177734 11.738838 29.058447 11.738838 27.677734 c
67 | 16.738838 27.677734 l
68 | h
69 | 14.238838 13.295068 m
70 | 14.238838 10.795069 l
71 | 15.619550 10.795069 16.738838 11.914356 16.738838 13.295068 c
72 | 14.238838 13.295068 l
73 | h
74 | 0.000000 15.795068 m
75 | -1.380712 15.795068 -2.500000 14.675780 -2.500000 13.295068 c
76 | -2.500000 11.914356 -1.380712 10.795069 0.000000 10.795069 c
77 | 0.000000 15.795068 l
78 | h
79 | 11.738838 27.677734 m
80 | 11.738838 13.295068 l
81 | 16.738838 13.295068 l
82 | 16.738838 27.677734 l
83 | 11.738838 27.677734 l
84 | h
85 | 14.238838 15.795068 m
86 | 0.000000 15.795068 l
87 | 0.000000 10.795069 l
88 | 14.238838 10.795069 l
89 | 14.238838 15.795068 l
90 | h
91 | f
92 | n
93 | Q
94 |
95 | endstream
96 | endobj
97 |
98 | 3 0 obj
99 | 2161
100 | endobj
101 |
102 | 4 0 obj
103 | << /Annots []
104 | /Type /Page
105 | /MediaBox [ 0.000000 0.000000 109.000000 112.000000 ]
106 | /Resources 1 0 R
107 | /Contents 2 0 R
108 | /Parent 5 0 R
109 | >>
110 | endobj
111 |
112 | 5 0 obj
113 | << /Kids [ 4 0 R ]
114 | /Count 1
115 | /Type /Pages
116 | >>
117 | endobj
118 |
119 | 6 0 obj
120 | << /Type /Catalog
121 | /Pages 5 0 R
122 | >>
123 | endobj
124 |
125 | xref
126 | 0 7
127 | 0000000000 65535 f
128 | 0000000010 00000 n
129 | 0000000074 00000 n
130 | 0000002291 00000 n
131 | 0000002314 00000 n
132 | 0000002489 00000 n
133 | 0000002563 00000 n
134 | trailer
135 | << /ID [ (some) (id) ]
136 | /Root 6 0 R
137 | /Size 7
138 | >>
139 | startxref
140 | 2622
141 | %%EOF
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/more-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "more-icon.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/more-icon.imageset/more-icon.pdf:
--------------------------------------------------------------------------------
1 | %PDF-1.7
2 |
3 | 1 0 obj
4 | << >>
5 | endobj
6 |
7 | 2 0 obj
8 | << /Length 3 0 R >>
9 | stream
10 | /DeviceRGB CS
11 | /DeviceRGB cs
12 | q
13 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
14 | 0.000000 0.000000 0.000000 scn
15 | 4.000000 2.000000 m
16 | 4.000000 0.895431 3.104569 0.000000 2.000000 0.000000 c
17 | 0.895431 0.000000 0.000000 0.895431 0.000000 2.000000 c
18 | 0.000000 3.104569 0.895431 4.000000 2.000000 4.000000 c
19 | 3.104569 4.000000 4.000000 3.104569 4.000000 2.000000 c
20 | h
21 | f
22 | n
23 | Q
24 | q
25 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
26 | 0.000000 0.000000 0.000000 scn
27 | 10.000000 2.000000 m
28 | 10.000000 0.895431 9.104569 0.000000 8.000000 0.000000 c
29 | 6.895431 0.000000 6.000000 0.895431 6.000000 2.000000 c
30 | 6.000000 3.104569 6.895431 4.000000 8.000000 4.000000 c
31 | 9.104569 4.000000 10.000000 3.104569 10.000000 2.000000 c
32 | h
33 | f
34 | n
35 | Q
36 | q
37 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
38 | 0.000000 0.000000 0.000000 scn
39 | 14.000000 0.000000 m
40 | 15.104568 0.000000 16.000000 0.895431 16.000000 2.000000 c
41 | 16.000000 3.104569 15.104568 4.000000 14.000000 4.000000 c
42 | 12.895430 4.000000 12.000000 3.104569 12.000000 2.000000 c
43 | 12.000000 0.895431 12.895430 0.000000 14.000000 0.000000 c
44 | h
45 | f
46 | n
47 | Q
48 |
49 | endstream
50 | endobj
51 |
52 | 3 0 obj
53 | 1074
54 | endobj
55 |
56 | 4 0 obj
57 | << /Annots []
58 | /Type /Page
59 | /MediaBox [ 0.000000 0.000000 16.000000 4.000000 ]
60 | /Resources 1 0 R
61 | /Contents 2 0 R
62 | /Parent 5 0 R
63 | >>
64 | endobj
65 |
66 | 5 0 obj
67 | << /Kids [ 4 0 R ]
68 | /Count 1
69 | /Type /Pages
70 | >>
71 | endobj
72 |
73 | 6 0 obj
74 | << /Type /Catalog
75 | /Pages 5 0 R
76 | >>
77 | endobj
78 |
79 | xref
80 | 0 7
81 | 0000000000 65535 f
82 | 0000000010 00000 n
83 | 0000000034 00000 n
84 | 0000001164 00000 n
85 | 0000001187 00000 n
86 | 0000001359 00000 n
87 | 0000001433 00000 n
88 | trailer
89 | << /ID [ (some) (id) ]
90 | /Root 6 0 R
91 | /Size 7
92 | >>
93 | startxref
94 | 1492
95 | %%EOF
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/search-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "search-icon.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Assets/search-icon.imageset/search-icon.pdf:
--------------------------------------------------------------------------------
1 | %PDF-1.7
2 |
3 | 1 0 obj
4 | << >>
5 | endobj
6 |
7 | 2 0 obj
8 | << /Length 3 0 R >>
9 | stream
10 | /DeviceRGB CS
11 | /DeviceRGB cs
12 | q
13 | -1.000000 -0.000000 -0.000000 1.000000 26.000000 -1.208801 cm
14 | 0.000000 0.000000 0.000000 scn
15 | 9.157823 10.366623 m
16 | 5.934361 13.590084 5.934361 18.816347 9.157823 22.039808 c
17 | 12.381284 25.263271 17.607548 25.263271 20.831009 22.039808 c
18 | 24.054470 18.816347 24.054470 13.590084 20.831009 10.366623 c
19 | 17.607548 7.143162 12.381284 7.143162 9.157823 10.366623 c
20 | h
21 | 7.212290 23.985340 m
22 | 3.243151 20.016201 2.939497 13.769619 6.301330 9.452015 c
23 | 6.280283 9.433380 6.259669 9.414001 6.239525 9.393858 c
24 | 0.402932 3.557262 l
25 | -0.134311 3.020020 -0.134311 2.148975 0.402932 1.611732 c
26 | 0.940175 1.074490 1.811220 1.074490 2.348463 1.611732 c
27 | 8.185057 7.448326 l
28 | 8.205199 7.468470 8.224580 7.489084 8.243214 7.510130 c
29 | 12.560818 4.148296 18.807400 4.451952 22.776539 8.421091 c
30 | 27.074488 12.719041 27.074488 19.687391 22.776539 23.985340 c
31 | 18.478590 28.283289 11.510241 28.283289 7.212290 23.985340 c
32 | h
33 | f*
34 | n
35 | Q
36 |
37 | endstream
38 | endobj
39 |
40 | 3 0 obj
41 | 924
42 | endobj
43 |
44 | 4 0 obj
45 | << /Annots []
46 | /Type /Page
47 | /MediaBox [ 0.000000 0.000000 26.000000 26.000000 ]
48 | /Resources 1 0 R
49 | /Contents 2 0 R
50 | /Parent 5 0 R
51 | >>
52 | endobj
53 |
54 | 5 0 obj
55 | << /Kids [ 4 0 R ]
56 | /Count 1
57 | /Type /Pages
58 | >>
59 | endobj
60 |
61 | 6 0 obj
62 | << /Type /Catalog
63 | /Pages 5 0 R
64 | >>
65 | endobj
66 |
67 | xref
68 | 0 7
69 | 0000000000 65535 f
70 | 0000000010 00000 n
71 | 0000000034 00000 n
72 | 0000001014 00000 n
73 | 0000001036 00000 n
74 | 0000001209 00000 n
75 | 0000001283 00000 n
76 | trailer
77 | << /ID [ (some) (id) ]
78 | /Root 6 0 R
79 | /Size 7
80 | >>
81 | startxref
82 | 1342
83 | %%EOF
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/flex.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "reset.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/flex.imageset/reset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Feed/flex.imageset/reset.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/functional-female.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_5650.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/functional-female.imageset/IMG_5650.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Feed/functional-female.imageset/IMG_5650.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/functional-male.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_5651.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/functional-male.imageset/IMG_5651.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Feed/functional-male.imageset/IMG_5651.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/must-have.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_5663.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/must-have.imageset/IMG_5663.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Feed/must-have.imageset/IMG_5663.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/raise.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_5662.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/raise.imageset/IMG_5662.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Feed/raise.imageset/IMG_5662.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/reset.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_5661.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Feed/reset.imageset/IMG_5661.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Feed/reset.imageset/IMG_5661.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/hoodie.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "hoodie.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/hoodie.imageset/hoodie.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Products/hoodie.imageset/hoodie.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/shorts.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shorts.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/shorts.imageset/shorts.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Products/shorts.imageset/shorts.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/top.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "top.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/top.imageset/top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Products/top.imageset/top.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/tshirt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tshirt.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Assets.xcassets/Products/tshirt.imageset/tshirt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gymshark/ios-stack-kit/dc4ea6594e1477c40bca84cb17c1c48443781599/Example/Example/Resources/Assets.xcassets/Products/tshirt.imageset/tshirt.jpg
--------------------------------------------------------------------------------
/Example/Example/Resources/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 |
--------------------------------------------------------------------------------
/Example/Example/Resources/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 09/05/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | enum Constants {
11 |
12 | enum colors {
13 | static var background = UIColor.init(white: 0.95, alpha: 1.0)
14 | static var tint = UIColor.init(red: 46.0/255.0, green: 142.0/255.0, blue: 215.0/255.0, alpha: 1.0)
15 | }
16 |
17 | enum layout {
18 | static let tableMargin: CGFloat = 10.0
19 | static let contentMargin: CGFloat = 20.0
20 | static let spacing: CGFloat = 20.0
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Example/Example/Resources/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSupportsIndirectInputEvents
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Example/Example/UI Components/Order Image Gallery/OrderGalleryImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderImage.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 19/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 | import ConstraintKit
11 |
12 | final class OrderGalleryImage: UIView {
13 |
14 | // MARK: - Init
15 |
16 | private let imageView = UIImageView().with({
17 | $0.contentMode = .scaleAspectFill
18 | $0.clipsToBounds = true
19 | $0.setCornerRadius(5)
20 | })
21 |
22 | private let label = UILabel().with({
23 | $0.textColor = .white
24 | $0.textAlignment = .center
25 | $0.font = .systemFont(ofSize: 18, weight: .semibold)
26 | $0.backgroundColor = .init(white: 0.0, alpha: 0.8)
27 | $0.clipsToBounds = true
28 | $0.setCornerRadius(5)
29 | })
30 |
31 | // MARK: - Init
32 |
33 | init(image: UIImage?, overlayText: String? = nil){
34 | super.init(frame: .zero)
35 | setConstraints()
36 | imageView.image = image
37 | label.text = overlayText
38 | label.isHidden = overlayText == nil
39 | }
40 |
41 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
42 |
43 | // MARK: - Configure
44 |
45 | private func setConstraints(){
46 | ZStack {
47 | imageView
48 | label
49 | }.withAspectRatio(0.85, priority: .init(999))
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Example/Example/UI Components/Order Image Gallery/OrderGalleryImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderImageGalleryView.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 19/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 | import SharkUtils
11 |
12 |
13 | final class OrderGalleryImageView: UIView {
14 |
15 | // MARK: - UI
16 |
17 | private let label = UILabel().with {
18 | $0.font = .systemFont(ofSize: 11.5, weight: .bold)
19 | $0.textColor = .white
20 | $0.textAlignment = .center
21 | }
22 |
23 | private let imageStack = UIStackView().with({
24 | $0.spacing = 7
25 | $0.distribution(.fillEqually)
26 | })
27 |
28 | // MARK: -
29 |
30 | private let imagesLimit = 4
31 | private let imagesAspect = 0.9
32 |
33 | // MARK: - Init
34 |
35 | init(){
36 | super.init(frame: .zero)
37 | configure()
38 | setConstraints()
39 | }
40 |
41 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
42 |
43 | // MARK: - Interface
44 |
45 | var images: [String]? {
46 | didSet {
47 | imageStack.removeAllArrangedSubviews()
48 | imageStack.addArrangedSubViews(generateImages() ?? [])
49 | }
50 | }
51 |
52 | // MARK: - Configure
53 |
54 | private func configure(){
55 | backgroundColor = .clear
56 | }
57 |
58 | private func setConstraints(){
59 | VStack {
60 | imageStack
61 | }
62 | }
63 |
64 | // MARK: - Builders
65 |
66 | private func generateImages() -> [UIView]? {
67 | return images?.prefix(imagesLimit).enumerated().map({ (index, name) -> UIView in
68 | if index == imagesLimit - 1 {
69 | let imageCount = ((images?.count ?? 0) - index)
70 | let overlayText = (imageCount == 0) ? nil : "+\(imageCount)"
71 |
72 | return OrderGalleryImage(image: UIImage(named: name), overlayText: overlayText)
73 |
74 | } else {
75 | return OrderGalleryImage (image: UIImage(named: name))
76 | }
77 | })
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Example/Example/UI Components/Tag View/TagView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagView.swift
3 | // Example
4 | //
5 | // Created by Russell Warwick on 18/07/2021.
6 | //
7 |
8 | import UIKit
9 | import StackKit
10 | import SharkUtils
11 | import ConstraintKit
12 |
13 | final class TagView: UIView {
14 |
15 | // MARK: - UI
16 |
17 | private let label = UILabel().with {
18 | $0.font = .systemFont(ofSize: 11.5, weight: .bold)
19 | $0.textColor = .white
20 | $0.textAlignment = .center
21 | }
22 |
23 | // MARK: - Init
24 |
25 | init(){
26 | super.init(frame: .zero)
27 | configure()
28 | setConstraints()
29 | }
30 |
31 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
32 |
33 | // MARK: - Interface
34 |
35 | var text: String? {
36 | didSet {
37 | label.text = text
38 | }
39 | }
40 |
41 | // MARK: - Configure
42 |
43 | private func configure(){
44 | backgroundColor = .init(white: 0.15, alpha: 0.95)
45 | alpha = 0.95
46 | setCornerRadius(4)
47 | }
48 |
49 | private func setConstraints(){
50 | VStack {
51 | label
52 | }.margin(.horizontal(12))
53 | .withHeight(30)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Gymshark
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "ConstraintKit",
6 | "repositoryURL": "https://github.com/gymshark/ios-constraint-kit.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "20069bdb8db549339d12af7836913e00597aa6d8",
10 | "version": "0.0.2"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "StackKit",
8 | platforms: [ .iOS(.v9)],
9 | products: [
10 | .library(
11 | name: "StackKit",
12 | targets: ["StackKit"]),
13 | ],
14 | dependencies: [
15 | .package(name: "ConstraintKit",
16 | url: "https://github.com/gymshark/ios-constraint-kit.git",
17 | .upToNextMinor(from: "1.0.0"))
18 | ],
19 | targets: [
20 | .target(
21 | name: "StackKit",
22 | dependencies: ["ConstraintKit"]),
23 | .testTarget(
24 | name: "StackKitTests",
25 | dependencies: ["StackKit"]),
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Introduction
4 |
5 | StackKit harnesses the power of [UIStackView's](https://developer.apple.com/documentation/uikit/uistackview) and Swift's [ResultBuilders](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) together. This allows you to easily build declarative UI while only using [UIKit](https://developer.apple.com/documentation/uikit). StackKit is a great solution for anyone looking to level up their programmatic UI without having to convert an existing project to SwiftUI. Its API design is closely matched to SwiftUI making it effortless to pick up without having to accommodate any reactive programming patterns.
6 |
7 | ## Examples
8 |
9 | | Feed | Account |
10 | | :------------------------------------------------: | :--------------------------------------------: |
11 | |  |  |
12 |
13 | ## Why StackKit?
14 |
15 | The main advantage StackKit has over a standard programmatic constraints-based approach is maintenance and flexibility. You can quickly change your layout by simply moving the ordering of a couple of views. No more painful rearranging of anchors e.c.t. You can also evaluate conditionals within the UI code, adding boolean properties, loops maps, and more.
16 |
17 | We've made some Example screens and code samples would definitely recommend starting there to grasp how powerful and simplistic StackKit is. We hope to see it in your projects in the near future 🙂
18 |
19 | StackKit is built and maintained with love by the Gymshark Engineering Team 💙📱
20 |
21 | ### Why would you use StackKit instead of SwiftUI
22 |
23 | - You still want to use UIKit throughout your app
24 | - Harness the power of building UI like SwiftUI does but your team/project isn’t ready for reactive programming.
25 | - It’s easier to integrate into existing code bases. With SwiftUI you can’t swap a **View** for a **UIView**. This makes it hard to integrate old UI components into SwiftUI.
26 | - SwiftUI hides the underlying view rendering, therefore making it hard to see what is actually going on and refine issues. With StackKit you are still within UIKit so don't lose any abilities.
27 |
28 | ### Note ⚠️
29 |
30 | If you are trying to use StackKit to handle large data sets to replace a UITableView or UICollection view you will run into performance issues. For this usecase you should use [ListKit](https://github.com/gymshark/ios-list-kit) 🦈🗒
31 |
32 | ## Installation
33 |
34 | **Swift Package Manager (SPM)**
35 |
36 | To install StackKit using [Swift Package Manager](https://github.com/apple/swift-package-manager) you can follow the [tutorial published by Apple](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) using the URL for the StackKit repo with the current version:
37 |
38 | \1. In Xcode, select “File” → “Swift Packages” → “Add Package Dependency”
39 |
40 | \2. Enter https://github.com/gymshark/ios-stack-kit.git
41 |
42 | ## Getting Started
43 |
44 | StackKit works by extending off of any `UIView`, adding a `UIStackView` to the parents' subview, setting the constraints, and then appending the views to the stack provided to it via the [ResultBuilder](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) This process makes laying out you UI programmatically effortless and because it is simply using [UIStackView](https://developer.apple.com/documentation/uikit/uistackview) underneath, all of the same layout logic still applies.
45 |
46 | If you haven't used UIStackView's before or need a refresher, I highly recommend checking out this [article](https://www.raywenderlich.com/2198310-uistackview-tutorial-for-ios-introducing-stack-views). Result builders are also key to the power of StackKit as well as SwiftUI. To get a better understanding of them check out this [article](https://www.swiftbysundell.com/articles/deep-dive-into-swift-function-builders/). They are awesome 🔥.
47 |
48 | With StackKit there are 4 main types of Stacks that you can use. VStack, HStack, VScroll and HScroll. Here are some common code examples below to help you get started with StackKit by extending off your UIView's.
49 |
50 | #### UIViewController
51 |
52 | ---
53 |
54 |
55 |
56 | ```swift
57 | func viewDidLoad(){
58 | view.VStack {
59 | avatar
60 | displayName
61 | friendStatus
62 | Spacer() // Fills rest of space
63 | }
64 | }
65 | ```
66 |
67 | ---
68 |
69 | #### UIView
70 |
71 | ---
72 |
73 |
74 |
75 | ```swift
76 | class ProfileView: UIView {
77 | init(){
78 | HStack {
79 | avatar
80 | displayName
81 | friendStatus
82 | }
83 | }
84 | }
85 | ```
86 |
87 | ---
88 |
89 | ## Stack's within stack's... ([Stackception](Assets/stackwithinastack.png) 🤯)
90 |
91 | Above you can see how extending off a UIView and placing UIView items within a Stack is a relatively straightforward process. But what about putting a stack within a stack? Because our result builder is expecting type is a UIView, we can simply pass in another Stack which inturn also excepts UIViews. But, for this, we need to use a Class variant of the Stack instead of extending off of a UIView. Thankfully this is all taken care of and you don't need to do anything differently. Here is a nice example of stacks within stacks.
92 |
93 | ---
94 |
95 |
96 |
97 | ```swift
98 | class ProductsView: UIView {
99 | init(){
100 | HStack {
101 | VStack {
102 | title1
103 | product1
104 | HStack {
105 | statusIcon1
106 | statusLabel1
107 | }
108 | orderButton1
109 | }
110 | VStack {
111 | title2
112 | product2
113 | HStack {
114 | statusIcon2
115 | statusLabel2
116 | }
117 | orderButton2
118 | }
119 | }
120 | }
121 | }
122 | ```
123 |
124 | ## Layout and customisation
125 |
126 | You might be asking but how do I control the layout, what about sizing constraints and setting spacing and padding. Don't worry we have you covered.
127 |
128 | 📝 Note: StackKit comes with a handy repo called [ConstraintsKit](https://github.com/gymshark/ios-constraint-kit). It makes writing Anchors, CGSize, UIEdgeInsets far easier with less boilerplate and some nice little extensions.
129 |
130 | ### Advanced stack layout
131 |
132 | To have greater control over how a particular Stack handles the layout of its views you can access its `alignment` and `distribution` properties by chaining off the Stack. You can find out more about how to best utilize these properties for your specific needs in these articles.
133 |
134 | • https://www.hackingwithswift.com/example-code/uikit/what-are-the-different-uistackview-distribution-types
135 | • https://www.raywenderlich.com/2198310-uistackview-tutorial-for-ios-introducing-stack-views
136 |
137 | ```swift
138 | VStack {
139 | displayName
140 |
141 | VStack {
142 | bioLabel
143 | HStack {
144 | skills.compactMap({ SkillView(skill: $0) })
145 | }.distribution(.fillEqually)
146 | .alignment(.top)
147 | }
148 |
149 | prices.compactMap({ SkillView(skill: $0) })
150 | Spacer()
151 |
152 | }.distribution(.fill)
153 | .alignment(.center)
154 | ```
155 |
156 | ### Spacing
157 |
158 | At some point, you will want to provide some spacing between your items. Thankfully with UIStackViews this is simple. We can chain off the specific stack using the `.spacing` function. As well as this we can also add some unique spacing in places where required using the `Spacing` class. It's worth noting that you will want to use this class when you would like the remaining area in a Stack to be taken up by an empty view.
159 |
160 | ---
161 |
162 |
163 |
164 | ```swift
165 | VStack {
166 | displayName
167 | tagline
168 | favouriteColor
169 | Spacer(h: 10)
170 |
171 | HStack {
172 | skills.compactMap({ SkillView(skill: $0) })
173 | }.spacing(150)
174 |
175 | Spacer(h: 20)
176 |
177 | seporator
178 |
179 | Spacer()
180 | }.spacing(5)
181 |
182 | ```
183 |
184 | ---
185 |
186 | ### Margin
187 |
188 | Applying padding around a stack is made a lot easier with UIStackViews `layoutMargins` which takes a UIEdgeInset property. To use this with your stack you can just chain off the stack again using the `.margin()` function. This provides a great way of giving spacing around views without lots of complex layout code.
189 |
190 | ---
191 |
192 |
193 |
194 | ```swift
195 | VStack {
196 | displayName
197 | tagline
198 | favouriteColor
199 | Spacer(h: 10)
200 | HStack {
201 | skills.compactMap({ SkillView(skill: $0) })
202 | }.spacing(5)
203 | .margin(.horizontal(10))
204 |
205 | Spacer()
206 | }.margin(.init(top: 20, left: 10, bottom: 20, right: 10)
207 |
208 | ```
209 |
210 | ---
211 |
212 | ### Sizing
213 |
214 | Sizing of views can still be achieved using standard NSLayout anchors width and height. Although we prefer to use some handy extensions to make this a lot easier from [ConstraintsKit](https://github.com/gymshark/ios-constraint-kit). Simply chain off any view you want to apply an explicit size to using `.with()` notation. You will notice we have width, height, and square extensions available. Because we are using stacks you don't need to worry about your other views. They will expand and contract where needed. You can even apply sizing to your stacks if you wish to.
215 |
216 | ---
217 |
218 |
219 |
220 | ```swift
221 |
222 |
223 | VStack {
224 |
225 | displayName.withHeight(120)
226 |
227 | profilePicture.withSquare(150)
228 |
229 | footer.withSize(w: 300, h: 150)
230 |
231 | }.alignment(.center)
232 | .margin(.all(20)
233 |
234 |
235 | ```
236 |
237 | ---
238 |
239 |
240 | ## Use of result builders
241 |
242 | With the power of result builders, we can also add conditional parameters and add things such as compact maps and for loops easily. This makes writing coding features a little bit easier. Some use cases might be, Feature Toggling, Hiding Views, easily applying arrays to the stacks.
243 |
244 | ---
245 |
246 | ```swift
247 | VStack {
248 | if premiumUser {
249 | PremiumBadge(name: "Russell")
250 | }
251 |
252 | userInfo.map({
253 | UserTag(text: $0)
254 | )}
255 |
256 | HStack {
257 | (0..10).map { (id: Int) in
258 | NumberView(number: id)
259 | }
260 | }
261 |
262 | }.alignment(.center)
263 |
264 |
265 | ```
266 |
267 | ---
268 |
269 |
270 |
--------------------------------------------------------------------------------
/Sources/StackKit/Stacks/Helpers/Spacer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Spacer.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 03/07/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | public final class Spacer: UIView {
11 |
12 | public static func auto() -> Spacer {
13 | let s = Spacer()
14 | s.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal)
15 | s.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
16 | s.setContentCompressionResistancePriority(.fittingSizeLevel, for: .horizontal)
17 | s.setContentCompressionResistancePriority(.fittingSizeLevel, for: .vertical)
18 | return s
19 | }
20 |
21 | // MARK: - Init
22 |
23 | public init() {
24 | super.init(frame: .zero)
25 | }
26 |
27 | public init(square: CGFloat) {
28 | super.init(frame: .zero)
29 | translatesAutoresizingMaskIntoConstraints = false
30 | widthAnchor.constraint(equalToConstant: square).isActive = true
31 | heightAnchor.constraint(equalToConstant: square).isActive = true
32 | }
33 |
34 | public init(w: CGFloat) {
35 | super.init(frame: .zero)
36 | translatesAutoresizingMaskIntoConstraints = false
37 | widthAnchor.constraint(equalToConstant: w).isActive = true
38 | }
39 |
40 | public init(h: CGFloat) {
41 | super.init(frame: .zero)
42 | translatesAutoresizingMaskIntoConstraints = false
43 | heightAnchor.constraint(equalToConstant: h).isActive = true
44 | }
45 |
46 | required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/StackKit/Stacks/Helpers/UIViewHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewHelpers.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 03/07/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIView {
11 | @discardableResult
12 | func withBackground(_ color: UIColor) -> Self {
13 | backgroundColor = color
14 | return self
15 | }
16 | }
17 |
18 | extension UIStackView {
19 | func addArrangedSubViews(_ views: [UIView]) {
20 | views.forEach { addArrangedSubview($0) }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/StackKit/Stacks/Scroll/ScrollStacks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollStacks.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 02/05/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | // MARK: - UIView Extension Variants
11 |
12 | public extension UIView {
13 |
14 | @discardableResult
15 | func VScroll(useSafeArea: Bool = true, @UIViewBuilder views: () -> [UIView]) -> ScrollViewBuilder {
16 | let scrollView = ScrollViewBuilder(axis: .vertical, views: views)
17 |
18 | VStack(useSafeArea: useSafeArea) { scrollView }
19 |
20 | return scrollView
21 | }
22 |
23 | @discardableResult
24 | func HScroll(useSafeArea: Bool = true, @UIViewBuilder views: () -> [UIView]) -> ScrollViewBuilder {
25 | let scrollView = ScrollViewBuilder(axis: .horizontal, views: views)
26 |
27 | VStack(useSafeArea: useSafeArea) { scrollView }
28 |
29 | return scrollView
30 | }
31 | }
32 |
33 | // MARK: - Class Variants
34 |
35 | public class HScroll: ScrollViewBuilder {
36 | public init(@UIViewBuilder views: () -> [UIView]) {
37 | super.init(axis: .horizontal, views: views)
38 | }
39 |
40 | public required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
41 | }
42 |
43 | public class VScroll: ScrollViewBuilder {
44 | public init(@UIViewBuilder views: () -> [UIView]) {
45 | super.init(axis: .vertical, views: views)
46 | }
47 |
48 | public required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/StackKit/Stacks/Scroll/ScrollViewBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollViewBuilder.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 04/07/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | open class ScrollViewBuilder: UIView {
11 |
12 | // MARK: - UI
13 |
14 | private let scrollView: UIScrollView = {
15 | let sv = UIScrollView()
16 | sv.translatesAutoresizingMaskIntoConstraints = false
17 | sv.showsHorizontalScrollIndicator = false
18 | sv.showsVerticalScrollIndicator = false
19 | return sv
20 | }()
21 |
22 | private let stackView: UIStackView = {
23 | let sv = UIStackView()
24 | sv.alignment = .fill
25 | sv.translatesAutoresizingMaskIntoConstraints = false
26 | return sv
27 | }()
28 |
29 | // MARK: - Init
30 |
31 | public init(axis: NSLayoutConstraint.Axis, @UIViewBuilder views: () -> [UIView]) {
32 |
33 | super.init(frame: .zero)
34 |
35 | addSubview(scrollView)
36 | scrollView.addSubview(stackView)
37 |
38 | if axis == .vertical {
39 | stackView.axis = .vertical
40 | stackView.distribution = .equalSpacing
41 | stackView.widthAnchor.constraint(equalTo:scrollView.widthAnchor).isActive = true
42 |
43 | } else {
44 | stackView.axis = .horizontal
45 | stackView.distribution = .fillEqually
46 | stackView.alignment = .center
47 | stackView.heightAnchor.constraint(equalTo:scrollView.heightAnchor).isActive = true
48 | }
49 |
50 | NSLayoutConstraint.activate([
51 | scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
52 | scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
53 | scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
54 | scrollView.topAnchor.constraint(equalTo:topAnchor),
55 |
56 | stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
57 | stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
58 | stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
59 | stackView.topAnchor.constraint(equalTo: scrollView.topAnchor)
60 | ])
61 |
62 | stackView.addArrangedSubViews(views())
63 | }
64 |
65 | required public init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
66 | }
67 |
68 | public extension ScrollViewBuilder {
69 |
70 | @discardableResult
71 | func spacing(_ spacing: CGFloat) -> Self {
72 | stackView.spacing = spacing
73 | return self
74 | }
75 |
76 | @discardableResult
77 | func margin(_ margins: UIEdgeInsets) -> Self {
78 | stackView.margin(margins)
79 | return self
80 | }
81 |
82 | @discardableResult
83 | func alignment(_ alignment: UIStackView.Alignment) -> Self {
84 | stackView.alignment = alignment
85 | return self
86 | }
87 |
88 | @discardableResult
89 | func showVerticalIndicators(_ value: Bool) -> Self {
90 | scrollView.showsVerticalScrollIndicator = value
91 | return self
92 | }
93 |
94 | @discardableResult
95 | func showHorizontalIndicators(_ value: Bool) -> Self {
96 | scrollView.showsHorizontalScrollIndicator = value
97 | return self
98 | }
99 |
100 | @discardableResult
101 | func alwaysBounceHorizontal(_ value: Bool) -> Self {
102 | scrollView.alwaysBounceHorizontal = value
103 | return self
104 | }
105 |
106 | @discardableResult
107 | func alwaysBounceVertical(_ value: Bool) -> Self {
108 | scrollView.alwaysBounceVertical = value
109 | return self
110 | }
111 |
112 | @discardableResult
113 | func scrollDelegate(_ delegate: UIScrollViewDelegate) -> Self {
114 | scrollView.delegate = delegate
115 | return self
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Sources/StackKit/Stacks/Stacks/StackBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StacksHelpers.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 04/07/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIView {
11 |
12 | @discardableResult
13 | func createStack(_ axis: NSLayoutConstraint.Axis = .vertical,
14 | views: [UIView], spacing: CGFloat = .zero,
15 | alignment: UIStackView.Alignment = .fill,
16 | distribution: UIStackView.Distribution = .fill,
17 | useSafeArea: Bool) -> UIStackView {
18 |
19 | let stackView = UIStackView(arrangedSubviews: views)
20 | stackView.axis = axis
21 | stackView.spacing = spacing
22 | stackView.alignment = alignment
23 | stackView.distribution = distribution
24 | addSubview(stackView)
25 |
26 | stackView.translatesAutoresizingMaskIntoConstraints = false
27 |
28 | if #available(iOS 11.0, *), useSafeArea {
29 | NSLayoutConstraint.activate([
30 | stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: .zero),
31 | stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: .zero),
32 | stackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: .zero),
33 | stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: .zero)
34 | ])
35 | } else {
36 | NSLayoutConstraint.activate([
37 | stackView.topAnchor.constraint(equalTo: topAnchor, constant: .zero),
38 | stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: .zero),
39 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: .zero),
40 | stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: .zero)
41 | ])
42 | }
43 |
44 | return stackView
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/StackKit/Stacks/Stacks/Stacks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stacks.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 01/05/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | // MARK: - UIView Extension Variants
11 |
12 | public extension UIView {
13 |
14 | @discardableResult
15 | func ZStack(useSafeArea: Bool = true, @UIViewBuilder views: () -> [UIView]) -> UIStackView {
16 | let container = UIView()
17 |
18 | views().forEach { view in
19 | container.VStack { view }
20 | }
21 |
22 | return VStack { container }
23 | }
24 |
25 | @discardableResult
26 | func VStack(spacing: CGFloat = .zero, useSafeArea: Bool = true, @UIViewBuilder views: () -> [UIView]) -> UIStackView {
27 | return createStack(.vertical, views: views(), spacing: spacing, alignment: .fill, distribution: .fill, useSafeArea: useSafeArea)
28 | }
29 |
30 | @discardableResult
31 | func HStack(spacing: CGFloat = .zero, useSafeArea: Bool = true, @UIViewBuilder views: () -> [UIView]) -> UIStackView {
32 | return createStack(.horizontal, views: views(), spacing: spacing, alignment: .fill, distribution: .fill, useSafeArea: useSafeArea)
33 | }
34 | }
35 |
36 | // MARK: - Class Variants
37 |
38 | public final class ZStack: UIStackView {
39 | public init(@UIViewBuilder views: () -> [UIView]) {
40 | super.init(frame: .zero)
41 |
42 | let container = UIView()
43 |
44 | views().forEach { view in
45 | container.VStack { view }
46 | }
47 |
48 | addArrangedSubview(container)
49 | }
50 |
51 | required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
52 | }
53 |
54 | public final class VStack: UIStackView {
55 | public init(spacing: CGFloat = .zero, @UIViewBuilder views: () -> [UIView]) {
56 | super.init(frame: .zero)
57 | self.axis = .vertical
58 | self.spacing = spacing
59 | self.addArrangedSubViews(views())
60 | }
61 |
62 | required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
63 | }
64 |
65 | public final class HStack: UIStackView {
66 | public init(spacing: CGFloat = .zero, @UIViewBuilder views: () -> [UIView]) {
67 | super.init(frame: .zero)
68 | self.axis = .horizontal
69 | self.spacing = spacing
70 | self.addArrangedSubViews(views())
71 | }
72 |
73 | required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
74 | }
75 |
76 | // MARK: - Extensions
77 |
78 | public extension UIStackView {
79 |
80 | @discardableResult
81 | func margin(_ margins: UIEdgeInsets) -> UIStackView {
82 | layoutMargins = margins
83 | isLayoutMarginsRelativeArrangement = true
84 | return self
85 | }
86 |
87 | @discardableResult
88 | func alignment(_ alignment: UIStackView.Alignment) -> UIStackView {
89 | self.alignment = alignment
90 | return self
91 | }
92 |
93 | @discardableResult
94 | func spacing(_ spacing: CGFloat) -> UIStackView {
95 | self.spacing = spacing
96 | return self
97 | }
98 |
99 | @discardableResult
100 | func distribution(_ distribution: UIStackView.Distribution) -> UIStackView {
101 | self.distribution = distribution
102 | return self
103 | }
104 |
105 | @discardableResult
106 | func withBackground(_ color: UIColor, cornerRadius: CGFloat = .zero) -> UIStackView {
107 | let view = UIView(frame: bounds)
108 | view.backgroundColor = color
109 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
110 | view.layer.cornerRadius = cornerRadius
111 | if #available(iOS 11.0, *) {
112 | view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
113 | }
114 | insertSubview(view, at: 0)
115 | return self
116 | }
117 |
118 | @discardableResult
119 | func removeAllArrangedSubviews() -> [UIView] {
120 | return arrangedSubviews.reduce([]) { (removedSubviews, subview) -> [UIView] in
121 | removeArrangedSubview(subview)
122 | NSLayoutConstraint.deactivate(subview.constraints)
123 | subview.removeFromSuperview()
124 | return removedSubviews + [subview]
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/StackKit/UIViewBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewBuilder.swift
3 | //
4 | //
5 | // Created by Russell Warwick on 01/05/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | @resultBuilder
11 | public struct UIViewBuilder {
12 |
13 | public typealias Expression = UIView
14 | public typealias Component = [UIView]
15 |
16 | public static func buildExpression(_ expression: Expression) -> Component {
17 | return [expression]
18 | }
19 |
20 | public static func buildExpression(_ expression: Component) -> Component {
21 | return expression
22 | }
23 |
24 | public static func buildExpression(_ expression: Expression?) -> Component {
25 | guard let expression = expression else { return [] }
26 | return [expression]
27 | }
28 |
29 | public static func buildBlock(_ children: Component...) -> Component {
30 | return children.flatMap { $0 }
31 | }
32 |
33 | public static func buildBlock(_ component: Component) -> Component {
34 | return component
35 | }
36 |
37 | public static func buildOptional(_ children: Component?) -> Component {
38 | return children ?? []
39 | }
40 |
41 | public static func buildEither(first child: Component) -> Component {
42 | return child
43 | }
44 |
45 | public static func buildEither(second child: Component) -> Component {
46 | return child
47 | }
48 |
49 | public static func buildArray(_ components: [Component]) -> Component {
50 | return components.flatMap { $0 }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/StackKit.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint StackKit.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'StackKit'
11 | s.version = '0.1.0'
12 | s.summary = 'The power of SwiftUI with UIKit'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = <<-DESC
21 | StackKit harnesses the power of UIStackView's and Swift's ResultBuilders together. This allows you to easily build declarative UI while only using UIKit. StackKit is a great solution for anyone looking to level up their programmatic UI without having to convert an existing project to SwiftUI. Its API design is closely matched to SwiftUI making it effortless to pick up without having to accommodate any reactive programming patterns.
22 | DESC
23 |
24 | s.homepage = 'https://github.com/gymshark/StackKit'
25 | s.screenshots = 'https://github.com/gymshark/ios-stack-kit/raw/master/Assets/example-1.png', 'https://github.com/gymshark/ios-stack-kit/raw/master/Assets/example-2.png'
26 | s.license = { :type => 'MIT', :file => 'LICENSE' }
27 | s.author = { 'gymshark' => 'engineering@gymshark.com' }
28 | s.source = { :git => 'https://github.com/gymshark/ios-stack-kit', :tag => s.version.to_s }
29 | # s.social_media_url = 'https://twitter.com/'
30 |
31 | s.ios.deployment_target = '9.0'
32 |
33 | s.source_files = 'Sources/StackKit/**/*'
34 |
35 | # s.resource_bundles = {
36 | # 'StackKit' => ['StackKit/Assets/*.png']
37 | # }
38 |
39 | # s.public_header_files = 'Pod/Classes/**/*.h'
40 | # s.frameworks = 'UIKit', 'MapKit'
41 | # s.dependency 'AFNetworking', '~> 2.3'
42 | s.dependency 'ConstraintKit'
43 | end
44 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import StackKitTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += StackKitTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/StackKitTests/StackKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import StackKit
3 |
4 | final class StackKitTests: XCTestCase {
5 | func testStart(){
6 | XCTAssert(true)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Tests/StackKitTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(StackKitTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------