├── .gitignore
├── Demo
└── Playground
│ ├── LegoKit Documentation.docc
│ ├── Documentation.md
│ └── Tutorial
│ │ ├── How to use LegoKit.tutorial
│ │ └── Chapter-1.tutorial
│ ├── Playground
│ ├── Resource
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ └── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ ├── Info.plist
│ ├── Playground.entitlements
│ └── Source
│ │ ├── Application
│ │ └── AppDelegate.swift
│ │ ├── ToggleViewController.swift
│ │ ├── Model
│ │ └── Model.swift
│ │ ├── Item
│ │ ├── GroupCell.swift
│ │ └── UserCell.swift
│ │ └── ViewController.swift
│ └── Playground.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── octree.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── xcuserdata
│ └── octree.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── project.pbxproj
├── Lego.xcworkspace
├── xcuserdata
│ └── octree.xcuserdatad
│ │ ├── UserInterfaceState.xcuserstate
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── swiftpm
│ │ └── Package.resolved
│ └── IDETemplateMacros.plist
└── contents.xcworkspacedata
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── octree.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ └── octree.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Tests
└── LegoKitTests
│ └── LegoKitTests.swift
├── Package.swift
├── LICENSE
├── Sources
└── LegoKit
│ ├── State
│ ├── LegoContainer.swift
│ ├── ObservableObject.swift
│ ├── LegoPublished.swift
│ ├── Binding.swift
│ ├── StateObject.swift
│ └── State.swift
│ ├── Core
│ ├── SectionBuilder.swift
│ ├── Section.swift
│ ├── Lego.swift
│ ├── ItemBuilder.swift
│ ├── Core.swift
│ └── LegoRenderer.swift
│ └── Utils
│ └── CFRunLoop+Extensions.swift
├── .swiftlint.yml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 |
--------------------------------------------------------------------------------
/Demo/Playground/LegoKit Documentation.docc/Documentation.md:
--------------------------------------------------------------------------------
1 | # ``LegoKit``
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Resource/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Lego.xcworkspace/xcuserdata/octree.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/octree/LegoKit/HEAD/Lego.xcworkspace/xcuserdata/octree.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcuserdata/octree.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/octree/LegoKit/HEAD/.swiftpm/xcode/package.xcworkspace/xcuserdata/octree.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/Demo/Playground/Playground.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Resource/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 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground.xcodeproj/project.xcworkspace/xcuserdata/octree.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/octree/LegoKit/HEAD/Demo/Playground/Playground.xcodeproj/project.xcworkspace/xcuserdata/octree.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/Lego.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/Playground/LegoKit Documentation.docc/Tutorial/How to use LegoKit.tutorial:
--------------------------------------------------------------------------------
1 | @Tutorials(name: "使用 LegoKit 配置 UICollectionView") {
2 | @Intro(title: "使用声明式语法配置 UICollectionView") {
3 | text
4 | }
5 |
6 | @Chapter(name: "创建工程") {
7 | 创建一个 iOS App 工程,添加 ``LegoKit`` 依赖
8 | @TutorialReference(tutorial: "doc:Chapter-1")
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Playground.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Tests/LegoKitTests/LegoKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import LegoKit
3 |
4 | final class LegoKitTests: XCTestCase {
5 | func testExample() throws {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(LegoKit().text, "Hello, World!")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Lego.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "SnapKit",
6 | "repositoryURL": "https://github.com/SnapKit/SnapKit",
7 | "state": {
8 | "branch": null,
9 | "revision": "f222cbdf325885926566172f6f5f06af95473158",
10 | "version": "5.6.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcuserdata/octree.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | LegoKit.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Lego.xcworkspace/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // ___FILENAME___
8 | // LegoKit
9 | //
10 | // Created by ___USERNAME___ on ___DATE___.
11 | //
12 | // Copyright (c) ___YEAR___ Octree <fouljz@gmail.com>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground.xcodeproj/xcuserdata/octree.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Playground.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Source/Application/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Playground
4 | //
5 | // Created by Octree on 2022/3/10.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 | var window: UIWindow?
13 |
14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
15 | // Override point for customization after application launch.
16 | return true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Lego.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
10 |
12 |
13 |
14 |
17 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Demo/Playground/LegoKit Documentation.docc/Tutorial/Chapter-1.tutorial:
--------------------------------------------------------------------------------
1 | @Tutorial(time: 10) {
2 | @Intro(title: "创建工程") {
3 | 创建一个 iOS App 工程,添加 ``LegoKit`` 依赖
4 | }
5 |
6 | @Section(title: "Hello") {
7 | @ContentAndMedia {
8 | text
9 |
10 | @Image(source: file, alt: "accessible description")
11 | }
12 |
13 | @Steps {
14 | @Step {
15 | text
16 | }
17 |
18 | @Step {
19 |
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
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: "LegoKit",
8 | platforms: [
9 | .macCatalyst(.v14),
10 | .iOS(.v13)
11 | ],
12 | products: [
13 | .library(
14 | name: "LegoKit",
15 | targets: ["LegoKit"]
16 | )
17 | ],
18 | dependencies: [],
19 | targets: [
20 | .target(
21 | name: "LegoKit",
22 | dependencies: []
23 | ),
24 | .testTarget(
25 | name: "LegoKitTests",
26 | dependencies: ["LegoKit"]
27 | )
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/Lego.xcworkspace/xcuserdata/octree.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2022 Octree
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Source/ToggleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToggleViewController.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/5/5.
6 | //
7 | // Copyright (c) 2022 Octree
8 |
9 | import LegoKit
10 | import UIKit
11 |
12 | public final class ToggleViewController: UIViewController {
13 | @Binding var flag: Bool
14 |
15 | public init(flag: Binding) {
16 | _flag = flag
17 | super.init(nibName: nil, bundle: nil)
18 | }
19 |
20 | @available(*, unavailable)
21 | required init?(coder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | override public func viewDidLoad() {
26 | super.viewDidLoad()
27 | if #available(iOS 13.0, *) {
28 | view.backgroundColor = .systemBackground
29 | } else {
30 | view.backgroundColor = .white
31 | }
32 | let toggleView = UISwitch(frame: CGRect(origin: .init(x: 100, y: 100),
33 | size: .init(width: 100, height: 40)))
34 | toggleView.addTarget(self, action: #selector(toggle), for: .primaryActionTriggered)
35 | view.addSubview(toggleView)
36 | toggleView.isOn = flag
37 | }
38 |
39 | @objc func toggle(sender: UISwitch) {
40 | flag = sender.isOn
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Lego.xcworkspace/xcuserdata/octree.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SnapKitPlayground (Playground) 1.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 3
13 |
14 | SnapKitPlayground (Playground) 2.xcscheme
15 |
16 | isShown
17 |
18 | orderHint
19 | 4
20 |
21 | SnapKitPlayground (Playground) 3.xcscheme
22 |
23 | isShown
24 |
25 | orderHint
26 | 5
27 |
28 | SnapKitPlayground (Playground) 4.xcscheme
29 |
30 | isShown
31 |
32 | orderHint
33 | 6
34 |
35 | SnapKitPlayground (Playground) 5.xcscheme
36 |
37 | isShown
38 |
39 | orderHint
40 | 7
41 |
42 | SnapKitPlayground (Playground).xcscheme
43 |
44 | isShown
45 |
46 | orderHint
47 | 2
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Source/Model/Model.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Model.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 |
9 | import Foundation
10 |
11 | public enum Gender: CaseIterable {
12 | case male
13 | case female
14 | }
15 |
16 | extension Gender: CustomStringConvertible {
17 | public var description: String {
18 | switch self {
19 | case .male:
20 | return "男"
21 | case .female:
22 | return "女"
23 | }
24 | }
25 | }
26 |
27 | extension Gender {
28 | static var random: Gender { allCases.randomElement()! }
29 | }
30 |
31 | public struct Group: Equatable {
32 | public var id: UUID = .init()
33 | public var name: String
34 | public var users: [User]
35 | }
36 |
37 | public struct User: Equatable {
38 | public var id: UUID = .init()
39 | public var name: String
40 | public var gender: Gender = .random
41 | }
42 |
43 | extension Group {
44 | static var initialGroups: [Group] {
45 | [
46 | .init(name: "iOS", users: [
47 | User(name: "Octree"),
48 | User(name: "Chris"),
49 | User(name: "James")
50 | ]),
51 | .init(name: "Android", users: [
52 | User(name: "A"),
53 | User(name: "B"),
54 | User(name: "C")
55 | ])
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Source/Item/GroupCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GroupCell.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 |
9 | import LegoKit
10 | import UIKit
11 | import SnapKit
12 |
13 | // MARK: - Item
14 |
15 | /// Item 用来描述渲染一种 Cell 需要携带的数据。
16 | /// 并从类型和 ``GroupCell`` 进行绑定
17 | public struct GroupItem: TypedItemType {
18 | // 绑定 Cell 的类型
19 | public typealias CellType = GroupCell
20 | // 预留 ID,为以后做准备
21 | public var id: UUID
22 | // 分组名称
23 | public var name: String
24 | }
25 |
26 | // MARK: - Cell
27 |
28 | public class GroupCell: UICollectionViewCell, TypedCellType {
29 | public typealias Item = GroupItem
30 |
31 | // MARK: - Properties
32 |
33 | private lazy var nameLabel: UILabel = .init()
34 |
35 | // MARK: - Life Cyle
36 |
37 | // MARK: Initializer
38 |
39 | override public init(frame: CGRect) {
40 | super.init(frame: frame)
41 | setup()
42 | }
43 |
44 | public required init?(coder: NSCoder) {
45 | super.init(coder: coder)
46 | setup()
47 | }
48 |
49 | // MARK: - TypedCellType
50 |
51 | public func update(with item: Item) {
52 | nameLabel.text = item.name
53 | }
54 |
55 | // MARK: - Private
56 |
57 | // MARK: Setup
58 |
59 | private func setup() {
60 | contentView.addSubview(nameLabel)
61 | nameLabel.snp.makeConstraints {
62 | $0.leading.equalToSuperview().offset(24)
63 | $0.top.equalToSuperview().offset(8)
64 | $0.bottom.equalToSuperview().offset(-8).priority(.low)
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/LegoKit/State/LegoContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LegoContainer.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import UIKit
28 |
29 | /// A protocol defines the part of a ``LegoContainer`` type.
30 | /// You can make your ``UIViewController`` confirm to this protocol,
31 | /// so that You can use ``@State``、``@StateObject``.
32 | @MainActor
33 | public protocol LegoContainer: AnyObject {
34 | var lego: Lego { get }
35 | var legoRenderer: LegoRenderer { get }
36 | }
37 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | opt_in_rules:
2 | - vertical_parameter_alignment_on_call
3 |
4 | disabled_rules:
5 | - force_try
6 | - force_cast
7 | - nesting
8 | - block_based_kvo
9 | - opening_brace
10 | - no_fallthrough_only
11 | - for_where
12 | - unhandled_throwing_task
13 | - unneeded_synthesized_initializer
14 |
15 | analyzer_rules:
16 | - unused_declaration
17 | - unused_import
18 |
19 | excluded:
20 | - .build
21 | - "**/Package.swift"
22 | - "**/Generated"
23 |
24 |
25 | identifier_name:
26 | max_length:
27 | warning: 60
28 | error: 100
29 | min_length:
30 | warning: 2
31 | excluded:
32 | - x
33 | - y
34 | - z
35 |
36 | type_name:
37 | max_length:
38 | warning: 60
39 | error: 100
40 | min_length:
41 | warning: 2
42 |
43 | function_parameter_count:
44 | warning: 6
45 |
46 | vertical_whitespace:
47 | max_empty_lines: 2
48 |
49 | file_length:
50 | warning: 2000
51 | error: 3000
52 |
53 | line_length:
54 | warning: 1000
55 | error: 2000
56 |
57 | type_body_length:
58 | warning: 1000
59 | error: 1500
60 |
61 | function_body_length:
62 | warning: 300
63 | error: 500
64 |
65 | cyclomatic_complexity:
66 | warning: 40
67 | error: 50
68 |
69 | large_tuple:
70 | warning: 3
71 | error: 6
72 |
73 | custom_rules:
74 | comment_space:
75 | name: "Space After Comment"
76 | regex: '//\S'
77 | match_kinds:
78 | - comment
79 | message: "There should be a space after a comment delimiter."
80 | severity: warning
81 |
82 | inline_comment_spaces:
83 | name: "Spaces Before Inline Comment"
84 | regex: '\S ?//'
85 | match_kinds:
86 | - comment
87 | message: "There should be more than 2 spaces before an inline comment."
88 | severity: warning
89 | reporter: "xcode"
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Resource/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 |
--------------------------------------------------------------------------------
/Sources/LegoKit/State/ObservableObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservableObject.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Combine
28 | import UIKit
29 |
30 | public protocol LegoObservableObject: AnyObject {
31 | var objectDidChange: PassthroughSubject { get }
32 | }
33 |
34 | private enum AssociatedKeys {
35 | static let publisher = malloc(1)!
36 | }
37 |
38 | public extension LegoObservableObject {
39 | var objectDidChange: PassthroughSubject {
40 | var publisher = objc_getAssociatedObject(self, AssociatedKeys.publisher) as? PassthroughSubject
41 | if publisher == nil {
42 | publisher = .init()
43 | objc_setAssociatedObject(self, AssociatedKeys.publisher, publisher, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
44 | }
45 | return publisher!
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Resource/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 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Source/Item/UserCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserCell.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 |
9 | import LegoKit
10 | import UIKit
11 | import SnapKit
12 |
13 | // MARK: - Item
14 |
15 | /// Item 用来描述渲染一种 Cell 需要携带的数据。
16 | /// 并从类型和 ``UserCell`` 进行绑定
17 | public struct UserItem: TypedItemType {
18 | // 绑定 Cell 的类型
19 | public typealias CellType = UserCell
20 | // 预留 ID,为以后做准备
21 | public var id: UUID
22 | // 用户名称
23 | public var name: String
24 | public var gender: Gender
25 | }
26 |
27 | // MARK: - Cell
28 |
29 | public class UserCell: UICollectionViewCell, TypedCellType {
30 | public typealias Item = UserItem
31 |
32 | // MARK: - Properties
33 |
34 | private lazy var nameLabel: UILabel = .init()
35 | private lazy var genderLabel: UILabel = {
36 | let label = UILabel()
37 | label.font = .systemFont(ofSize: 12)
38 | label.textColor = .lightGray
39 | return label
40 | }()
41 | private let containerView = UIView()
42 |
43 | // MARK: - Life Cyle
44 |
45 | // MARK: Initializer
46 |
47 | override public init(frame: CGRect) {
48 | super.init(frame: frame)
49 | setup()
50 | }
51 |
52 | public required init?(coder: NSCoder) {
53 | super.init(coder: coder)
54 | setup()
55 | }
56 |
57 | // MARK: - TypedCellType
58 |
59 | public func update(with item: Item) {
60 | nameLabel.text = item.name
61 | genderLabel.text = "性别:\(item.gender)"
62 | }
63 |
64 | // MARK: - Private
65 |
66 | // MARK: Setup
67 |
68 | private func setup() {
69 | nameLabel.numberOfLines = 0
70 | contentView.addSubview(nameLabel)
71 | nameLabel.snp.makeConstraints {
72 | $0.leading.equalToSuperview().offset(24)
73 | $0.top.equalToSuperview().offset(16)
74 | $0.trailing.equalToSuperview().offset(-24)
75 | }
76 |
77 | contentView.addSubview(genderLabel)
78 | genderLabel.snp.makeConstraints {
79 | $0.leading.equalToSuperview().offset(24)
80 | $0.top.equalTo(nameLabel.snp.bottom).offset(8)
81 | $0.trailing.equalToSuperview().offset(-24)
82 | $0.bottom.equalToSuperview().offset(-16).priority(.low)
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/LegoKit/State/LegoPublished.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LegoPublished.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// The property wrapped is always used within a ``LegoObservableObject``
30 | /// It will call the ``LegoObservableObject/objectDidChange`` after the wrapped value is changed.
31 | @propertyWrapper
32 | public struct LegoPublished {
33 | public static subscript(
34 | _enclosingInstance instance: T,
35 | wrapped wrappedKeyPath: ReferenceWritableKeyPath,
36 | storage storageKeyPath: ReferenceWritableKeyPath
37 | ) -> Value {
38 | get {
39 | instance[keyPath: storageKeyPath].storage
40 | }
41 | set {
42 | instance[keyPath: storageKeyPath].storage = newValue
43 | instance.objectDidChange.send()
44 | }
45 | }
46 |
47 | @available(*, unavailable,
48 | message: "@LegoPublished can only be applied to classes")
49 | public var wrappedValue: Value {
50 | get { fatalError() }
51 | // swiftlint:disable unused_setter_value
52 | set { fatalError() }
53 | // swiftlint:enable unused_setter_value
54 | }
55 |
56 | private var storage: Value
57 |
58 | public init(wrappedValue: Value) {
59 | storage = wrappedValue
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Core/SectionBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionBuilder.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | @resultBuilder
30 | public enum SectionBuilder {
31 | public static func buildExpression(_ expression: Void) -> [AnySection] {
32 | []
33 | }
34 |
35 | public static func buildExpression(_ expression: Section) -> [AnySection] {
36 | [AnySection(expression)]
37 | }
38 |
39 | public static func buildBlock(_ components: [AnySection]...) -> [AnySection] {
40 | components.flatMap { $0 }
41 | }
42 |
43 | public static func buildIf(_ components: AnySection?...) -> [AnySection] {
44 | components.compactMap { $0 }
45 | }
46 |
47 | public static func buildEither(first component: [AnySection]) -> [AnySection] {
48 | component
49 | }
50 |
51 | public static func buildEither(second component: [AnySection]) -> [AnySection] {
52 | component
53 | }
54 |
55 | public static func buildArray(_ components: [[AnySection]]) -> [AnySection] {
56 | components.flatMap { $0 }
57 | }
58 |
59 | public static func buildLimitedAvailability(_ component: [AnySection]) -> [AnySection] {
60 | component
61 | }
62 |
63 | public static func buildOptional(_ component: [AnySection]?) -> [AnySection] {
64 | component ?? []
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Core/Section.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Section.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import UIKit
28 |
29 | public struct Section {
30 | public var id: ID
31 | var layout: (NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection
32 | public var items: [AnyItem] = []
33 |
34 | public init(id: ID, layout: NSCollectionLayoutSection, @ItemBuilder items: () -> [AnyItem]) {
35 | self.id = id
36 | self.layout = { _ in layout }
37 | self.items = items()
38 | }
39 |
40 | public init(id: ID, layout: @escaping () -> NSCollectionLayoutSection, @ItemBuilder items: () -> [AnyItem]) {
41 | self.id = id
42 | self.layout = { _ in layout() }
43 | self.items = items()
44 | }
45 |
46 | public init(id: ID, layout: @escaping (NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection, @ItemBuilder items: () -> [AnyItem]) {
47 | self.id = id
48 | self.layout = layout
49 | self.items = items()
50 | }
51 | }
52 |
53 | public struct AnySection {
54 | public var id: AnyID
55 | var layout: (NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection
56 | public var items: [AnyItem] = []
57 |
58 | init(_ section: Section) {
59 | id = AnyID(section.id)
60 | layout = section.layout
61 | items = section.items
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Core/Lego.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Lego.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import UIKit
28 |
29 | @MainActor
30 | public struct Lego {
31 | public var sections: [AnySection]
32 |
33 | public init(@SectionBuilder sections: () -> [AnySection]) {
34 | self.sections = sections()
35 | }
36 | }
37 |
38 | public extension Lego {
39 | subscript(indexPath: IndexPath) -> AnyItem {
40 | sections[indexPath.section].items[indexPath.item]
41 | }
42 |
43 | subscript(indexPath: IndexPath, as type: C.Type) -> C? {
44 | self[indexPath].as(type)
45 | }
46 | }
47 |
48 | extension Lego {
49 | func indexPathForItem(with identifier: ID) -> IndexPath? {
50 | for (sectionIndex, section) in sections.enumerated() {
51 | for (itemIndex, item) in section.items.enumerated() where item.anyID == AnyID(identifier) {
52 | return IndexPath(item: itemIndex, section: sectionIndex)
53 | }
54 | }
55 | return nil
56 | }
57 |
58 | func indexPathForItem(with identifier: AnyID) -> IndexPath? {
59 | for (sectionIndex, section) in sections.enumerated() {
60 | for (itemIndex, item) in section.items.enumerated() where item.anyID == identifier {
61 | return IndexPath(item: itemIndex, section: sectionIndex)
62 | }
63 | }
64 | return nil
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/LegoKit/State/Binding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Binding.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/5/5.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// 这个 PropertyWrapper 可以在任何情况下使用
30 | /// 当是一个 ``LegoContainer`` 的属性时,会自动更新 ``LegoRenderer``
31 | @propertyWrapper
32 | @MainActor
33 | public struct Binding {
34 | public var wrappedValue: Value {
35 | get { get() }
36 | set { set(newValue) }
37 | }
38 |
39 | private var get: () -> Value
40 | private var set: (Value) -> Void
41 |
42 | public var projectedValue: Binding {
43 | self
44 | }
45 |
46 | /// Create a property wrapper with initial value.
47 | /// - Parameter wrappedValue: The initial value.
48 | public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
49 | self.get = get
50 | self.set = set
51 | }
52 |
53 | public static subscript(
54 | _enclosingInstance instance: T,
55 | wrapped wrappedKeyPath: ReferenceWritableKeyPath,
56 | storage storageKeyPath: ReferenceWritableKeyPath
57 | ) -> Value {
58 | get {
59 | return instance[keyPath: storageKeyPath].get()
60 | }
61 | set {
62 | instance[keyPath: storageKeyPath].set(newValue)
63 | if let container = instance as? LegoContainer {
64 | container.legoRenderer.apply(lego: container.lego)
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Core/ItemBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemBuilder.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | @resultBuilder
30 | @MainActor
31 | public enum ItemBuilder {
32 | public static func buildExpression(_ expression: Void) -> [AnyItem] {
33 | []
34 | }
35 |
36 | public static func buildExpression(_ expression: AnyItem) -> [AnyItem] {
37 | [expression]
38 | }
39 |
40 | public static func buildExpression(_ expression: C) -> [AnyItem] {
41 | [AnyItem(expression)]
42 | }
43 |
44 | public static func buildExpression(_ expression: [AnyItem]) -> [AnyItem] {
45 | expression
46 | }
47 |
48 | public static func buildExpression(_ expression: [C]) -> [AnyItem] {
49 | expression.map { AnyItem($0) }
50 | }
51 |
52 | public static func buildBlock(_ components: [AnyItem]...) -> [AnyItem] {
53 | Array(components.joined())
54 | }
55 |
56 | public static func buildIf(_ components: AnyItem?...) -> [AnyItem] {
57 | components.compactMap { $0 }
58 | }
59 |
60 | public static func buildEither(first component: [AnyItem]) -> [AnyItem] {
61 | component
62 | }
63 |
64 | public static func buildEither(second component: [AnyItem]) -> [AnyItem] {
65 | component
66 | }
67 |
68 | public static func buildArray(_ components: [[AnyItem]]) -> [AnyItem] {
69 | components.flatMap { $0 }
70 | }
71 |
72 | public static func buildLimitedAvailability(_ component: [AnyItem]) -> [AnyItem] {
73 | component
74 | }
75 |
76 | public static func buildOptional(_ component: [AnyItem]?) -> [AnyItem] {
77 | component ?? []
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Source/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Playground
4 | //
5 | // Created by Octree on 2022/3/10.
6 | //
7 |
8 | import LegoKit
9 | import UIKit
10 |
11 | class ViewModel: LegoObservableObject {
12 | @LegoPublished var groups: [Group] = Group.initialGroups
13 |
14 | func addMember() {
15 | let alpha = "abcdefghijklmnopqrstuvwxyz"
16 | let name = String((0 ... 8).map { _ in alpha.randomElement()! }).capitalized
17 | groups[0].users.append(.init(name: name))
18 | }
19 | }
20 |
21 | class ViewController: UIViewController, LegoContainer {
22 | @StateObject var viewModel = ViewModel()
23 | @State var flag: Bool = true
24 |
25 | private var sectionLayout: NSCollectionLayoutSection = {
26 | let size: NSCollectionLayoutSize = .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(40))
27 | let item = NSCollectionLayoutItem(layoutSize: size)
28 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)
29 | let section = NSCollectionLayoutSection(group: group)
30 | section.interGroupSpacing = 1
31 | return section
32 | }()
33 |
34 | var lego: Lego {
35 | Lego {
36 | for group in viewModel.groups {
37 | Section(id: group.id, layout: sectionLayout) {
38 | if flag {
39 | GroupItem(id: group.id, name: group.name)
40 | }
41 | for user in group.users {
42 | UserItem(id: user.id, name: user.name, gender: user.gender)
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
49 | lazy var legoRenderer: LegoRenderer = .init(lego: lego)
50 |
51 | override func viewDidLoad() {
52 | super.viewDidLoad()
53 | legoRenderer.render(in: view) {
54 | $0.backgroundColor = UIColor(white: 0.95, alpha: 1)
55 | $0.alwaysBounceVertical = true
56 | }
57 |
58 | let barItem = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(toggle))
59 | let push = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(push))
60 | navigationItem.rightBarButtonItems = [push, barItem]
61 |
62 | let updateItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(updateItem))
63 | let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addMember))
64 | navigationItem.leftBarButtonItems = [addItem, updateItem]
65 | }
66 |
67 | @objc private func updateItem() {
68 | viewModel.groups[0].users[0].name += " Octree"
69 | }
70 |
71 | @objc private func toggle() {
72 | flag.toggle()
73 | }
74 |
75 | @objc private func addMember() {
76 | viewModel.addMember()
77 | }
78 |
79 | @objc private func push() {
80 | let vc = ToggleViewController(flag: $flag)
81 | navigationController?.pushViewController(vc, animated: true)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LegoKit
2 |
3 | 使用声明式语法组织 UICollectionView。支持类似 SwiftUI 的 `@State`、`@Published` 和 `@StateObject`
4 |
5 |
6 | ## Installation
7 |
8 | ### Swift Package Manager
9 |
10 | File > Swift Packages > Add Package Dependency
11 | Add https://github.com/octree/LegoKit.git
12 | Select "Up to Next Major" with "1.0.0"
13 |
14 |
15 | ## How to Use
16 |
17 | ### Cell 的实现
18 |
19 | ```swift
20 | public struct ColorItem: TypedItemType {
21 | public typealias CellType = ColorCell
22 | public var id: UUID
23 | public var color: UIColor
24 | public var height: CGFloat
25 | }
26 |
27 | public class ColorCell: UICollectionViewCell, TypedCellType {
28 | public typealias Item = ColorItem
29 |
30 | public func update(with item: ColorItem) {
31 | backgroundColor = item.color
32 | }
33 | }
34 | ```
35 |
36 | * id: 可以是任意 `Hashable`类型,为了给以后做动画,进行 diff 准备的预留属性。
37 | * 其他的属性,是用来配置 Cell 的信息
38 | * Cell 需要实现 `TypedCellType` 的方法
39 | * **update**:根据对应的 `Item` 更新 UI
40 |
41 | ### 渲染 UICollectionView
42 |
43 | #### 配置 Lego
44 |
45 | ```swift
46 | class ViewController: UIViewController {
47 | var lego: Lego {
48 | Lego {
49 | Section(id: ..., WaterfallLayout()) {
50 | ColorItem(...)
51 | ColorItem(...)
52 | ColorItem(...)
53 | }
54 |
55 | Section(WaterfallLayout()) {
56 | for elt in array {
57 | ColorItem(elt)
58 | }
59 | }
60 | }
61 | }
62 | }
63 | ```
64 |
65 | * Lego 用于描述 **CollectionView** 中的 Section 和 Cell 的信息
66 | * 使用类似 SwiftUI 的声明式语法构建
67 | * 支持 `if`、`#if @available `、`for in`、`switch case`等结构
68 |
69 | #### 渲染
70 |
71 | ```swift
72 | class ViewController: UIViewController {
73 | lazy var legoRenderer: LegoRenderer = .init(lego: lego)
74 | override func viewDidLoad() {
75 | legoRenderer.render(in: view) {
76 | $0.backgroundColor = UIColor(white: 0.95, alpha: 1)
77 | }
78 | }
79 |
80 | private func reload() {
81 | legoRenderer.apply(lego)
82 | }
83 | }
84 | ```
85 |
86 |
87 |
88 | ### 使用 @State、@StateObject 等,自动更新 CollectionView
89 |
90 | ```swift
91 | class ViewModel: LegoObservableObject {
92 | @LegoPublished var items: [ColorItem] = []
93 | func addRandomColor() {
94 | items.append(.random)
95 | }
96 | }
97 |
98 | class ViewController: UIViewController, LegoContainer {
99 | @StateObject var viewModel = ViewModel()
100 | @State var flag: Bool = false
101 | lazy var legoRenderer: LegoRenderer = .init(lego: lego)
102 | var lego: Lego {
103 | Lego {
104 | Section(id: 0, layout: WaterfallLayout()) {
105 | if flag {
106 | // ... some items.
107 | }
108 | viewModel.items
109 | }
110 | }
111 | }
112 |
113 | override func viewDidLoad() {
114 | super.viewDidLoad()
115 | legoRenderer.render(in: view) {
116 | $0.backgroundColor = UIColor(white: 0.95, alpha: 1)
117 | }
118 | }
119 | }
120 | ```
121 |
122 | * 当用于配置 Lego 的数据,使用了 `@State` 或者 `@StateObject ` 时就不需要手动调用 `apply` 函数。当数据发生变动时,会自动更新 `UICollectionView`。
123 |
124 | ## License
125 |
126 | **LegoKit** is available under the MIT license. See the LICENSE file for more info.
127 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground/Resource/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Sources/LegoKit/State/StateObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StateObject.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Combine
28 | import Foundation
29 |
30 | /// This property wrapper will apply ``Lego`` to ``LegoRenderer`` automatically while the ``Published`` value wa changed.
31 | /// The instance that hold this property must confirm to ``LegoContainer`` protocol.
32 | /// And the value must confirm to ``LegoObservableObject`` protocol
33 | @propertyWrapper
34 | @MainActor
35 | public struct StateObject {
36 | @available(*, unavailable,
37 | message: "This property wrapper can only be applied to ``LegoContainer``")
38 | public var wrappedValue: Value {
39 | get { fatalError() }
40 | // swiftlint:disable unused_setter_value
41 | set { fatalError() }
42 | // swiftlint:enable unused_setter_value
43 | }
44 |
45 | private class Box {
46 | weak var container: LegoContainer?
47 | var cancellable: AnyCancellable?
48 | }
49 |
50 | private var storage: Value
51 | private var box: Box = .init()
52 |
53 | /// Create a property wrapper with initial value
54 | /// - Parameter wrappedValue: The object to observed.
55 | public init(wrappedValue: Value) {
56 | storage = wrappedValue
57 | }
58 |
59 | private func bind(to instance: LegoContainer) {
60 | guard box.container !== instance else { return }
61 | box.container = instance
62 | box.cancellable = storage.objectDidChange.sink { [weak box] in
63 | guard let container = box?.container else {
64 | return
65 | }
66 | container.legoRenderer.apply(lego: container.lego)
67 | }
68 | }
69 |
70 | public static subscript(
71 | _enclosingInstance instance: T,
72 | wrapped wrappedKeyPath: ReferenceWritableKeyPath,
73 | storage storageKeyPath: ReferenceWritableKeyPath
74 | ) -> Value {
75 | get {
76 | instance[keyPath: storageKeyPath].bind(to: instance)
77 | return instance[keyPath: storageKeyPath].storage
78 | }
79 | set {
80 | instance[keyPath: storageKeyPath].storage = newValue
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Utils/CFRunLoop+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Binding.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/6/6.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Combine
28 | import CoreFoundation
29 | import Foundation
30 |
31 | private final class _AnyObserverBox {
32 | private var _trigger: () -> Void
33 | init(_ box: _ObserverBox) {
34 | _trigger = { box.trigger() }
35 | }
36 |
37 | func trigger() {
38 | _trigger()
39 | }
40 | }
41 |
42 | private final class _ObserverBox {
43 | weak var target: T?
44 | var action: (T) -> Void
45 |
46 | init(target: T, action: @escaping (T) -> Void) {
47 | self.target = target
48 | self.action = action
49 | }
50 |
51 | func trigger() {
52 | guard let target = target else {
53 | return
54 | }
55 | action(target)
56 | }
57 |
58 | func typeErased() -> _AnyObserverBox { .init(self) }
59 | }
60 |
61 | public extension CFRunLoop {
62 | func addObserver(_ target: T,
63 | activity: CFRunLoopActivity,
64 | mode: CFRunLoopMode = .defaultMode,
65 | repeat: Bool = true,
66 | order: CFIndex = 0,
67 | action: @escaping (T) -> Void) -> AnyCancellable
68 | {
69 | let box = _ObserverBox(target: target, action: action).typeErased()
70 | let info = Unmanaged.passRetained(box).toOpaque()
71 | var context = CFRunLoopObserverContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil)
72 | let observer = CFRunLoopObserverCreate(kCFAllocatorDefault, activity.rawValue, `repeat`, order, runLoopObserverCallback(), &context)
73 | CFRunLoopAddObserver(self,
74 | observer,
75 | mode)
76 | return .init {
77 | CFRunLoopRemoveObserver(self, observer, mode)
78 | }
79 | }
80 |
81 | func runLoopObserverCallback() -> CFRunLoopObserverCallBack {
82 | return { _, _, info in
83 | guard let info = info else {
84 | return
85 | }
86 | let box = Unmanaged<_AnyObserverBox>.fromOpaque(info).takeUnretainedValue()
87 | box.trigger()
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/LegoKit/State/State.swift:
--------------------------------------------------------------------------------
1 | //
2 | // State.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | final class AnyLocation {
30 | private var get: (() -> Value)!
31 | private var set: ((Value) -> Void)!
32 |
33 | var value: Value {
34 | get { get() }
35 | set { set(newValue) }
36 | }
37 |
38 | init() {}
39 |
40 | func bind(to instance: T, keyPath: ReferenceWritableKeyPath) {
41 | get = { [unowned instance] in
42 | instance[keyPath: keyPath]
43 | }
44 | set = { [unowned instance] in
45 | instance[keyPath: keyPath] = $0
46 | }
47 | }
48 | }
49 |
50 | /// This property wrapper will apply ``Lego`` to ``LegoRenderer`` automatically while the wrappedValue was changed.
51 | /// The instance that hold this property must confirm to ``LegoContainer`` protocol.
52 | @propertyWrapper
53 | @MainActor
54 | public struct State {
55 | @available(*, unavailable,
56 | message: "This property wrapper can only be applied to ``LegoContainer``")
57 | public var wrappedValue: Value {
58 | get { fatalError() }
59 | // swiftlint:disable unused_setter_value
60 | set { fatalError() }
61 | // swiftlint:enable unused_setter_value
62 | }
63 |
64 | private var location: AnyLocation = .init()
65 | private var storage: Value
66 | public var projectedValue: Binding {
67 | .init {
68 | location.value
69 | } set: {
70 | location.value = $0
71 | }
72 | }
73 |
74 | /// Create a property wrapper with initial value.
75 | /// - Parameter wrappedValue: The initial value.
76 | public init(wrappedValue: Value) {
77 | storage = wrappedValue
78 | }
79 |
80 | public static subscript(
81 | _enclosingInstance instance: T,
82 | wrapped wrappedKeyPath: ReferenceWritableKeyPath,
83 | storage storageKeyPath: ReferenceWritableKeyPath
84 | ) -> Value {
85 | get {
86 | let state = instance[keyPath: storageKeyPath]
87 | instance[keyPath: storageKeyPath].location.bind(to: instance, keyPath: wrappedKeyPath)
88 | return state.storage
89 | }
90 | set {
91 | instance[keyPath: storageKeyPath].storage = newValue
92 | instance.legoRenderer.apply(lego: instance.lego)
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Core/Core.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Core.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import UIKit
28 |
29 | public struct AnyID: Hashable, Sendable, Equatable {
30 | private nonisolated(unsafe) var value: Any
31 | private nonisolated(unsafe) var hashInto: (inout Hasher) -> Void
32 | private nonisolated(unsafe) var equalTo: (AnyID) -> Bool
33 | init(_ value: Value) {
34 | self.value = value
35 | hashInto = {
36 | $0.combine(value)
37 | }
38 | equalTo = {
39 | guard let other = $0.value as? Value else { return false }
40 | return other == value
41 | }
42 | }
43 |
44 | public func hash(into hasher: inout Hasher) {
45 | hashInto(&hasher)
46 | }
47 |
48 | public static func == (lhs: AnyID, rhs: AnyID) -> Bool {
49 | lhs.equalTo(rhs)
50 | }
51 | }
52 |
53 | /// A type represents the part of a lego cell
54 | @MainActor
55 | public protocol CellType {}
56 |
57 | /// A Lego cell with a specified Item type
58 | @MainActor
59 | public protocol TypedCellType: CellType {
60 | associatedtype Item
61 | func update(with item: Item)
62 | }
63 |
64 | @MainActor
65 | public protocol ItemType {
66 | var anyID: AnyID { get }
67 | func createCell(in collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell
68 | }
69 |
70 | @MainActor
71 | public struct AnyItem: ItemType {
72 | public var base: ItemType
73 | public var anyID: AnyID { base.anyID }
74 | private var _updateCell: (UICollectionViewCell) -> Void
75 | public func createCell(in collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell {
76 | base.createCell(in: collectionView, at: indexPath)
77 | }
78 |
79 | init(_ item: C) {
80 | base = item
81 | _updateCell = { item.updateCell($0) }
82 | }
83 |
84 | func updateCell(_ cell: UICollectionViewCell) {
85 | _updateCell(cell)
86 | }
87 |
88 | func `as`(_ type: C.Type) -> C? {
89 | base as? C
90 | }
91 | }
92 |
93 | @MainActor
94 | public protocol TypedItemType: ItemType {
95 | associatedtype ID: Hashable & Sendable
96 | associatedtype CellType: UICollectionViewCell, TypedCellType where CellType.Item == Self
97 | var id: ID { get }
98 | }
99 |
100 | public extension TypedItemType {
101 | var asItems: [AnyItem] { [AnyItem(self)] }
102 | }
103 |
104 | public extension TypedItemType {
105 | var anyID: AnyID { .init(id) }
106 | }
107 |
108 | public extension ItemType {
109 | func createCell(in collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell {
110 | fatalError()
111 | }
112 | }
113 |
114 | extension TypedItemType {
115 | static var identifier: String {
116 | return String(describing: CellType.self)
117 | }
118 |
119 | public func createCell(in collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell {
120 | Self.register(in: collectionView)
121 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Self.identifier, for: indexPath) as? CellType else {
122 | fatalError("Cannot create a new cell with identifier: \(Self.identifier), Expected cell type: \(CellType.self)")
123 | }
124 | cell.update(with: self)
125 | return cell
126 | }
127 |
128 | static func register(in collectionView: UICollectionView) {
129 | collectionView.register(CellType.classForCoder(), forCellWithReuseIdentifier: identifier)
130 | }
131 | }
132 |
133 | extension ItemType {
134 | func updateCell(_ cell: UICollectionViewCell) { fatalError() }
135 | }
136 |
137 | extension TypedItemType {
138 | func updateCell(_ cell: UICollectionViewCell) {
139 | guard let cell = cell as? CellType else { return }
140 | cell.update(with: self)
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Sources/LegoKit/Core/LegoRenderer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LegoRenderer.swift
3 | // LegoKit
4 | //
5 | // Created by octree on 2022/3/11.
6 | //
7 | // Copyright (c) 2022 Octree
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Combine
28 | import UIKit
29 |
30 | public typealias CellProvider = (UICollectionView, IndexPath, AnyHashable) -> UICollectionViewCell?
31 | public typealias SupplementaryViewProvider = (UICollectionView, String, IndexPath) -> UICollectionReusableView?
32 |
33 | @MainActor
34 | public final class LegoRenderer {
35 | public private(set) lazy var collectionView: UICollectionView = {
36 | let view = UICollectionView(frame: CGRect(), collectionViewLayout: layout)
37 | view.translatesAutoresizingMaskIntoConstraints = false
38 | return view
39 | }()
40 |
41 | private lazy var layout: UICollectionViewCompositionalLayout = {
42 | let provider: UICollectionViewCompositionalLayoutSectionProvider = { [unowned self] section, environment in
43 | lego.sections[section].layout(environment)
44 | }
45 | if let layoutConfiguration {
46 | return .init(sectionProvider: provider, configuration: layoutConfiguration)
47 | } else {
48 | return .init(sectionProvider: provider)
49 | }
50 | }()
51 |
52 | private lazy var dataSource: UICollectionViewDiffableDataSource = .init(collectionView: collectionView) { [weak self] in
53 | self?.cellProvider(collectionView: $0, indexPath: $1, itemID: $2)
54 | }
55 |
56 | public var supplementaryViewProvider: SupplementaryViewProvider? {
57 | get {
58 | dataSource.supplementaryViewProvider
59 | }
60 | set {
61 | dataSource.supplementaryViewProvider = newValue
62 | }
63 | }
64 |
65 | private var layoutConfiguration: UICollectionViewCompositionalLayoutConfiguration?
66 | public var isAnimationEnabled: Bool = true
67 |
68 | public private(set) var lego: Lego
69 | public init(lego: Lego, layoutConfiguration: UICollectionViewCompositionalLayoutConfiguration? = nil) {
70 | self.layoutConfiguration = layoutConfiguration
71 | self.lego = lego
72 | }
73 |
74 | public func render(in view: UIView, config: (UICollectionView) -> Void = { _ in }) {
75 | view.addSubview(collectionView)
76 | NSLayoutConstraint.activate([
77 | collectionView.topAnchor.constraint(equalTo: view.topAnchor),
78 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
79 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
80 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
81 | ])
82 | config(collectionView)
83 | apply(lego: lego, animatingDifferences: true)
84 | }
85 |
86 | /// Update collectionView with a specified ``Lego``
87 | /// - Parameters:
88 | /// - lego: The lego specified to apply
89 | /// - animatingDifferences: A bool value indicates whether perform animations.
90 | public func apply(lego: Lego, animatingDifferences: Bool? = nil) {
91 | var snapshot = NSDiffableDataSourceSnapshot()
92 | snapshot.appendSections(lego.sections.map { $0.id })
93 | lego.sections.forEach {
94 | snapshot.appendItems($0.items.map { $0.anyID }, toSection: $0.id)
95 | }
96 | let allIdentifiers = Set(snapshot.itemIdentifiers)
97 | for indexPath in collectionView.indexPathsForVisibleItems {
98 | guard let identifier = dataSource.itemIdentifier(for: indexPath),
99 | allIdentifiers.contains(identifier),
100 | let cell = collectionView.cellForItem(at: indexPath),
101 | let newIndexPath = lego.indexPathForItem(with: identifier)
102 | else {
103 | continue
104 | }
105 |
106 | lego[newIndexPath].updateCell(cell)
107 | }
108 | self.lego = lego
109 | dataSource.apply(snapshot, animatingDifferences: animatingDifferences ?? isAnimationEnabled)
110 | }
111 |
112 | private func cellProvider(collectionView: UICollectionView, indexPath: IndexPath, itemID: AnyHashable) -> UICollectionViewCell? {
113 | lego[indexPath].createCell(in: collectionView, at: indexPath)
114 | }
115 | }
116 |
117 | // MARK: - Subscript
118 |
119 | public extension LegoRenderer {
120 | subscript(indexPath: IndexPath) -> AnyItem {
121 | lego[indexPath]
122 | }
123 |
124 | subscript(indexPath: IndexPath, as type: C.Type) -> C? {
125 | lego[indexPath, as: type]
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Demo/Playground/Playground.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C202A26227D99B2000210384 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C202A26127D99B2000210384 /* AppDelegate.swift */; };
11 | C202A26627D99B2000210384 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C202A26527D99B2000210384 /* ViewController.swift */; };
12 | C202A26927D99B2000210384 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C202A26727D99B2000210384 /* Main.storyboard */; };
13 | C202A26B27D99B2100210384 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C202A26A27D99B2100210384 /* Assets.xcassets */; };
14 | C202A26E27D99B2100210384 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C202A26C27D99B2100210384 /* LaunchScreen.storyboard */; };
15 | C26015E327D99C4000E63536 /* LegoKit in Frameworks */ = {isa = PBXBuildFile; productRef = C26015E227D99C4000E63536 /* LegoKit */; };
16 | C26015E927DAF8C800E63536 /* GroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26015E827DAF8C800E63536 /* GroupCell.swift */; };
17 | C26015EB27DAF8D000E63536 /* UserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26015EA27DAF8D000E63536 /* UserCell.swift */; };
18 | C26015F127DAFBF500E63536 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26015F027DAFBF500E63536 /* Model.swift */; };
19 | C26AE122283F78D200E65D2C /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = C26AE121283F78D200E65D2C /* SnapKit */; };
20 | C290C3C327DB21A6005F0AEC /* LegoKit Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = C290C3C227DB21A6005F0AEC /* LegoKit Documentation.docc */; };
21 | C2FEB1F62823B616000FF4E5 /* ToggleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FEB1F52823B616000FF4E5 /* ToggleViewController.swift */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | C202A25E27D99B2000210384 /* Playground.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Playground.app; sourceTree = BUILT_PRODUCTS_DIR; };
26 | C202A26127D99B2000210384 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
27 | C202A26527D99B2000210384 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
28 | C202A26827D99B2000210384 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
29 | C202A26A27D99B2100210384 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
30 | C202A26D27D99B2100210384 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
31 | C202A26F27D99B2100210384 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
32 | C26015E427D99DFF00E63536 /* Playground.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Playground.entitlements; sourceTree = ""; };
33 | C26015E827DAF8C800E63536 /* GroupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCell.swift; sourceTree = ""; };
34 | C26015EA27DAF8D000E63536 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = ""; };
35 | C26015F027DAFBF500E63536 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; };
36 | C290C3C227DB21A6005F0AEC /* LegoKit Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = "LegoKit Documentation.docc"; sourceTree = ""; };
37 | C2FEB1F52823B616000FF4E5 /* ToggleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleViewController.swift; sourceTree = ""; };
38 | /* End PBXFileReference section */
39 |
40 | /* Begin PBXFrameworksBuildPhase section */
41 | C202A25B27D99B2000210384 /* Frameworks */ = {
42 | isa = PBXFrameworksBuildPhase;
43 | buildActionMask = 2147483647;
44 | files = (
45 | C26015E327D99C4000E63536 /* LegoKit in Frameworks */,
46 | C26AE122283F78D200E65D2C /* SnapKit in Frameworks */,
47 | );
48 | runOnlyForDeploymentPostprocessing = 0;
49 | };
50 | /* End PBXFrameworksBuildPhase section */
51 |
52 | /* Begin PBXGroup section */
53 | C202A25527D99B2000210384 = {
54 | isa = PBXGroup;
55 | children = (
56 | C290C3C227DB21A6005F0AEC /* LegoKit Documentation.docc */,
57 | C202A26027D99B2000210384 /* Playground */,
58 | C202A25F27D99B2000210384 /* Products */,
59 | C26015E127D99C4000E63536 /* Frameworks */,
60 | );
61 | sourceTree = "";
62 | };
63 | C202A25F27D99B2000210384 /* Products */ = {
64 | isa = PBXGroup;
65 | children = (
66 | C202A25E27D99B2000210384 /* Playground.app */,
67 | );
68 | name = Products;
69 | sourceTree = "";
70 | };
71 | C202A26027D99B2000210384 /* Playground */ = {
72 | isa = PBXGroup;
73 | children = (
74 | C26015ED27DAFBC800E63536 /* Resource */,
75 | C26015EE27DAFBCC00E63536 /* Source */,
76 | C26015E427D99DFF00E63536 /* Playground.entitlements */,
77 | C202A26F27D99B2100210384 /* Info.plist */,
78 | );
79 | path = Playground;
80 | sourceTree = "";
81 | };
82 | C26015E127D99C4000E63536 /* Frameworks */ = {
83 | isa = PBXGroup;
84 | children = (
85 | );
86 | name = Frameworks;
87 | sourceTree = "";
88 | };
89 | C26015E527D9AEDA00E63536 /* Item */ = {
90 | isa = PBXGroup;
91 | children = (
92 | C26015E827DAF8C800E63536 /* GroupCell.swift */,
93 | C26015EA27DAF8D000E63536 /* UserCell.swift */,
94 | );
95 | path = Item;
96 | sourceTree = "";
97 | };
98 | C26015EC27DAFBC300E63536 /* Application */ = {
99 | isa = PBXGroup;
100 | children = (
101 | C202A26127D99B2000210384 /* AppDelegate.swift */,
102 | );
103 | path = Application;
104 | sourceTree = "";
105 | };
106 | C26015ED27DAFBC800E63536 /* Resource */ = {
107 | isa = PBXGroup;
108 | children = (
109 | C202A26727D99B2000210384 /* Main.storyboard */,
110 | C202A26A27D99B2100210384 /* Assets.xcassets */,
111 | C202A26C27D99B2100210384 /* LaunchScreen.storyboard */,
112 | );
113 | path = Resource;
114 | sourceTree = "";
115 | };
116 | C26015EE27DAFBCC00E63536 /* Source */ = {
117 | isa = PBXGroup;
118 | children = (
119 | C26015EC27DAFBC300E63536 /* Application */,
120 | C26015E527D9AEDA00E63536 /* Item */,
121 | C26015EF27DAFBEB00E63536 /* Model */,
122 | C202A26527D99B2000210384 /* ViewController.swift */,
123 | C2FEB1F52823B616000FF4E5 /* ToggleViewController.swift */,
124 | );
125 | path = Source;
126 | sourceTree = "";
127 | };
128 | C26015EF27DAFBEB00E63536 /* Model */ = {
129 | isa = PBXGroup;
130 | children = (
131 | C26015F027DAFBF500E63536 /* Model.swift */,
132 | );
133 | path = Model;
134 | sourceTree = "";
135 | };
136 | /* End PBXGroup section */
137 |
138 | /* Begin PBXNativeTarget section */
139 | C202A25D27D99B2000210384 /* Playground */ = {
140 | isa = PBXNativeTarget;
141 | buildConfigurationList = C202A27227D99B2100210384 /* Build configuration list for PBXNativeTarget "Playground" */;
142 | buildPhases = (
143 | C202A25A27D99B2000210384 /* Sources */,
144 | C202A25B27D99B2000210384 /* Frameworks */,
145 | C202A25C27D99B2000210384 /* Resources */,
146 | C26AE11C283F74EA00E65D2C /* Swiftlint */,
147 | );
148 | buildRules = (
149 | );
150 | dependencies = (
151 | );
152 | name = Playground;
153 | packageProductDependencies = (
154 | C26015E227D99C4000E63536 /* LegoKit */,
155 | C26AE121283F78D200E65D2C /* SnapKit */,
156 | );
157 | productName = Playground;
158 | productReference = C202A25E27D99B2000210384 /* Playground.app */;
159 | productType = "com.apple.product-type.application";
160 | };
161 | /* End PBXNativeTarget section */
162 |
163 | /* Begin PBXProject section */
164 | C202A25627D99B2000210384 /* Project object */ = {
165 | isa = PBXProject;
166 | attributes = {
167 | BuildIndependentTargetsInParallel = 1;
168 | LastSwiftUpdateCheck = 1320;
169 | LastUpgradeCheck = 1320;
170 | TargetAttributes = {
171 | C202A25D27D99B2000210384 = {
172 | CreatedOnToolsVersion = 13.2.1;
173 | };
174 | };
175 | };
176 | buildConfigurationList = C202A25927D99B2000210384 /* Build configuration list for PBXProject "Playground" */;
177 | compatibilityVersion = "Xcode 13.0";
178 | developmentRegion = en;
179 | hasScannedForEncodings = 0;
180 | knownRegions = (
181 | en,
182 | Base,
183 | );
184 | mainGroup = C202A25527D99B2000210384;
185 | packageReferences = (
186 | C26AE120283F78D200E65D2C /* XCRemoteSwiftPackageReference "SnapKit" */,
187 | );
188 | productRefGroup = C202A25F27D99B2000210384 /* Products */;
189 | projectDirPath = "";
190 | projectRoot = "";
191 | targets = (
192 | C202A25D27D99B2000210384 /* Playground */,
193 | );
194 | };
195 | /* End PBXProject section */
196 |
197 | /* Begin PBXResourcesBuildPhase section */
198 | C202A25C27D99B2000210384 /* Resources */ = {
199 | isa = PBXResourcesBuildPhase;
200 | buildActionMask = 2147483647;
201 | files = (
202 | C202A26E27D99B2100210384 /* LaunchScreen.storyboard in Resources */,
203 | C202A26B27D99B2100210384 /* Assets.xcassets in Resources */,
204 | C202A26927D99B2000210384 /* Main.storyboard in Resources */,
205 | );
206 | runOnlyForDeploymentPostprocessing = 0;
207 | };
208 | /* End PBXResourcesBuildPhase section */
209 |
210 | /* Begin PBXShellScriptBuildPhase section */
211 | C26AE11C283F74EA00E65D2C /* Swiftlint */ = {
212 | isa = PBXShellScriptBuildPhase;
213 | alwaysOutOfDate = 1;
214 | buildActionMask = 12;
215 | files = (
216 | );
217 | inputFileListPaths = (
218 | );
219 | inputPaths = (
220 | );
221 | name = Swiftlint;
222 | outputFileListPaths = (
223 | );
224 | outputPaths = (
225 | );
226 | runOnlyForDeploymentPostprocessing = 0;
227 | shellPath = /bin/sh;
228 | shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint >/dev/null; then\n cd \"${SRCROOT}/../..\" && swiftlint --config \"${SRCROOT}/../../.swiftlint.yml\" \nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n\n\n";
229 | };
230 | /* End PBXShellScriptBuildPhase section */
231 |
232 | /* Begin PBXSourcesBuildPhase section */
233 | C202A25A27D99B2000210384 /* Sources */ = {
234 | isa = PBXSourcesBuildPhase;
235 | buildActionMask = 2147483647;
236 | files = (
237 | C26015F127DAFBF500E63536 /* Model.swift in Sources */,
238 | C26015E927DAF8C800E63536 /* GroupCell.swift in Sources */,
239 | C290C3C327DB21A6005F0AEC /* LegoKit Documentation.docc in Sources */,
240 | C202A26627D99B2000210384 /* ViewController.swift in Sources */,
241 | C26015EB27DAF8D000E63536 /* UserCell.swift in Sources */,
242 | C2FEB1F62823B616000FF4E5 /* ToggleViewController.swift in Sources */,
243 | C202A26227D99B2000210384 /* AppDelegate.swift in Sources */,
244 | );
245 | runOnlyForDeploymentPostprocessing = 0;
246 | };
247 | /* End PBXSourcesBuildPhase section */
248 |
249 | /* Begin PBXVariantGroup section */
250 | C202A26727D99B2000210384 /* Main.storyboard */ = {
251 | isa = PBXVariantGroup;
252 | children = (
253 | C202A26827D99B2000210384 /* Base */,
254 | );
255 | name = Main.storyboard;
256 | sourceTree = "";
257 | };
258 | C202A26C27D99B2100210384 /* LaunchScreen.storyboard */ = {
259 | isa = PBXVariantGroup;
260 | children = (
261 | C202A26D27D99B2100210384 /* Base */,
262 | );
263 | name = LaunchScreen.storyboard;
264 | sourceTree = "";
265 | };
266 | /* End PBXVariantGroup section */
267 |
268 | /* Begin XCBuildConfiguration section */
269 | C202A27027D99B2100210384 /* Debug */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | ALWAYS_SEARCH_USER_PATHS = NO;
273 | CLANG_ANALYZER_NONNULL = YES;
274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
276 | CLANG_CXX_LIBRARY = "libc++";
277 | CLANG_ENABLE_MODULES = YES;
278 | CLANG_ENABLE_OBJC_ARC = YES;
279 | CLANG_ENABLE_OBJC_WEAK = YES;
280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
281 | CLANG_WARN_BOOL_CONVERSION = YES;
282 | CLANG_WARN_COMMA = YES;
283 | CLANG_WARN_CONSTANT_CONVERSION = YES;
284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
287 | CLANG_WARN_EMPTY_BODY = YES;
288 | CLANG_WARN_ENUM_CONVERSION = YES;
289 | CLANG_WARN_INFINITE_RECURSION = YES;
290 | CLANG_WARN_INT_CONVERSION = YES;
291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
295 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
296 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
297 | CLANG_WARN_STRICT_PROTOTYPES = YES;
298 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
299 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
300 | CLANG_WARN_UNREACHABLE_CODE = YES;
301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
302 | COPY_PHASE_STRIP = NO;
303 | DEBUG_INFORMATION_FORMAT = dwarf;
304 | ENABLE_STRICT_OBJC_MSGSEND = YES;
305 | ENABLE_TESTABILITY = YES;
306 | GCC_C_LANGUAGE_STANDARD = gnu11;
307 | GCC_DYNAMIC_NO_PIC = NO;
308 | GCC_NO_COMMON_BLOCKS = YES;
309 | GCC_OPTIMIZATION_LEVEL = 0;
310 | GCC_PREPROCESSOR_DEFINITIONS = (
311 | "DEBUG=1",
312 | "$(inherited)",
313 | );
314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
316 | GCC_WARN_UNDECLARED_SELECTOR = YES;
317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
318 | GCC_WARN_UNUSED_FUNCTION = YES;
319 | GCC_WARN_UNUSED_VARIABLE = YES;
320 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
321 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
322 | MTL_FAST_MATH = YES;
323 | ONLY_ACTIVE_ARCH = YES;
324 | SDKROOT = iphoneos;
325 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
326 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
327 | };
328 | name = Debug;
329 | };
330 | C202A27127D99B2100210384 /* Release */ = {
331 | isa = XCBuildConfiguration;
332 | buildSettings = {
333 | ALWAYS_SEARCH_USER_PATHS = NO;
334 | CLANG_ANALYZER_NONNULL = YES;
335 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
337 | CLANG_CXX_LIBRARY = "libc++";
338 | CLANG_ENABLE_MODULES = YES;
339 | CLANG_ENABLE_OBJC_ARC = YES;
340 | CLANG_ENABLE_OBJC_WEAK = YES;
341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
342 | CLANG_WARN_BOOL_CONVERSION = YES;
343 | CLANG_WARN_COMMA = YES;
344 | CLANG_WARN_CONSTANT_CONVERSION = YES;
345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
347 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
348 | CLANG_WARN_EMPTY_BODY = YES;
349 | CLANG_WARN_ENUM_CONVERSION = YES;
350 | CLANG_WARN_INFINITE_RECURSION = YES;
351 | CLANG_WARN_INT_CONVERSION = YES;
352 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
353 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
354 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
356 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
357 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
358 | CLANG_WARN_STRICT_PROTOTYPES = YES;
359 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
360 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
361 | CLANG_WARN_UNREACHABLE_CODE = YES;
362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
363 | COPY_PHASE_STRIP = NO;
364 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
365 | ENABLE_NS_ASSERTIONS = NO;
366 | ENABLE_STRICT_OBJC_MSGSEND = YES;
367 | GCC_C_LANGUAGE_STANDARD = gnu11;
368 | GCC_NO_COMMON_BLOCKS = YES;
369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
371 | GCC_WARN_UNDECLARED_SELECTOR = YES;
372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
373 | GCC_WARN_UNUSED_FUNCTION = YES;
374 | GCC_WARN_UNUSED_VARIABLE = YES;
375 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
376 | MTL_ENABLE_DEBUG_INFO = NO;
377 | MTL_FAST_MATH = YES;
378 | SDKROOT = iphoneos;
379 | SWIFT_COMPILATION_MODE = wholemodule;
380 | SWIFT_OPTIMIZATION_LEVEL = "-O";
381 | VALIDATE_PRODUCT = YES;
382 | };
383 | name = Release;
384 | };
385 | C202A27327D99B2100210384 /* Debug */ = {
386 | isa = XCBuildConfiguration;
387 | buildSettings = {
388 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
389 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
390 | CODE_SIGN_ENTITLEMENTS = Playground/Playground.entitlements;
391 | CODE_SIGN_STYLE = Automatic;
392 | CURRENT_PROJECT_VERSION = 1;
393 | DEVELOPMENT_TEAM = 7A5GV3DH33;
394 | GENERATE_INFOPLIST_FILE = YES;
395 | INFOPLIST_FILE = Playground/Info.plist;
396 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
397 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
398 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
399 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
400 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
401 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
402 | LD_RUNPATH_SEARCH_PATHS = (
403 | "$(inherited)",
404 | "@executable_path/Frameworks",
405 | );
406 | MARKETING_VERSION = 1.0;
407 | PRODUCT_BUNDLE_IDENTIFIER = io.ijk.Playground;
408 | PRODUCT_NAME = "$(TARGET_NAME)";
409 | SUPPORTS_MACCATALYST = YES;
410 | SWIFT_EMIT_LOC_STRINGS = YES;
411 | SWIFT_VERSION = 6.0;
412 | TARGETED_DEVICE_FAMILY = "1,2";
413 | };
414 | name = Debug;
415 | };
416 | C202A27427D99B2100210384 /* Release */ = {
417 | isa = XCBuildConfiguration;
418 | buildSettings = {
419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
420 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
421 | CODE_SIGN_ENTITLEMENTS = Playground/Playground.entitlements;
422 | CODE_SIGN_STYLE = Automatic;
423 | CURRENT_PROJECT_VERSION = 1;
424 | DEVELOPMENT_TEAM = 7A5GV3DH33;
425 | GENERATE_INFOPLIST_FILE = YES;
426 | INFOPLIST_FILE = Playground/Info.plist;
427 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
428 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
429 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
430 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
431 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
432 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
433 | LD_RUNPATH_SEARCH_PATHS = (
434 | "$(inherited)",
435 | "@executable_path/Frameworks",
436 | );
437 | MARKETING_VERSION = 1.0;
438 | PRODUCT_BUNDLE_IDENTIFIER = io.ijk.Playground;
439 | PRODUCT_NAME = "$(TARGET_NAME)";
440 | SUPPORTS_MACCATALYST = YES;
441 | SWIFT_EMIT_LOC_STRINGS = YES;
442 | SWIFT_VERSION = 6.0;
443 | TARGETED_DEVICE_FAMILY = "1,2";
444 | };
445 | name = Release;
446 | };
447 | /* End XCBuildConfiguration section */
448 |
449 | /* Begin XCConfigurationList section */
450 | C202A25927D99B2000210384 /* Build configuration list for PBXProject "Playground" */ = {
451 | isa = XCConfigurationList;
452 | buildConfigurations = (
453 | C202A27027D99B2100210384 /* Debug */,
454 | C202A27127D99B2100210384 /* Release */,
455 | );
456 | defaultConfigurationIsVisible = 0;
457 | defaultConfigurationName = Release;
458 | };
459 | C202A27227D99B2100210384 /* Build configuration list for PBXNativeTarget "Playground" */ = {
460 | isa = XCConfigurationList;
461 | buildConfigurations = (
462 | C202A27327D99B2100210384 /* Debug */,
463 | C202A27427D99B2100210384 /* Release */,
464 | );
465 | defaultConfigurationIsVisible = 0;
466 | defaultConfigurationName = Release;
467 | };
468 | /* End XCConfigurationList section */
469 |
470 | /* Begin XCRemoteSwiftPackageReference section */
471 | C26AE120283F78D200E65D2C /* XCRemoteSwiftPackageReference "SnapKit" */ = {
472 | isa = XCRemoteSwiftPackageReference;
473 | repositoryURL = "https://github.com/SnapKit/SnapKit";
474 | requirement = {
475 | kind = upToNextMajorVersion;
476 | minimumVersion = 5.0.0;
477 | };
478 | };
479 | /* End XCRemoteSwiftPackageReference section */
480 |
481 | /* Begin XCSwiftPackageProductDependency section */
482 | C26015E227D99C4000E63536 /* LegoKit */ = {
483 | isa = XCSwiftPackageProductDependency;
484 | productName = LegoKit;
485 | };
486 | C26AE121283F78D200E65D2C /* SnapKit */ = {
487 | isa = XCSwiftPackageProductDependency;
488 | package = C26AE120283F78D200E65D2C /* XCRemoteSwiftPackageReference "SnapKit" */;
489 | productName = SnapKit;
490 | };
491 | /* End XCSwiftPackageProductDependency section */
492 | };
493 | rootObject = C202A25627D99B2000210384 /* Project object */;
494 | }
495 |
--------------------------------------------------------------------------------