├── AppStore
├── Assets.xcassets
│ ├── Contents.json
│ ├── food.imageset
│ │ ├── food.jpeg
│ │ └── Contents.json
│ ├── desert.imageset
│ │ ├── desert.jpeg
│ │ └── Contents.json
│ ├── temple.imageset
│ │ ├── temple.jpeg
│ │ └── Contents.json
│ ├── oldTemple.imageset
│ │ ├── oldTemple.jpeg
│ │ └── Contents.json
│ ├── containers.imageset
│ │ ├── containers.jpeg
│ │ └── Contents.json
│ ├── KoreanTemple.imageset
│ │ ├── KoreanTemple.jpeg
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Models
│ ├── Category.swift
│ ├── App.swift
│ └── Section.swift
├── Cells
│ ├── Protocols.swift
│ ├── SmallCategoryCell.swift
│ ├── SmallAppCell.swift
│ ├── FeaturedCell.swift
│ └── MediumAppCell.swift
├── Misc
│ └── UICollectionViewCell+Identifier.swift
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── SupplementaryViews
│ └── SectionHeader.swift
├── Info.plist
├── SceneDelegate.swift
├── AppsPresenter.swift
└── ViewController.swift
└── AppStore.xcodeproj
├── project.xcworkspace
├── contents.xcworkspacedata
├── xcuserdata
│ └── bob.xcuserdatad
│ │ └── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── xcuserdata
└── bob.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
└── project.pbxproj
/AppStore/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/food.imageset/food.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/Compositional-Layout-AppStore/develop/AppStore/Assets.xcassets/food.imageset/food.jpeg
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/desert.imageset/desert.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/Compositional-Layout-AppStore/develop/AppStore/Assets.xcassets/desert.imageset/desert.jpeg
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/temple.imageset/temple.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/Compositional-Layout-AppStore/develop/AppStore/Assets.xcassets/temple.imageset/temple.jpeg
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/oldTemple.imageset/oldTemple.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/Compositional-Layout-AppStore/develop/AppStore/Assets.xcassets/oldTemple.imageset/oldTemple.jpeg
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/containers.imageset/containers.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/Compositional-Layout-AppStore/develop/AppStore/Assets.xcassets/containers.imageset/containers.jpeg
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/KoreanTemple.imageset/KoreanTemple.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/Compositional-Layout-AppStore/develop/AppStore/Assets.xcassets/KoreanTemple.imageset/KoreanTemple.jpeg
--------------------------------------------------------------------------------
/AppStore.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AppStore.xcodeproj/project.xcworkspace/xcuserdata/bob.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/AppStore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/AppStore/Models/Category.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Category.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | struct Category: SectionData {
13 | let id: Int
14 | let name: String
15 | let icon: UIImage
16 | }
17 |
--------------------------------------------------------------------------------
/AppStore/Cells/Protocols.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Protocols.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol AppConfigurable {
12 | func configure(with app: App)
13 | }
14 |
15 | protocol CategoryConfigurable {
16 | func configure(with category: Category)
17 | }
18 |
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/food.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "food.jpeg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/desert.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "desert.jpeg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/temple.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "temple.jpeg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/containers.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "containers.jpeg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/oldTemple.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "oldTemple.jpeg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/KoreanTemple.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "KoreanTemple.jpeg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/AppStore.xcodeproj/xcuserdata/bob.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | AppStore.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/AppStore/Models/App.swift:
--------------------------------------------------------------------------------
1 | //
2 | // App.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | // Token protocol to avoid using "Any" in the dataSource, as we are working with App and Category
13 | protocol SectionData { }
14 |
15 |
16 | struct App: SectionData {
17 | let id: Int
18 | let type: String
19 | let name: String
20 | let subTitle: String
21 | let image: UIImage
22 | let hasIAP: Bool
23 | }
24 |
--------------------------------------------------------------------------------
/AppStore/Misc/UICollectionViewCell+Identifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionViewCell+Identifier.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /*
12 | Simple extension that adds the "identifier" property to all UICollectionReusableView,
13 | it uses its own class name as the identifier so it should always be unique
14 | */
15 |
16 | extension UICollectionReusableView {
17 | static var identifier: String {
18 | return "\(self)"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/AppStore/Models/Section.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Section.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /*
12 | The Section type will decide what layout the UICollectionView will use for that section
13 | */
14 | enum SectionType: Int, CaseIterable {
15 | case singleList // Featured
16 | case doubleList // This weeks favorites
17 | case tripleList // Learn something
18 | case categoryList // Top Categories
19 | }
20 |
21 | // Descibes the info needed for a Section
22 | struct Section {
23 | let id: Int
24 | let type: SectionType
25 | let title: String?
26 | let subtitle: String?
27 | let data: [SectionData]
28 | }
29 |
--------------------------------------------------------------------------------
/AppStore/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/AppStore/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 |
--------------------------------------------------------------------------------
/AppStore/SupplementaryViews/SectionHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionHeader.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SectionHeader: UICollectionReusableView {
12 |
13 | let titleLabel = UILabel()
14 | let subtitleLabel = UILabel()
15 |
16 | override init(frame: CGRect) {
17 | super.init(frame: frame)
18 | setup()
19 | }
20 |
21 | required init?(coder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | private func setup() {
26 | let separator = UIView(frame: .zero)
27 | separator.translatesAutoresizingMaskIntoConstraints = false
28 | separator.backgroundColor = .quaternaryLabel
29 |
30 | let stackView = UIStackView(arrangedSubviews: [separator, titleLabel, subtitleLabel])
31 | stackView.translatesAutoresizingMaskIntoConstraints = false
32 | stackView.axis = .vertical
33 | addSubview(stackView)
34 |
35 | NSLayoutConstraint.activate([
36 | separator.heightAnchor.constraint(equalToConstant: 1),
37 |
38 | stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
39 | stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
40 | stackView.topAnchor.constraint(equalTo: topAnchor),
41 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10)
42 | ])
43 |
44 | stackView.setCustomSpacing(10, after: separator)
45 |
46 | style()
47 | }
48 |
49 | private func style() {
50 | titleLabel.textColor = .label
51 | titleLabel.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .bold))
52 | subtitleLabel.textColor = .secondaryLabel
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/AppStore/Cells/SmallCategoryCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SmallCategoryCell.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SmallCategoryCell: UICollectionViewCell, CategoryConfigurable {
12 |
13 | let nameLabel = UILabel()
14 | let imageView = UIImageView()
15 |
16 | override init(frame: CGRect) {
17 | super.init(frame: frame)
18 |
19 | setup()
20 | }
21 |
22 | required init?(coder: NSCoder) {
23 | fatalError("init(coder:) has not been implemented")
24 | }
25 |
26 | private func setup() {
27 | let stackView = UIStackView(arrangedSubviews: [imageView, nameLabel])
28 | stackView.alignment = .center
29 | stackView.spacing = 12
30 |
31 | stackView.translatesAutoresizingMaskIntoConstraints = false
32 | contentView.addSubview(stackView)
33 |
34 | NSLayoutConstraint.activate([
35 | stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
36 | stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
37 | stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
38 | stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
39 | imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
40 | imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor)
41 | ])
42 |
43 | style()
44 | }
45 |
46 | private func style() {
47 | nameLabel.font = UIFont.preferredFont(forTextStyle: .title3)
48 | nameLabel.textColor = .label
49 |
50 | imageView.layer.cornerRadius = 5
51 | imageView.clipsToBounds = true
52 | }
53 |
54 | func configure(with category: Category) {
55 | nameLabel.text = category.name
56 | imageView.image = category.icon
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/AppStore/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/AppStore/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 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/AppStore/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let _ = (scene as? UIWindowScene) else { return }
21 | }
22 |
23 | func sceneDidDisconnect(_ scene: UIScene) {
24 | // Called as the scene is being released by the system.
25 | // This occurs shortly after the scene enters the background, or when its session is discarded.
26 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
28 | }
29 |
30 | func sceneDidBecomeActive(_ scene: UIScene) {
31 | // Called when the scene has moved from an inactive state to an active state.
32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
33 | }
34 |
35 | func sceneWillResignActive(_ scene: UIScene) {
36 | // Called when the scene will move from an active state to an inactive state.
37 | // This may occur due to temporary interruptions (ex. an incoming phone call).
38 | }
39 |
40 | func sceneWillEnterForeground(_ scene: UIScene) {
41 | // Called as the scene transitions from the background to the foreground.
42 | // Use this method to undo the changes made on entering the background.
43 | }
44 |
45 | func sceneDidEnterBackground(_ scene: UIScene) {
46 | // Called as the scene transitions from the foreground to the background.
47 | // Use this method to save data, release shared resources, and store enough scene-specific state information
48 | // to restore the scene back to its current state.
49 | }
50 |
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/AppStore/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/AppStore/Cells/SmallAppCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SmallAppCell.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SmallAppCell: UICollectionViewCell, AppConfigurable {
12 |
13 | let imageView = UIImageView()
14 | let nameLabel = UILabel()
15 | let subtitleLabel = UILabel()
16 | let buyButton = UIButton(type: .custom)
17 | let iapLabel = UILabel()
18 |
19 | override init(frame: CGRect) {
20 | super.init(frame: frame)
21 | setup()
22 | }
23 |
24 | private func setup() {
25 |
26 | let textStackView = UIStackView(arrangedSubviews: [nameLabel, subtitleLabel])
27 | textStackView.axis = .vertical
28 | textStackView.alignment = .leading
29 | textStackView.distribution = .fill
30 |
31 | let buttonStackView = UIStackView(arrangedSubviews: [buyButton, iapLabel])
32 | buttonStackView.axis = .vertical
33 | buttonStackView.alignment = .center
34 |
35 | let stackView = UIStackView(arrangedSubviews: [imageView, textStackView, buttonStackView])
36 | stackView.spacing = 10
37 |
38 | stackView.translatesAutoresizingMaskIntoConstraints = false
39 | contentView.addSubview(stackView)
40 |
41 | NSLayoutConstraint.activate([
42 | stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
43 | stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
44 | stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
45 | stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
46 | imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
47 | imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
48 | buttonStackView.widthAnchor.constraint(equalToConstant: 75)
49 | ])
50 |
51 | style()
52 | }
53 |
54 | private func style() {
55 | nameLabel.font = UIFont.preferredFont(forTextStyle: .headline)
56 | nameLabel.textColor = .label
57 | nameLabel.numberOfLines = 0
58 |
59 | subtitleLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
60 | subtitleLabel.textColor = .secondaryLabel
61 | subtitleLabel.numberOfLines = 0
62 |
63 | imageView.layer.cornerRadius = 5
64 | imageView.clipsToBounds = true
65 | imageView.contentMode = .scaleAspectFill
66 |
67 | buyButton.setImage(UIImage(systemName: "icloud.and.arrow.down"),
68 | for: .normal)
69 | iapLabel.font = UIFont.systemFont(ofSize: 8)
70 | iapLabel.textColor = .tertiaryLabel
71 | iapLabel.numberOfLines = 0
72 | iapLabel.text = "In-App Purchases"
73 | }
74 |
75 | func configure(with app: App) {
76 | nameLabel.text = app.name
77 | subtitleLabel.text = app.subTitle
78 | imageView.image = app.image
79 | iapLabel.isHidden = !app.hasIAP
80 | }
81 |
82 | required init?(coder: NSCoder) {
83 | fatalError("init(coder:) has not been implemented")
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/AppStore/Cells/FeaturedCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeaturedCell.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class FeaturedCell: UICollectionViewCell, AppConfigurable {
12 |
13 | // UI components
14 | let typeLabel = UILabel()
15 | let nameLabel = UILabel()
16 | let subtitleLabel = UILabel()
17 | let imageView = UIImageView()
18 |
19 | // Initialiser
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 |
23 | setup()
24 | }
25 |
26 | /// Sets up the UI components for this cell and adds them to the contentview
27 | private func setup() {
28 | // Separator is a line at the top of the cell to visually separate it from the Navigation bar
29 | let separator = UIView(frame: .zero)
30 | separator.translatesAutoresizingMaskIntoConstraints = false
31 | separator.backgroundColor = .separator
32 |
33 | // Constructing our main stack view with all the UI components we defined above
34 | let stackView = UIStackView(arrangedSubviews: [separator, typeLabel, nameLabel, subtitleLabel, imageView])
35 | stackView.axis = .vertical
36 |
37 | // We will define our own constraints
38 | stackView.translatesAutoresizingMaskIntoConstraints = false
39 |
40 | // Add the stackview to the content view
41 | contentView.addSubview(stackView)
42 |
43 | // Constraint the stackview to all 4 edges of the content view
44 | // Constraint the separator height to 0.5
45 | NSLayoutConstraint.activate([
46 | separator.heightAnchor.constraint(equalToConstant: 0.5),
47 | stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
48 | stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
49 | stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
50 | stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
51 | ])
52 |
53 | // Adds custom spacing to the stackview
54 | stackView.setCustomSpacing(10, after: separator)
55 | stackView.setCustomSpacing(10, after: subtitleLabel)
56 |
57 | style()
58 | }
59 |
60 | /// Styles all the UI components in the cell
61 | private func style() {
62 | typeLabel.font = UIFont.systemFont(ofSize: 12, weight: .bold)
63 | typeLabel.textColor = .systemBlue
64 |
65 | nameLabel.font = UIFont.preferredFont(forTextStyle: .title3)
66 | nameLabel.textColor = .label
67 |
68 | subtitleLabel.font = UIFont.preferredFont(forTextStyle: .title3)
69 | subtitleLabel.textColor = .secondaryLabel
70 |
71 | imageView.layer.cornerRadius = 10
72 | imageView.clipsToBounds = true
73 | imageView.contentMode = .scaleAspectFill
74 | }
75 |
76 | /// Configures the cell with a given app
77 | func configure(with app: App) {
78 | typeLabel.text = app.type.uppercased()
79 | nameLabel.text = app.name
80 | subtitleLabel.text = app.subTitle
81 | imageView.image = app.image
82 | }
83 |
84 | required init?(coder: NSCoder) {
85 | fatalError("init(coder:) has not been implemented")
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/AppStore/Cells/MediumAppCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediumAppCell.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MediumAppCell: UICollectionViewCell, AppConfigurable {
12 |
13 | let nameLabel = UILabel()
14 | let subtitleLabel = UILabel()
15 | let imageView = UIImageView()
16 | let buyButton = UIButton(type: .custom)
17 | let iapLabel = UILabel()
18 |
19 | override init(frame: CGRect) {
20 | super.init(frame: frame)
21 | setup()
22 | }
23 |
24 | private func setup() {
25 | imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
26 | buyButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
27 |
28 | let innerTextStackView = UIStackView(arrangedSubviews: [nameLabel,
29 | subtitleLabel])
30 | innerTextStackView.axis = .vertical
31 |
32 | let innerButtonStackView = UIStackView(arrangedSubviews: [buyButton,
33 | iapLabel])
34 | innerButtonStackView.axis = .horizontal
35 | innerButtonStackView.spacing = 8
36 | innerButtonStackView.alignment = .leading
37 | innerButtonStackView.distribution = .fillProportionally
38 |
39 | let innerStackView = UIStackView(arrangedSubviews: [innerTextStackView,
40 | innerButtonStackView])
41 | innerStackView.axis = .vertical
42 | innerStackView.spacing = 10
43 | innerStackView.alignment = .leading
44 | innerStackView.distribution = .fillProportionally
45 |
46 | let outerStackView = UIStackView(arrangedSubviews: [imageView, innerStackView])
47 | outerStackView.alignment = .center
48 | outerStackView.spacing = 10
49 | outerStackView.distribution = .fillProportionally
50 |
51 | outerStackView.translatesAutoresizingMaskIntoConstraints = false
52 | contentView.addSubview(outerStackView)
53 |
54 | NSLayoutConstraint.activate([
55 | outerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
56 | outerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
57 | outerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
58 | outerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
59 | imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
60 | imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
61 | iapLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 75)
62 | ])
63 |
64 | style()
65 | }
66 |
67 | private func style() {
68 | nameLabel.font = UIFont.preferredFont(forTextStyle: .headline)
69 | nameLabel.textColor = .label
70 |
71 | subtitleLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
72 | subtitleLabel.textColor = .secondaryLabel
73 |
74 | imageView.layer.cornerRadius = 15
75 | imageView.clipsToBounds = true
76 | imageView.contentMode = .scaleAspectFill
77 |
78 | buyButton.setImage(UIImage(systemName: "icloud.and.arrow.down"),
79 | for: .normal)
80 | iapLabel.font = UIFont.systemFont(ofSize: 10)
81 | iapLabel.textColor = .tertiaryLabel
82 | iapLabel.numberOfLines = 0
83 | iapLabel.text = "In-App\nPurchases"
84 | }
85 |
86 | func configure(with app: App) {
87 | nameLabel.text = app.name
88 | subtitleLabel.text = app.subTitle
89 | imageView.image = app.image
90 | iapLabel.isHidden = !app.hasIAP
91 | }
92 |
93 | required init?(coder: NSCoder) {
94 | fatalError("init(coder:) has not been implemented")
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/AppStore/AppsPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppPresenter.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 07/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /* NOTE
12 | This is the minimal data layer of this ViewController.
13 | In a normal project depending on your architecture this would be in the Presenter,
14 | ViewModel, the ViewController itself or anywhere else to your liking.
15 |
16 | This will use similar methods and variables that we would use in a Presenter in the VIPER
17 | architecture
18 | */
19 |
20 | class AppsPresenter {
21 |
22 | private var dataSource: [Section] = []
23 |
24 | init() {
25 | // First section: Featured
26 | let featuredApps = [App(id: 1, type: "GET FIT", name: "GFA", subTitle: "Generic Fitness App", image: UIImage(named: "desert")!, hasIAP: true),
27 | App(id: 2, type: "SOMETHING NEW", name: "Calorie Counter App", subTitle: "Very Original concept", image: UIImage(named: "food")!, hasIAP: true),
28 | App(id: 3, type: "LIFE GOALS", name: "Learn a language", subTitle: "Triolingo 5.7", image: UIImage(named: "KoreanTemple")!, hasIAP: false)]
29 | let section1 = Section(id: 1, type: .singleList, title: nil, subtitle: nil, data: featuredApps)
30 |
31 | dataSource.append(section1)
32 |
33 | // Second section: This weeks favorites
34 | let favorites = [App(id: 4, type: "Photo", name: "FilmStuff", subTitle: "Photo effects and filters", image: UIImage(named: "food")!, hasIAP: true),
35 | App(id: 5, type: "Shopping", name: "Get Shopping", subTitle: "Simple grocery list", image: UIImage(named: "temple")!, hasIAP: false),
36 | App(id: 6, type: "Notes", name: "White Board", subTitle: "Notes, Sketching, ...", image: UIImage(named: "desert")!, hasIAP: true),
37 | App(id: 7, type: "Photo", name: "Video maker", subTitle: "Make simple videos", image: UIImage(named: "containers")!, hasIAP: true),
38 | App(id: 8, type: "Shopping", name: "NaNzone", subTitle: "Buy everything here", image: UIImage(named: "temple")!, hasIAP: false),
39 | App(id: 9, type: "Meditation", name: "MUtopia", subTitle: "Relax, breathe", image: UIImage(named: "oldTemple")!, hasIAP: true),
40 | App(id: 10, type: "Learning", name: "Memo", subTitle: "Remind your self of your routine", image: UIImage(named: "food")!, hasIAP: false)]
41 | let section2 = Section(id: 2, type: .doubleList, title: "This weeks favorites", subtitle: nil, data: favorites)
42 |
43 | dataSource.append(section2)
44 |
45 | // Third section: Learn something
46 | let learning = [App(id: 11, type: "Learning", name: "Momi", subTitle: "Programming", image: UIImage(named: "containers")!, hasIAP: true),
47 | App(id: 12, type: "Learning", name: "Tod", subTitle: "Feed your brain", image: UIImage(named: "oldTemple")!, hasIAP: false),
48 | App(id: 13, type: "Learning", name: "Academy", subTitle: "Learn everything", image: UIImage(named: "food")!, hasIAP: true),
49 | App(id: 14, type: "Learning", name: "Praat", subTitle: "Learn to speak", image: UIImage(named: "KoreanTemple")!, hasIAP: true),
50 | App(id: 15, type: "Learning", name: "Memorable", subTitle: "Memory game", image: UIImage(named: "oldTemple")!, hasIAP: true),
51 | App(id: 16, type: "Learning", name: "Mysician", subTitle: "Learn music", image: UIImage(named: "temple")!, hasIAP: true),
52 | App(id: 17, type: "Learning", name: "ABE English", subTitle: "Learn englsish", image: UIImage(named: "KoreanTemple")!, hasIAP: false),
53 | App(id: 18, type: "Learning", name: "Math teacher", subTitle: "quick maths!", image: UIImage(named: "food")!, hasIAP: true),
54 | App(id: 19, type: "Learning", name: "TapTapRev", subTitle: "Reflexes", image: UIImage(named: "containers")!, hasIAP: true)]
55 | let section3 = Section(id: 3, type: .tripleList, title: "Learn something", subtitle: "Self development is key", data: learning)
56 |
57 | dataSource.append(section3)
58 |
59 | // Fourth section: Categories
60 | let categories = [Category(id: 1, name: "Apple watch apps", icon: UIImage(named: "desert")!),
61 | Category(id: 1, name: "AR Apps", icon: UIImage(named: "temple")!),
62 | Category(id: 1, name: "Photo & Video", icon: UIImage(named: "KoreanTemple")!),
63 | Category(id: 1, name: "Entertainment", icon: UIImage(named: "containers")!),
64 | Category(id: 1, name: "Utilities", icon: UIImage(named: "food")!)]
65 | let section4 = Section(id: 4, type: .categoryList, title: "Top Categories", subtitle: nil, data: categories)
66 |
67 | dataSource.append(section4)
68 | }
69 |
70 | // Collection View
71 | var numberOfSections: Int {
72 | return dataSource.count
73 | }
74 |
75 | func numberOfItems(for sectionIndex: Int) -> Int {
76 | let section = dataSource[sectionIndex]
77 | return section.data.count
78 | }
79 |
80 | // Cells
81 | func configure(item: AppConfigurable, for indexPath: IndexPath) {
82 | let section = dataSource[indexPath.section]
83 | if let app = section.data[indexPath.row] as? App {
84 | item.configure(with: app)
85 | } else {
86 | print("Error getting app for indexPath: \(indexPath)")
87 | }
88 | }
89 |
90 | func configure(item: CategoryConfigurable, for indexPath: IndexPath) {
91 | let section = dataSource[indexPath.section]
92 | if let category = section.data[indexPath.row] as? Category {
93 | item.configure(with: category)
94 | } else {
95 | print("Error getting category for indexPath: \(indexPath)")
96 | }
97 | }
98 |
99 | func sectionType(for sectionIndex: Int) -> SectionType {
100 | let section = dataSource[sectionIndex]
101 | return section.type
102 | }
103 |
104 | // Supplementary Views
105 | func title(for sectionIndex: Int) -> String? {
106 | let section = dataSource[sectionIndex]
107 | return section.title
108 | }
109 |
110 | func subtitle(for sectionIndex: Int) -> String? {
111 | let section = dataSource[sectionIndex]
112 | return section.subtitle
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/AppStore/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // AppStore
4 | //
5 | // Created by Bob De Kort on 06/01/2020.
6 | // Copyright © 2020 Nodes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | // MARK: - Properties -
14 | private var presenter = AppsPresenter() // Handles all the data
15 | private var collectionView: UICollectionView!
16 |
17 | // MARK: - Life cycle -
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | self.title = "Apps"
21 | navigationController?.navigationBar.prefersLargeTitles = true
22 | setupCollectionView()
23 | }
24 |
25 | // MARK: - Setup methods -
26 | /// Constructs the UICollectionView and adds it to the view.
27 | /// Registers all the Cells and Views that the UICollectionView will need
28 | private func setupCollectionView() {
29 | // Initialises the collection view with a CollectionViewLayout which we will define
30 | collectionView = UICollectionView.init(frame: .zero,
31 | collectionViewLayout: makeLayout())
32 | // Assigning data source and background color
33 | collectionView.dataSource = self
34 | collectionView.backgroundColor = .systemBackground
35 | // Adding the collection view to the view
36 | view.addSubview(collectionView)
37 |
38 | // This line tells the system we will define our own constraints
39 | collectionView.translatesAutoresizingMaskIntoConstraints = false
40 |
41 | // Constraining the collection view to the 4 edges of the view
42 | NSLayoutConstraint.activate([
43 | collectionView.topAnchor.constraint(equalTo: view.topAnchor),
44 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
45 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
46 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
47 | ])
48 |
49 | // Registering all Cells and Classes we will need
50 | collectionView.register(SectionHeader.self,
51 | forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
52 | withReuseIdentifier: SectionHeader.identifier)
53 | collectionView.register(FeaturedCell.self,
54 | forCellWithReuseIdentifier: FeaturedCell.identifier)
55 | collectionView.register(MediumAppCell.self,
56 | forCellWithReuseIdentifier: MediumAppCell.identifier)
57 | collectionView.register(SmallAppCell.self,
58 | forCellWithReuseIdentifier: SmallAppCell.identifier)
59 | collectionView.register(SmallCategoryCell.self,
60 | forCellWithReuseIdentifier: SmallCategoryCell.identifier)
61 | }
62 |
63 |
64 | // MARK: - Collection View Helper Methods -
65 | // In this section you can find all the layout related code
66 |
67 | /// Creates the appropriate UICollectionViewLayout for each section type
68 | private func makeLayout() -> UICollectionViewLayout {
69 | // Constructs the UICollectionViewCompositionalLayout
70 | let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnv: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
71 | switch self.presenter.sectionType(for: sectionIndex) {
72 | case .singleList: return self.createSingleListSection()
73 | case .doubleList: return self.createDoubleListSection()
74 | case .tripleList: return self.createTripleListSection()
75 | case .categoryList: return self.createCategoryListSection(for: self.presenter.numberOfItems(for: sectionIndex))
76 | }
77 | }
78 |
79 | // Configure the Layout with interSectionSpacing
80 | let config = UICollectionViewCompositionalLayoutConfiguration()
81 | config.interSectionSpacing = 20
82 | layout.configuration = config
83 |
84 | return layout
85 | }
86 |
87 | /// Creates the layout for the Featured styled sections
88 | private func createSingleListSection() -> NSCollectionLayoutSection {
89 | // Defining the size of a single item in this layout
90 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
91 | heightDimension: .fractionalHeight(1))
92 | // Construct the Layout Item
93 | let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
94 |
95 | // Configuring the Layout Item
96 | layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
97 |
98 | // Defining the size of a group in this layout
99 | let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95),
100 | heightDimension: .estimated(250))
101 | // Constructing the Layout Group
102 | let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize,
103 | subitems: [layoutItem])
104 |
105 | // Constructing the Layout Section
106 | let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
107 | // Configuring the Layout Section
108 | //
109 | layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
110 |
111 | return layoutSection
112 | }
113 |
114 | /// Creates a layout that shows 2 items per group and scrolls horizontally
115 | private func createDoubleListSection() -> NSCollectionLayoutSection {
116 | // Defining the size of a single item in this layout
117 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
118 | heightDimension: .fractionalHeight(0.5))
119 | // Construct the Layout Item
120 | let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
121 |
122 | // Configure the Layout Item
123 | layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
124 |
125 | // Defining the size of a group in this layout
126 | let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95),
127 | heightDimension: .estimated(165))
128 |
129 | // Constructing the Layout Group
130 | let layoutGroup = NSCollectionLayoutGroup.vertical(layoutSize: layoutGroupSize,
131 | subitem: layoutItem,
132 | count: 2)
133 | // Configuring the Layout Group
134 | layoutGroup.interItemSpacing = .fixed(8)
135 |
136 | // Constructing the Layout Section
137 | let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
138 |
139 | // Configuring the Layout Section
140 | layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
141 |
142 | // Constructing the Section Header
143 | let layoutSectionHeader = createSectionHeader()
144 |
145 | // Adding the Section Header to the Section Layout
146 | layoutSection.boundarySupplementaryItems = [layoutSectionHeader]
147 |
148 | return layoutSection
149 | }
150 |
151 | /// Creates a layout that shows 3 items per group and scrolls horizontally
152 | private func createTripleListSection() -> NSCollectionLayoutSection {
153 | // Defining the size of a single item in this layout
154 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
155 | heightDimension: .fractionalHeight(0.33))
156 | // Construct the Layout Item
157 | let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
158 |
159 | // Configure the Layout Item
160 | layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
161 |
162 | // Defining the size of a group in this layout
163 | let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95),
164 | heightDimension: .estimated(165))
165 | // Constructing the Layout Group
166 | let layoutGroup = NSCollectionLayoutGroup.vertical(layoutSize: layoutGroupSize,
167 | subitem: layoutItem,
168 | count: 3)
169 | // Configuring the Layout Group
170 | layoutGroup.interItemSpacing = .fixed(8)
171 |
172 | // Constructing the Layout Section
173 | let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
174 |
175 | // Configuring the Layout Section
176 | layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
177 |
178 | // Constructing the Section Header
179 | let layoutSectionHeader = createSectionHeader()
180 |
181 | // Adding the Section Header to the Section Layout
182 | layoutSection.boundarySupplementaryItems = [layoutSectionHeader]
183 |
184 | return layoutSection
185 | }
186 |
187 | /// Creates a layout that shows a list of small items based on the given amount.
188 | private func createCategoryListSection(for amount: Int) -> NSCollectionLayoutSection {
189 | // Defining the size of a single item in this layout
190 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
191 | heightDimension: .fractionalHeight(CGFloat(1/amount)))
192 | // Constructing the Layout Item
193 | let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
194 |
195 | // Configuring the Layout Item
196 | // We use a leading of 20 here and not 5 as the other layouts because we do not use the
197 | // orthogonalScrollingBehavior of groupPagingCentered, which handled part of the insets.
198 | layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 5)
199 |
200 | // Defining the size of a group in this layout
201 | let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95),
202 | heightDimension: .estimated(CGFloat(40 * amount)))
203 | // Constructing the Layout Group
204 | let layoutGroup = NSCollectionLayoutGroup.vertical(layoutSize: layoutGroupSize,
205 | subitem: layoutItem,
206 | count: amount)
207 | // Configuring the Layout Group
208 | layoutGroup.interItemSpacing = .fixed(8)
209 |
210 | // Constructing the Layout Section
211 | let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
212 |
213 | // Constructing the Section header
214 | let layoutSectionHeader = createSectionHeader()
215 |
216 | // Adding the Section Header to the Layout Section
217 | layoutSection.boundarySupplementaryItems = [layoutSectionHeader]
218 |
219 | return layoutSection
220 | }
221 |
222 | private func createSectionHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
223 | // Define size of Section Header
224 | let layoutSectionHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95),
225 | heightDimension: .estimated(80))
226 |
227 | // Construct Section Header Layout
228 | let layoutSectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: layoutSectionHeaderSize,
229 | elementKind: UICollectionView.elementKindSectionHeader,
230 | alignment: .top)
231 | return layoutSectionHeader
232 | }
233 | }
234 |
235 | // MARK: - UICollectionViewDataSource -
236 |
237 | extension ViewController: UICollectionViewDataSource {
238 | func numberOfSections(in collectionView: UICollectionView) -> Int {
239 | return presenter.numberOfSections
240 | }
241 |
242 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
243 | return presenter.numberOfItems(for: section)
244 | }
245 |
246 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
247 | // Checks what section type we should use for this indexPath so we use the right cells for that section
248 | switch presenter.sectionType(for: indexPath.section) {
249 | case .singleList:
250 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FeaturedCell.identifier, for: indexPath) as? FeaturedCell else {
251 | fatalError("Could not dequeue FeatureCell")
252 | }
253 |
254 | presenter.configure(item: cell, for: indexPath)
255 |
256 | return cell
257 |
258 | case .doubleList:
259 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediumAppCell.identifier, for: indexPath) as? MediumAppCell else {
260 | fatalError("Could not dequeue MediumAppCell")
261 | }
262 |
263 | presenter.configure(item: cell, for: indexPath)
264 |
265 | return cell
266 | case .tripleList:
267 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SmallAppCell.identifier, for: indexPath) as? SmallAppCell else {
268 | fatalError("Could not dequeue SmallAppCell")
269 | }
270 |
271 | presenter.configure(item: cell, for: indexPath)
272 |
273 | return cell
274 | case .categoryList:
275 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SmallCategoryCell.identifier, for: indexPath) as? SmallCategoryCell else {
276 | fatalError("Could not dequeue SmallCategoryCell")
277 | }
278 |
279 | presenter.configure(item: cell, for: indexPath)
280 |
281 | return cell
282 | }
283 | }
284 |
285 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
286 | guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeader.identifier, for: indexPath) as? SectionHeader else {
287 | fatalError("Could not dequeue SectionHeader")
288 | }
289 |
290 | // If the section has a title show it in the Section header, otherwise hide the titleLabel
291 | if let title = presenter.title(for: indexPath.section) {
292 | headerView.titleLabel.text = title
293 | headerView.titleLabel.isHidden = false
294 | } else {
295 | headerView.titleLabel.isHidden = true
296 | }
297 |
298 | // If the section has a subtitle show it in the Section header, otherwise hide the subtitleLabel
299 | if let subtitle = presenter.subtitle(for: indexPath.section) {
300 | headerView.subtitleLabel.text = subtitle
301 | headerView.subtitleLabel.isHidden = false
302 | } else {
303 | headerView.subtitleLabel.isHidden = true
304 | }
305 |
306 | return headerView
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/AppStore.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C47DC0E323C367770019AA75 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0E223C367770019AA75 /* AppDelegate.swift */; };
11 | C47DC0E523C367770019AA75 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0E423C367770019AA75 /* SceneDelegate.swift */; };
12 | C47DC0E723C367770019AA75 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0E623C367770019AA75 /* ViewController.swift */; };
13 | C47DC0EA23C367770019AA75 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C47DC0E823C367770019AA75 /* Main.storyboard */; };
14 | C47DC0EC23C3677A0019AA75 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C47DC0EB23C3677A0019AA75 /* Assets.xcassets */; };
15 | C47DC0EF23C3677A0019AA75 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C47DC0ED23C3677A0019AA75 /* LaunchScreen.storyboard */; };
16 | C47DC0F823C372EC0019AA75 /* FeaturedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0F723C372EC0019AA75 /* FeaturedCell.swift */; };
17 | C47DC0FB23C375640019AA75 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0FA23C375640019AA75 /* App.swift */; };
18 | C47DC0FD23C3831E0019AA75 /* MediumAppCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0FC23C3831E0019AA75 /* MediumAppCell.swift */; };
19 | C47DC0FF23C38B960019AA75 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC0FE23C38B960019AA75 /* SectionHeader.swift */; };
20 | C47DC10123C481390019AA75 /* SmallAppCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10023C481390019AA75 /* SmallAppCell.swift */; };
21 | C47DC10423C4AC140019AA75 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10323C4AC140019AA75 /* Category.swift */; };
22 | C47DC10623C4AE260019AA75 /* SmallCategoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10523C4AE260019AA75 /* SmallCategoryCell.swift */; };
23 | C47DC10923C4AE8F0019AA75 /* UICollectionViewCell+Identifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10823C4AE8F0019AA75 /* UICollectionViewCell+Identifier.swift */; };
24 | C47DC10B23C4BDF30019AA75 /* AppsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10A23C4BDF30019AA75 /* AppsPresenter.swift */; };
25 | C47DC10D23C4BFED0019AA75 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10C23C4BFED0019AA75 /* Protocols.swift */; };
26 | C47DC10F23C4C2C30019AA75 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DC10E23C4C2C30019AA75 /* Section.swift */; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXFileReference section */
30 | C47DC0DF23C367770019AA75 /* AppStore.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppStore.app; sourceTree = BUILT_PRODUCTS_DIR; };
31 | C47DC0E223C367770019AA75 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
32 | C47DC0E423C367770019AA75 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
33 | C47DC0E623C367770019AA75 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
34 | C47DC0E923C367770019AA75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
35 | C47DC0EB23C3677A0019AA75 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
36 | C47DC0EE23C3677A0019AA75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
37 | C47DC0F023C3677A0019AA75 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | C47DC0F723C372EC0019AA75 /* FeaturedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedCell.swift; sourceTree = ""; };
39 | C47DC0FA23C375640019AA75 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; };
40 | C47DC0FC23C3831E0019AA75 /* MediumAppCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumAppCell.swift; sourceTree = ""; };
41 | C47DC0FE23C38B960019AA75 /* SectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeader.swift; sourceTree = ""; };
42 | C47DC10023C481390019AA75 /* SmallAppCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallAppCell.swift; sourceTree = ""; };
43 | C47DC10323C4AC140019AA75 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; };
44 | C47DC10523C4AE260019AA75 /* SmallCategoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallCategoryCell.swift; sourceTree = ""; };
45 | C47DC10823C4AE8F0019AA75 /* UICollectionViewCell+Identifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewCell+Identifier.swift"; sourceTree = ""; };
46 | C47DC10A23C4BDF30019AA75 /* AppsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsPresenter.swift; sourceTree = ""; };
47 | C47DC10C23C4BFED0019AA75 /* Protocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; };
48 | C47DC10E23C4C2C30019AA75 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | C47DC0DC23C367770019AA75 /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | /* End PBXFrameworksBuildPhase section */
60 |
61 | /* Begin PBXGroup section */
62 | C47DC0D623C367770019AA75 = {
63 | isa = PBXGroup;
64 | children = (
65 | C47DC0E123C367770019AA75 /* AppStore */,
66 | C47DC0E023C367770019AA75 /* Products */,
67 | );
68 | sourceTree = "";
69 | };
70 | C47DC0E023C367770019AA75 /* Products */ = {
71 | isa = PBXGroup;
72 | children = (
73 | C47DC0DF23C367770019AA75 /* AppStore.app */,
74 | );
75 | name = Products;
76 | sourceTree = "";
77 | };
78 | C47DC0E123C367770019AA75 /* AppStore */ = {
79 | isa = PBXGroup;
80 | children = (
81 | C47DC10723C4AE7C0019AA75 /* Misc */,
82 | C47DC0F923C3755C0019AA75 /* Models */,
83 | C47DC10223C4ABE60019AA75 /* SupplementaryViews */,
84 | C47DC0F623C368F40019AA75 /* Cells */,
85 | C47DC0E223C367770019AA75 /* AppDelegate.swift */,
86 | C47DC0E423C367770019AA75 /* SceneDelegate.swift */,
87 | C47DC10A23C4BDF30019AA75 /* AppsPresenter.swift */,
88 | C47DC0E623C367770019AA75 /* ViewController.swift */,
89 | C47DC0E823C367770019AA75 /* Main.storyboard */,
90 | C47DC0EB23C3677A0019AA75 /* Assets.xcassets */,
91 | C47DC0ED23C3677A0019AA75 /* LaunchScreen.storyboard */,
92 | C47DC0F023C3677A0019AA75 /* Info.plist */,
93 | );
94 | path = AppStore;
95 | sourceTree = "";
96 | };
97 | C47DC0F623C368F40019AA75 /* Cells */ = {
98 | isa = PBXGroup;
99 | children = (
100 | C47DC0F723C372EC0019AA75 /* FeaturedCell.swift */,
101 | C47DC0FC23C3831E0019AA75 /* MediumAppCell.swift */,
102 | C47DC10023C481390019AA75 /* SmallAppCell.swift */,
103 | C47DC10523C4AE260019AA75 /* SmallCategoryCell.swift */,
104 | C47DC10C23C4BFED0019AA75 /* Protocols.swift */,
105 | );
106 | path = Cells;
107 | sourceTree = "";
108 | };
109 | C47DC0F923C3755C0019AA75 /* Models */ = {
110 | isa = PBXGroup;
111 | children = (
112 | C47DC10E23C4C2C30019AA75 /* Section.swift */,
113 | C47DC0FA23C375640019AA75 /* App.swift */,
114 | C47DC10323C4AC140019AA75 /* Category.swift */,
115 | );
116 | path = Models;
117 | sourceTree = "";
118 | };
119 | C47DC10223C4ABE60019AA75 /* SupplementaryViews */ = {
120 | isa = PBXGroup;
121 | children = (
122 | C47DC0FE23C38B960019AA75 /* SectionHeader.swift */,
123 | );
124 | path = SupplementaryViews;
125 | sourceTree = "";
126 | };
127 | C47DC10723C4AE7C0019AA75 /* Misc */ = {
128 | isa = PBXGroup;
129 | children = (
130 | C47DC10823C4AE8F0019AA75 /* UICollectionViewCell+Identifier.swift */,
131 | );
132 | path = Misc;
133 | sourceTree = "";
134 | };
135 | /* End PBXGroup section */
136 |
137 | /* Begin PBXNativeTarget section */
138 | C47DC0DE23C367770019AA75 /* AppStore */ = {
139 | isa = PBXNativeTarget;
140 | buildConfigurationList = C47DC0F323C3677A0019AA75 /* Build configuration list for PBXNativeTarget "AppStore" */;
141 | buildPhases = (
142 | C47DC0DB23C367770019AA75 /* Sources */,
143 | C47DC0DC23C367770019AA75 /* Frameworks */,
144 | C47DC0DD23C367770019AA75 /* Resources */,
145 | );
146 | buildRules = (
147 | );
148 | dependencies = (
149 | );
150 | name = AppStore;
151 | productName = AppStore;
152 | productReference = C47DC0DF23C367770019AA75 /* AppStore.app */;
153 | productType = "com.apple.product-type.application";
154 | };
155 | /* End PBXNativeTarget section */
156 |
157 | /* Begin PBXProject section */
158 | C47DC0D723C367770019AA75 /* Project object */ = {
159 | isa = PBXProject;
160 | attributes = {
161 | LastSwiftUpdateCheck = 1130;
162 | LastUpgradeCheck = 1130;
163 | ORGANIZATIONNAME = Nodes;
164 | TargetAttributes = {
165 | C47DC0DE23C367770019AA75 = {
166 | CreatedOnToolsVersion = 11.3;
167 | };
168 | };
169 | };
170 | buildConfigurationList = C47DC0DA23C367770019AA75 /* Build configuration list for PBXProject "AppStore" */;
171 | compatibilityVersion = "Xcode 9.3";
172 | developmentRegion = en;
173 | hasScannedForEncodings = 0;
174 | knownRegions = (
175 | en,
176 | Base,
177 | );
178 | mainGroup = C47DC0D623C367770019AA75;
179 | productRefGroup = C47DC0E023C367770019AA75 /* Products */;
180 | projectDirPath = "";
181 | projectRoot = "";
182 | targets = (
183 | C47DC0DE23C367770019AA75 /* AppStore */,
184 | );
185 | };
186 | /* End PBXProject section */
187 |
188 | /* Begin PBXResourcesBuildPhase section */
189 | C47DC0DD23C367770019AA75 /* Resources */ = {
190 | isa = PBXResourcesBuildPhase;
191 | buildActionMask = 2147483647;
192 | files = (
193 | C47DC0EF23C3677A0019AA75 /* LaunchScreen.storyboard in Resources */,
194 | C47DC0EC23C3677A0019AA75 /* Assets.xcassets in Resources */,
195 | C47DC0EA23C367770019AA75 /* Main.storyboard in Resources */,
196 | );
197 | runOnlyForDeploymentPostprocessing = 0;
198 | };
199 | /* End PBXResourcesBuildPhase section */
200 |
201 | /* Begin PBXSourcesBuildPhase section */
202 | C47DC0DB23C367770019AA75 /* Sources */ = {
203 | isa = PBXSourcesBuildPhase;
204 | buildActionMask = 2147483647;
205 | files = (
206 | C47DC0E723C367770019AA75 /* ViewController.swift in Sources */,
207 | C47DC0F823C372EC0019AA75 /* FeaturedCell.swift in Sources */,
208 | C47DC10B23C4BDF30019AA75 /* AppsPresenter.swift in Sources */,
209 | C47DC10F23C4C2C30019AA75 /* Section.swift in Sources */,
210 | C47DC0E323C367770019AA75 /* AppDelegate.swift in Sources */,
211 | C47DC0FF23C38B960019AA75 /* SectionHeader.swift in Sources */,
212 | C47DC0FB23C375640019AA75 /* App.swift in Sources */,
213 | C47DC10423C4AC140019AA75 /* Category.swift in Sources */,
214 | C47DC0FD23C3831E0019AA75 /* MediumAppCell.swift in Sources */,
215 | C47DC10623C4AE260019AA75 /* SmallCategoryCell.swift in Sources */,
216 | C47DC10D23C4BFED0019AA75 /* Protocols.swift in Sources */,
217 | C47DC0E523C367770019AA75 /* SceneDelegate.swift in Sources */,
218 | C47DC10923C4AE8F0019AA75 /* UICollectionViewCell+Identifier.swift in Sources */,
219 | C47DC10123C481390019AA75 /* SmallAppCell.swift in Sources */,
220 | );
221 | runOnlyForDeploymentPostprocessing = 0;
222 | };
223 | /* End PBXSourcesBuildPhase section */
224 |
225 | /* Begin PBXVariantGroup section */
226 | C47DC0E823C367770019AA75 /* Main.storyboard */ = {
227 | isa = PBXVariantGroup;
228 | children = (
229 | C47DC0E923C367770019AA75 /* Base */,
230 | );
231 | name = Main.storyboard;
232 | sourceTree = "";
233 | };
234 | C47DC0ED23C3677A0019AA75 /* LaunchScreen.storyboard */ = {
235 | isa = PBXVariantGroup;
236 | children = (
237 | C47DC0EE23C3677A0019AA75 /* Base */,
238 | );
239 | name = LaunchScreen.storyboard;
240 | sourceTree = "";
241 | };
242 | /* End PBXVariantGroup section */
243 |
244 | /* Begin XCBuildConfiguration section */
245 | C47DC0F123C3677A0019AA75 /* Debug */ = {
246 | isa = XCBuildConfiguration;
247 | buildSettings = {
248 | ALWAYS_SEARCH_USER_PATHS = NO;
249 | CLANG_ANALYZER_NONNULL = YES;
250 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
251 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
252 | CLANG_CXX_LIBRARY = "libc++";
253 | CLANG_ENABLE_MODULES = YES;
254 | CLANG_ENABLE_OBJC_ARC = YES;
255 | CLANG_ENABLE_OBJC_WEAK = YES;
256 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
257 | CLANG_WARN_BOOL_CONVERSION = YES;
258 | CLANG_WARN_COMMA = YES;
259 | CLANG_WARN_CONSTANT_CONVERSION = YES;
260 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
263 | CLANG_WARN_EMPTY_BODY = YES;
264 | CLANG_WARN_ENUM_CONVERSION = YES;
265 | CLANG_WARN_INFINITE_RECURSION = YES;
266 | CLANG_WARN_INT_CONVERSION = YES;
267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
268 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
269 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
271 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
272 | CLANG_WARN_STRICT_PROTOTYPES = YES;
273 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
274 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
275 | CLANG_WARN_UNREACHABLE_CODE = YES;
276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
277 | COPY_PHASE_STRIP = NO;
278 | DEBUG_INFORMATION_FORMAT = dwarf;
279 | ENABLE_STRICT_OBJC_MSGSEND = YES;
280 | ENABLE_TESTABILITY = YES;
281 | GCC_C_LANGUAGE_STANDARD = gnu11;
282 | GCC_DYNAMIC_NO_PIC = NO;
283 | GCC_NO_COMMON_BLOCKS = YES;
284 | GCC_OPTIMIZATION_LEVEL = 0;
285 | GCC_PREPROCESSOR_DEFINITIONS = (
286 | "DEBUG=1",
287 | "$(inherited)",
288 | );
289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
291 | GCC_WARN_UNDECLARED_SELECTOR = YES;
292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
293 | GCC_WARN_UNUSED_FUNCTION = YES;
294 | GCC_WARN_UNUSED_VARIABLE = YES;
295 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
296 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
297 | MTL_FAST_MATH = YES;
298 | ONLY_ACTIVE_ARCH = YES;
299 | SDKROOT = iphoneos;
300 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
301 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
302 | };
303 | name = Debug;
304 | };
305 | C47DC0F223C3677A0019AA75 /* Release */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_ANALYZER_NONNULL = YES;
310 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
312 | CLANG_CXX_LIBRARY = "libc++";
313 | CLANG_ENABLE_MODULES = YES;
314 | CLANG_ENABLE_OBJC_ARC = YES;
315 | CLANG_ENABLE_OBJC_WEAK = YES;
316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
317 | CLANG_WARN_BOOL_CONVERSION = YES;
318 | CLANG_WARN_COMMA = YES;
319 | CLANG_WARN_CONSTANT_CONVERSION = YES;
320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
322 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
323 | CLANG_WARN_EMPTY_BODY = YES;
324 | CLANG_WARN_ENUM_CONVERSION = YES;
325 | CLANG_WARN_INFINITE_RECURSION = YES;
326 | CLANG_WARN_INT_CONVERSION = YES;
327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
328 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
329 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
330 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
331 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
332 | CLANG_WARN_STRICT_PROTOTYPES = YES;
333 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
334 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
335 | CLANG_WARN_UNREACHABLE_CODE = YES;
336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
337 | COPY_PHASE_STRIP = NO;
338 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
339 | ENABLE_NS_ASSERTIONS = NO;
340 | ENABLE_STRICT_OBJC_MSGSEND = YES;
341 | GCC_C_LANGUAGE_STANDARD = gnu11;
342 | GCC_NO_COMMON_BLOCKS = YES;
343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
345 | GCC_WARN_UNDECLARED_SELECTOR = YES;
346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
347 | GCC_WARN_UNUSED_FUNCTION = YES;
348 | GCC_WARN_UNUSED_VARIABLE = YES;
349 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
350 | MTL_ENABLE_DEBUG_INFO = NO;
351 | MTL_FAST_MATH = YES;
352 | SDKROOT = iphoneos;
353 | SWIFT_COMPILATION_MODE = wholemodule;
354 | SWIFT_OPTIMIZATION_LEVEL = "-O";
355 | VALIDATE_PRODUCT = YES;
356 | };
357 | name = Release;
358 | };
359 | C47DC0F423C3677A0019AA75 /* Debug */ = {
360 | isa = XCBuildConfiguration;
361 | buildSettings = {
362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
363 | CODE_SIGN_STYLE = Automatic;
364 | DEVELOPMENT_TEAM = DKT53LL7M6;
365 | INFOPLIST_FILE = AppStore/Info.plist;
366 | LD_RUNPATH_SEARCH_PATHS = (
367 | "$(inherited)",
368 | "@executable_path/Frameworks",
369 | );
370 | PRODUCT_BUNDLE_IDENTIFIER = Nodes.AppStore;
371 | PRODUCT_NAME = "$(TARGET_NAME)";
372 | SWIFT_VERSION = 5.0;
373 | TARGETED_DEVICE_FAMILY = "1,2";
374 | };
375 | name = Debug;
376 | };
377 | C47DC0F523C3677A0019AA75 /* Release */ = {
378 | isa = XCBuildConfiguration;
379 | buildSettings = {
380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
381 | CODE_SIGN_STYLE = Automatic;
382 | DEVELOPMENT_TEAM = DKT53LL7M6;
383 | INFOPLIST_FILE = AppStore/Info.plist;
384 | LD_RUNPATH_SEARCH_PATHS = (
385 | "$(inherited)",
386 | "@executable_path/Frameworks",
387 | );
388 | PRODUCT_BUNDLE_IDENTIFIER = Nodes.AppStore;
389 | PRODUCT_NAME = "$(TARGET_NAME)";
390 | SWIFT_VERSION = 5.0;
391 | TARGETED_DEVICE_FAMILY = "1,2";
392 | };
393 | name = Release;
394 | };
395 | /* End XCBuildConfiguration section */
396 |
397 | /* Begin XCConfigurationList section */
398 | C47DC0DA23C367770019AA75 /* Build configuration list for PBXProject "AppStore" */ = {
399 | isa = XCConfigurationList;
400 | buildConfigurations = (
401 | C47DC0F123C3677A0019AA75 /* Debug */,
402 | C47DC0F223C3677A0019AA75 /* Release */,
403 | );
404 | defaultConfigurationIsVisible = 0;
405 | defaultConfigurationName = Release;
406 | };
407 | C47DC0F323C3677A0019AA75 /* Build configuration list for PBXNativeTarget "AppStore" */ = {
408 | isa = XCConfigurationList;
409 | buildConfigurations = (
410 | C47DC0F423C3677A0019AA75 /* Debug */,
411 | C47DC0F523C3677A0019AA75 /* Release */,
412 | );
413 | defaultConfigurationIsVisible = 0;
414 | defaultConfigurationName = Release;
415 | };
416 | /* End XCConfigurationList section */
417 | };
418 | rootObject = C47DC0D723C367770019AA75 /* Project object */;
419 | }
420 |
--------------------------------------------------------------------------------