├── .gitattributes
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ ├── CoreRender-Package.xcscheme
│ ├── CoreRender.xcscheme
│ ├── CoreRenderObjC.xcscheme
│ ├── CoreRenderObjCTests.xcscheme
│ └── CoreRenderTests.xcscheme
├── Demo
├── CoreRenderDemo.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── CoreRenderDemo
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ ├── ContentView.swift
│ ├── DemoWidget.swift
│ ├── Info.plist
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── ViewController.swift
├── Package.swift
├── README.md
├── Sources
├── CoreRender
│ ├── Bridge.swift
│ ├── CommonNodeTypes.swift
│ └── Gestures.swift
└── CoreRenderObjC
│ ├── CRContext.h
│ ├── CRContext.mm
│ ├── CRCoordinator+Private.h
│ ├── CRCoordinator.h
│ ├── CRCoordinator.mm
│ ├── CRHostingView.h
│ ├── CRHostingView.mm
│ ├── CRMacros.h
│ ├── CRNode.h
│ ├── CRNode.mm
│ ├── CRNodeBridge.h
│ ├── CRNodeBridge.mm
│ ├── CRNodeBuilder+Modifiers.h
│ ├── CRNodeBuilder+Modifiers.mm
│ ├── CRNodeBuilder.h
│ ├── CRNodeBuilder.mm
│ ├── CRNodeHierarchy.h
│ ├── CRNodeHierarchy.mm
│ ├── CRNodeLayoutSpec.h
│ ├── CRNodeLayoutSpec.mm
│ ├── CRUmbrellaHeader.h
│ ├── UIView+CRNode.h
│ ├── UIView+CRNode.mm
│ ├── YGLayout.h
│ ├── YGLayout.mm
│ ├── Yoga.c
│ ├── Yoga.h
│ └── include
│ └── CoreRenderObjC.h
├── Tests
├── CoreRenderObjCTests
│ └── CRNodeTests.m
├── CoreRenderTests
│ ├── BridgeTest.swift
│ └── XCTestManifests.swift
└── LinuxMain.swift
├── docs
└── assets
│ ├── carbon_4.png
│ ├── logo_new.png
│ └── screen_2.png
└── format_objc.sh
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.c linguist-language=ObjectiveC++
2 | *.mm linguist-language=ObjectiveC++
3 | *.mm linguist-language=ObjectiveC++
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | .DS_Store
6 |
7 | ## Build generated
8 | build/
9 | DerivedData/
10 |
11 | ## Various settings
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 | xcuserdata/
21 |
22 | ## Other
23 | *.moved-aside
24 | *.xccheckout
25 | *.xcscmblueprint
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | # CocoaPods
34 | #
35 | # We recommend against adding the Pods directory to your .gitignore. However
36 | # you should judge for yourself, the pros and cons are mentioned at:
37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
38 | #
39 | # Pods/
40 |
41 | # Carthage
42 | #
43 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
44 | # Carthage/Checkouts
45 |
46 | Carthage/Build
47 |
48 | # fastlane
49 | #
50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
51 | # screenshots whenever they are needed.
52 | # For more information about the recommended setup visit:
53 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
54 |
55 | fastlane/report.xml
56 | fastlane/Preview.html
57 | fastlane/screenshots/**/*.png
58 | fastlane/test_output
59 |
60 | # Code Injection
61 | #
62 | # After new code Injection tools there's a generated folder /iOSInjectionProject
63 | # https://github.com/johnno1962/injectionforxcode
64 |
65 | iOSInjectionProject/
66 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/CoreRender-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
57 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
75 |
81 |
82 |
83 |
85 |
91 |
92 |
93 |
94 |
95 |
105 |
106 |
112 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/CoreRender.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/CoreRenderObjC.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/CoreRenderObjCTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/CoreRenderTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1622F3D223687938007C7E00 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1622F3D123687938007C7E00 /* AppDelegate.swift */; };
11 | 1622F3D623687938007C7E00 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1622F3D523687938007C7E00 /* ContentView.swift */; };
12 | 1622F3D82368793A007C7E00 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1622F3D72368793A007C7E00 /* Assets.xcassets */; };
13 | 1622F3DB2368793A007C7E00 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1622F3DA2368793A007C7E00 /* Preview Assets.xcassets */; };
14 | 1622F3DE2368793A007C7E00 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1622F3DC2368793A007C7E00 /* LaunchScreen.storyboard */; };
15 | 1622F3E623687988007C7E00 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1622F3E523687988007C7E00 /* ViewController.swift */; };
16 | 16E97B6223687B7E00CEFB67 /* CoreRenderObjC in Frameworks */ = {isa = PBXBuildFile; productRef = 16E97B6123687B7E00CEFB67 /* CoreRenderObjC */; };
17 | 16E97B6423687B7E00CEFB67 /* CoreRender in Frameworks */ = {isa = PBXBuildFile; productRef = 16E97B6323687B7E00CEFB67 /* CoreRender */; };
18 | 16E97B6623687C3D00CEFB67 /* DemoWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E97B6523687C3D00CEFB67 /* DemoWidget.swift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXFileReference section */
22 | 1622F3CE23687938007C7E00 /* CoreRenderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoreRenderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
23 | 1622F3D123687938007C7E00 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
24 | 1622F3D523687938007C7E00 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
25 | 1622F3D72368793A007C7E00 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
26 | 1622F3DA2368793A007C7E00 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
27 | 1622F3DD2368793A007C7E00 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 1622F3DF2368793A007C7E00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 1622F3E523687988007C7E00 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
30 | 16E97B6523687C3D00CEFB67 /* DemoWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoWidget.swift; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 1622F3CB23687938007C7E00 /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | 16E97B6223687B7E00CEFB67 /* CoreRenderObjC in Frameworks */,
39 | 16E97B6423687B7E00CEFB67 /* CoreRender in Frameworks */,
40 | );
41 | runOnlyForDeploymentPostprocessing = 0;
42 | };
43 | /* End PBXFrameworksBuildPhase section */
44 |
45 | /* Begin PBXGroup section */
46 | 1622F3C523687938007C7E00 = {
47 | isa = PBXGroup;
48 | children = (
49 | 1622F3D023687938007C7E00 /* CoreRenderDemo */,
50 | 1622F3CF23687938007C7E00 /* Products */,
51 | );
52 | sourceTree = "";
53 | };
54 | 1622F3CF23687938007C7E00 /* Products */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 1622F3CE23687938007C7E00 /* CoreRenderDemo.app */,
58 | );
59 | name = Products;
60 | sourceTree = "";
61 | };
62 | 1622F3D023687938007C7E00 /* CoreRenderDemo */ = {
63 | isa = PBXGroup;
64 | children = (
65 | 1622F3D123687938007C7E00 /* AppDelegate.swift */,
66 | 16E97B6523687C3D00CEFB67 /* DemoWidget.swift */,
67 | 1622F3D523687938007C7E00 /* ContentView.swift */,
68 | 1622F3E523687988007C7E00 /* ViewController.swift */,
69 | 1622F3D72368793A007C7E00 /* Assets.xcassets */,
70 | 1622F3DC2368793A007C7E00 /* LaunchScreen.storyboard */,
71 | 1622F3DF2368793A007C7E00 /* Info.plist */,
72 | 1622F3D92368793A007C7E00 /* Preview Content */,
73 | );
74 | path = CoreRenderDemo;
75 | sourceTree = "";
76 | };
77 | 1622F3D92368793A007C7E00 /* Preview Content */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 1622F3DA2368793A007C7E00 /* Preview Assets.xcassets */,
81 | );
82 | path = "Preview Content";
83 | sourceTree = "";
84 | };
85 | /* End PBXGroup section */
86 |
87 | /* Begin PBXNativeTarget section */
88 | 1622F3CD23687938007C7E00 /* CoreRenderDemo */ = {
89 | isa = PBXNativeTarget;
90 | buildConfigurationList = 1622F3E22368793A007C7E00 /* Build configuration list for PBXNativeTarget "CoreRenderDemo" */;
91 | buildPhases = (
92 | 1622F3CA23687938007C7E00 /* Sources */,
93 | 1622F3CB23687938007C7E00 /* Frameworks */,
94 | 1622F3CC23687938007C7E00 /* Resources */,
95 | );
96 | buildRules = (
97 | );
98 | dependencies = (
99 | );
100 | name = CoreRenderDemo;
101 | packageProductDependencies = (
102 | 16E97B6123687B7E00CEFB67 /* CoreRenderObjC */,
103 | 16E97B6323687B7E00CEFB67 /* CoreRender */,
104 | );
105 | productName = CoreRenderDemo;
106 | productReference = 1622F3CE23687938007C7E00 /* CoreRenderDemo.app */;
107 | productType = "com.apple.product-type.application";
108 | };
109 | /* End PBXNativeTarget section */
110 |
111 | /* Begin PBXProject section */
112 | 1622F3C623687938007C7E00 /* Project object */ = {
113 | isa = PBXProject;
114 | attributes = {
115 | LastSwiftUpdateCheck = 1110;
116 | LastUpgradeCheck = 1110;
117 | ORGANIZATIONNAME = "Alex Usbergo";
118 | TargetAttributes = {
119 | 1622F3CD23687938007C7E00 = {
120 | CreatedOnToolsVersion = 11.1;
121 | };
122 | };
123 | };
124 | buildConfigurationList = 1622F3C923687938007C7E00 /* Build configuration list for PBXProject "CoreRenderDemo" */;
125 | compatibilityVersion = "Xcode 9.3";
126 | developmentRegion = en;
127 | hasScannedForEncodings = 0;
128 | knownRegions = (
129 | en,
130 | Base,
131 | );
132 | mainGroup = 1622F3C523687938007C7E00;
133 | packageReferences = (
134 | 16E97B6023687B7E00CEFB67 /* XCRemoteSwiftPackageReference "CoreRender" */,
135 | );
136 | productRefGroup = 1622F3CF23687938007C7E00 /* Products */;
137 | projectDirPath = "";
138 | projectRoot = "";
139 | targets = (
140 | 1622F3CD23687938007C7E00 /* CoreRenderDemo */,
141 | );
142 | };
143 | /* End PBXProject section */
144 |
145 | /* Begin PBXResourcesBuildPhase section */
146 | 1622F3CC23687938007C7E00 /* Resources */ = {
147 | isa = PBXResourcesBuildPhase;
148 | buildActionMask = 2147483647;
149 | files = (
150 | 1622F3DE2368793A007C7E00 /* LaunchScreen.storyboard in Resources */,
151 | 1622F3DB2368793A007C7E00 /* Preview Assets.xcassets in Resources */,
152 | 1622F3D82368793A007C7E00 /* Assets.xcassets in Resources */,
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXResourcesBuildPhase section */
157 |
158 | /* Begin PBXSourcesBuildPhase section */
159 | 1622F3CA23687938007C7E00 /* Sources */ = {
160 | isa = PBXSourcesBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | 1622F3D223687938007C7E00 /* AppDelegate.swift in Sources */,
164 | 1622F3E623687988007C7E00 /* ViewController.swift in Sources */,
165 | 16E97B6623687C3D00CEFB67 /* DemoWidget.swift in Sources */,
166 | 1622F3D623687938007C7E00 /* ContentView.swift in Sources */,
167 | );
168 | runOnlyForDeploymentPostprocessing = 0;
169 | };
170 | /* End PBXSourcesBuildPhase section */
171 |
172 | /* Begin PBXVariantGroup section */
173 | 1622F3DC2368793A007C7E00 /* LaunchScreen.storyboard */ = {
174 | isa = PBXVariantGroup;
175 | children = (
176 | 1622F3DD2368793A007C7E00 /* Base */,
177 | );
178 | name = LaunchScreen.storyboard;
179 | sourceTree = "";
180 | };
181 | /* End PBXVariantGroup section */
182 |
183 | /* Begin XCBuildConfiguration section */
184 | 1622F3E02368793A007C7E00 /* Debug */ = {
185 | isa = XCBuildConfiguration;
186 | buildSettings = {
187 | ALWAYS_SEARCH_USER_PATHS = NO;
188 | CLANG_ANALYZER_NONNULL = YES;
189 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
190 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
191 | CLANG_CXX_LIBRARY = "libc++";
192 | CLANG_ENABLE_MODULES = YES;
193 | CLANG_ENABLE_OBJC_ARC = YES;
194 | CLANG_ENABLE_OBJC_WEAK = YES;
195 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
196 | CLANG_WARN_BOOL_CONVERSION = YES;
197 | CLANG_WARN_COMMA = YES;
198 | CLANG_WARN_CONSTANT_CONVERSION = YES;
199 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
200 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
201 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
202 | CLANG_WARN_EMPTY_BODY = YES;
203 | CLANG_WARN_ENUM_CONVERSION = YES;
204 | CLANG_WARN_INFINITE_RECURSION = YES;
205 | CLANG_WARN_INT_CONVERSION = YES;
206 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
207 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
208 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
209 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
210 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
211 | CLANG_WARN_STRICT_PROTOTYPES = YES;
212 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
213 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
214 | CLANG_WARN_UNREACHABLE_CODE = YES;
215 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
216 | COPY_PHASE_STRIP = NO;
217 | DEBUG_INFORMATION_FORMAT = dwarf;
218 | ENABLE_STRICT_OBJC_MSGSEND = YES;
219 | ENABLE_TESTABILITY = YES;
220 | GCC_C_LANGUAGE_STANDARD = gnu11;
221 | GCC_DYNAMIC_NO_PIC = NO;
222 | GCC_NO_COMMON_BLOCKS = YES;
223 | GCC_OPTIMIZATION_LEVEL = 0;
224 | GCC_PREPROCESSOR_DEFINITIONS = (
225 | "DEBUG=1",
226 | "$(inherited)",
227 | );
228 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
229 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
230 | GCC_WARN_UNDECLARED_SELECTOR = YES;
231 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
232 | GCC_WARN_UNUSED_FUNCTION = YES;
233 | GCC_WARN_UNUSED_VARIABLE = YES;
234 | IPHONEOS_DEPLOYMENT_TARGET = 13.1;
235 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
236 | MTL_FAST_MATH = YES;
237 | ONLY_ACTIVE_ARCH = YES;
238 | SDKROOT = iphoneos;
239 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
240 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
241 | };
242 | name = Debug;
243 | };
244 | 1622F3E12368793A007C7E00 /* Release */ = {
245 | isa = XCBuildConfiguration;
246 | buildSettings = {
247 | ALWAYS_SEARCH_USER_PATHS = NO;
248 | CLANG_ANALYZER_NONNULL = YES;
249 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
250 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
251 | CLANG_CXX_LIBRARY = "libc++";
252 | CLANG_ENABLE_MODULES = YES;
253 | CLANG_ENABLE_OBJC_ARC = YES;
254 | CLANG_ENABLE_OBJC_WEAK = YES;
255 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
256 | CLANG_WARN_BOOL_CONVERSION = YES;
257 | CLANG_WARN_COMMA = YES;
258 | CLANG_WARN_CONSTANT_CONVERSION = YES;
259 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
260 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
261 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
262 | CLANG_WARN_EMPTY_BODY = YES;
263 | CLANG_WARN_ENUM_CONVERSION = YES;
264 | CLANG_WARN_INFINITE_RECURSION = YES;
265 | CLANG_WARN_INT_CONVERSION = YES;
266 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
267 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
268 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
269 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
270 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
271 | CLANG_WARN_STRICT_PROTOTYPES = YES;
272 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
273 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
274 | CLANG_WARN_UNREACHABLE_CODE = YES;
275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
276 | COPY_PHASE_STRIP = NO;
277 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
278 | ENABLE_NS_ASSERTIONS = NO;
279 | ENABLE_STRICT_OBJC_MSGSEND = YES;
280 | GCC_C_LANGUAGE_STANDARD = gnu11;
281 | GCC_NO_COMMON_BLOCKS = YES;
282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
284 | GCC_WARN_UNDECLARED_SELECTOR = YES;
285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
286 | GCC_WARN_UNUSED_FUNCTION = YES;
287 | GCC_WARN_UNUSED_VARIABLE = YES;
288 | IPHONEOS_DEPLOYMENT_TARGET = 13.1;
289 | MTL_ENABLE_DEBUG_INFO = NO;
290 | MTL_FAST_MATH = YES;
291 | SDKROOT = iphoneos;
292 | SWIFT_COMPILATION_MODE = wholemodule;
293 | SWIFT_OPTIMIZATION_LEVEL = "-O";
294 | VALIDATE_PRODUCT = YES;
295 | };
296 | name = Release;
297 | };
298 | 1622F3E32368793A007C7E00 /* Debug */ = {
299 | isa = XCBuildConfiguration;
300 | buildSettings = {
301 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
302 | CODE_SIGN_STYLE = Automatic;
303 | DEVELOPMENT_ASSET_PATHS = "\"CoreRenderDemo/Preview Content\"";
304 | DEVELOPMENT_TEAM = 9Z67YL2L6Z;
305 | ENABLE_PREVIEWS = YES;
306 | INFOPLIST_FILE = CoreRenderDemo/Info.plist;
307 | LD_RUNPATH_SEARCH_PATHS = (
308 | "$(inherited)",
309 | "@executable_path/Frameworks",
310 | );
311 | PRODUCT_BUNDLE_IDENTIFIER = io.coreRender.CoreRenderDemo;
312 | PRODUCT_NAME = "$(TARGET_NAME)";
313 | SWIFT_VERSION = 5.0;
314 | TARGETED_DEVICE_FAMILY = "1,2";
315 | };
316 | name = Debug;
317 | };
318 | 1622F3E42368793A007C7E00 /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | buildSettings = {
321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
322 | CODE_SIGN_STYLE = Automatic;
323 | DEVELOPMENT_ASSET_PATHS = "\"CoreRenderDemo/Preview Content\"";
324 | DEVELOPMENT_TEAM = 9Z67YL2L6Z;
325 | ENABLE_PREVIEWS = YES;
326 | INFOPLIST_FILE = CoreRenderDemo/Info.plist;
327 | LD_RUNPATH_SEARCH_PATHS = (
328 | "$(inherited)",
329 | "@executable_path/Frameworks",
330 | );
331 | PRODUCT_BUNDLE_IDENTIFIER = io.coreRender.CoreRenderDemo;
332 | PRODUCT_NAME = "$(TARGET_NAME)";
333 | SWIFT_VERSION = 5.0;
334 | TARGETED_DEVICE_FAMILY = "1,2";
335 | };
336 | name = Release;
337 | };
338 | /* End XCBuildConfiguration section */
339 |
340 | /* Begin XCConfigurationList section */
341 | 1622F3C923687938007C7E00 /* Build configuration list for PBXProject "CoreRenderDemo" */ = {
342 | isa = XCConfigurationList;
343 | buildConfigurations = (
344 | 1622F3E02368793A007C7E00 /* Debug */,
345 | 1622F3E12368793A007C7E00 /* Release */,
346 | );
347 | defaultConfigurationIsVisible = 0;
348 | defaultConfigurationName = Release;
349 | };
350 | 1622F3E22368793A007C7E00 /* Build configuration list for PBXNativeTarget "CoreRenderDemo" */ = {
351 | isa = XCConfigurationList;
352 | buildConfigurations = (
353 | 1622F3E32368793A007C7E00 /* Debug */,
354 | 1622F3E42368793A007C7E00 /* Release */,
355 | );
356 | defaultConfigurationIsVisible = 0;
357 | defaultConfigurationName = Release;
358 | };
359 | /* End XCConfigurationList section */
360 |
361 | /* Begin XCRemoteSwiftPackageReference section */
362 | 16E97B6023687B7E00CEFB67 /* XCRemoteSwiftPackageReference "CoreRender" */ = {
363 | isa = XCRemoteSwiftPackageReference;
364 | repositoryURL = "https://github.com/alexdrone/CoreRender";
365 | requirement = {
366 | branch = master;
367 | kind = branch;
368 | };
369 | };
370 | /* End XCRemoteSwiftPackageReference section */
371 |
372 | /* Begin XCSwiftPackageProductDependency section */
373 | 16E97B6123687B7E00CEFB67 /* CoreRenderObjC */ = {
374 | isa = XCSwiftPackageProductDependency;
375 | package = 16E97B6023687B7E00CEFB67 /* XCRemoteSwiftPackageReference "CoreRender" */;
376 | productName = CoreRenderObjC;
377 | };
378 | 16E97B6323687B7E00CEFB67 /* CoreRender */ = {
379 | isa = XCSwiftPackageProductDependency;
380 | package = 16E97B6023687B7E00CEFB67 /* XCRemoteSwiftPackageReference "CoreRender" */;
381 | productName = CoreRender;
382 | };
383 | /* End XCSwiftPackageProductDependency section */
384 | };
385 | rootObject = 1622F3C623687938007C7E00 /* Project object */;
386 | }
387 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CoreRender",
6 | "repositoryURL": "https://github.com/alexdrone/CoreRender",
7 | "state": {
8 | "branch": "master",
9 | "revision": "f037c40be86d247577a3c7bde3264b5ea2140a20",
10 | "version": null
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 |
4 | @UIApplicationMain
5 | class AppDelegate: UIResponder, UIApplicationDelegate {
6 | func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
9 | return true
10 | }
11 |
12 | func application(
13 | _ application: UIApplication,
14 | configurationForConnecting connectingSceneSession: UISceneSession,
15 | options: UIScene.ConnectionOptions
16 | ) -> UISceneConfiguration {
17 | // Called when a new scene session is being created.
18 | // Use this method to select a configuration to create the new scene with.
19 | return UISceneConfiguration(
20 | name: "Default Configuration",
21 | sessionRole: connectingSceneSession.role)
22 | }
23 |
24 | func application(
25 | _ application: UIApplication,
26 | didDiscardSceneSessions sceneSessions: Set) {
27 | // Called when the user discards a scene session.
28 | // If any sessions were discarded while the application was not running, this will be called
29 | // shortly after application:didFinishLaunchingWithOptions.
30 | // Use this method to release any resources that were specific to the discarded scenes,
31 | //as they will not return.
32 | }
33 | }
34 |
35 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
36 |
37 | var useSwiftUI = false
38 | var window: UIWindow?
39 |
40 | func scene(
41 | _ scene: UIScene,
42 | willConnectTo session: UISceneSession,
43 | options connectionOptions: UIScene.ConnectionOptions) {
44 |
45 | if (useSwiftUI) {
46 | let contentView = ContentView()
47 | // Use a UIHostingCoordinator as window root view coordinator.
48 | if let windowScene = scene as? UIWindowScene {
49 | let window = UIWindow(windowScene: windowScene)
50 | window.rootViewController = UIHostingController(rootView: contentView)
51 | self.window = window
52 | window.makeKeyAndVisible()
53 | }
54 | } else {
55 | // Use a normal ViewCoordinator as window root view coordinator.
56 | if let windowScene = scene as? UIWindowScene {
57 | let window = UIWindow(windowScene: windowScene)
58 | window.rootViewController = ViewCoordinator()
59 | self.window = window
60 | window.makeKeyAndVisible()
61 | }
62 | }
63 | }
64 |
65 | func sceneDidDisconnect(_ scene: UIScene) {
66 | }
67 |
68 | func sceneDidBecomeActive(_ scene: UIScene) {
69 | }
70 |
71 | func sceneWillResignActive(_ scene: UIScene) {
72 | }
73 |
74 | func sceneWillEnterForeground(_ scene: UIScene) {
75 | }
76 |
77 | func sceneDidEnterBackground(_ scene: UIScene) {
78 | }
79 | }
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/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 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CoreRender
3 | import CoreRenderObjC
4 |
5 | struct ContentView: View {
6 | var body: some View {
7 | Text("Hello World")
8 | }
9 | }
10 |
11 | struct ContentView_Previews: PreviewProvider {
12 | static var previews: some View {
13 | ContentView()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/DemoWidget.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreRender
3 | import CoreRenderObjC
4 |
5 | // MARK: - Coordinator
6 |
7 | class DemoWidgetCoordinator: Coordinator {
8 | var count: UInt = 0
9 | var isRotated: Bool = false
10 |
11 | @objc dynamic func increase() {
12 | count += 1
13 | setNeedsReconcile()
14 | }
15 |
16 | override func onLayout() {
17 | // Override this to manually override the layout of some of the views in the view hierarchy.
18 | // e.g.
19 | // view(withKey: Const.increaseButtonKey)?.frame = ...
20 | }
21 | }
22 |
23 | // MARK: - Body
24 |
25 | func makeDemoWidget(context: Context, coordinator: DemoWidgetCoordinator) -> OpaqueNodeBuilder {
26 | VStackNode {
27 | LabelNode(text: "\(coordinator.count)")
28 | .font(UIFont.systemFont(ofSize: 24, weight: .black))
29 | .textAlignment(.center)
30 | .textColor(.darkText)
31 | .background(.secondarySystemBackground)
32 | .width(Const.size + 8 * CGFloat(coordinator.count))
33 | .height(Const.size)
34 | .margin(Const.margin)
35 | .cornerRadius(Const.cornerRadius)
36 | LabelNode(text: ">> TAP HERE TO SPIN THE BUTTON >>")
37 | .font(UIFont.systemFont(ofSize: 12, weight: .bold))
38 | .textAlignment(.center)
39 | .textColor(.systemOrange)
40 | .height(Const.size)
41 | .margin(Const.margin)
42 | .userInteractionEnabled(true)
43 | .onTouchUpInside { _ in
44 | coordinator.doSomeFunkyStuff()
45 | }
46 | HStackNode {
47 | ButtonNode(key: Const.increaseButtonKey)
48 | .text("TAP HERE TO INCREASE COUNT")
49 | .font(UIFont.systemFont(ofSize: 12, weight: .bold))
50 | .setTarget(
51 | coordinator, action: #selector(DemoWidgetCoordinator.increase), for: .touchUpInside)
52 | .background(.systemTeal)
53 | .padding(Const.margin * 2)
54 | .cornerRadius(Const.cornerRadius)
55 | EmptyNode()
56 | }
57 | }
58 | .alignItems(.center)
59 | .matchHostingViewWidth(withMargin: 0)
60 | }
61 |
62 | // MARK: - Manual View Manipulation Example
63 |
64 | extension DemoWidgetCoordinator {
65 | // Example of manual access to the underlying view hierarchy.
66 | // Transitions can be performed in the node description as well, this is just an
67 | // example of manual view hierarchy manipulation.
68 | func doSomeFunkyStuff() {
69 | let transform = isRotated
70 | ? CGAffineTransform.identity
71 | : CGAffineTransform.init(rotationAngle: .pi)
72 | isRotated.toggle()
73 | UIView.animate(withDuration: 1) {
74 | self.view(withKey: Const.increaseButtonKey)?.transform = transform
75 | }
76 | }
77 | }
78 |
79 | // MARK: - Constants
80 |
81 | struct Const {
82 | static let increaseButtonKey = "button_increase"
83 | static let size: CGFloat = 48.0
84 | static let cornerRadius: CGFloat = 8.0
85 | static let margin: CGFloat = 4.0
86 | }
87 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Demo/CoreRenderDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import CoreRender
3 | import CoreRenderObjC
4 |
5 | class ViewCoordinator: UIViewController {
6 | var hostingView: HostingView!
7 | let context = Context()
8 |
9 | override func loadView() {
10 | hostingView = HostingView(context: context, with: [.useSafeAreaInsets]) { context in
11 | Component(context: context) { context, coordinator in
12 | makeDemoWidget(context: context, coordinator: coordinator)
13 | }.builder()
14 | }
15 | self.view = hostingView
16 | }
17 |
18 | override func viewDidLayoutSubviews() {
19 | super.viewDidLayoutSubviews()
20 | hostingView.setNeedsLayout()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "CoreRender",
8 | platforms: [
9 | .iOS(.v10)
10 | ],
11 | products: [
12 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
13 | .library(
14 | name: "CoreRenderObjC",
15 | targets: ["CoreRenderObjC"]),
16 | .library(
17 | name: "CoreRender",
18 | targets: ["CoreRender"]),
19 | ],
20 | dependencies: [
21 | // Dependencies declare other packages that this package depends on.
22 | // .package(url: /* package url */, from: "1.0.0"),
23 | ],
24 | targets: [
25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
26 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
27 | .target(
28 | name: "CoreRenderObjC",
29 | dependencies: []),
30 | .target(
31 | name: "CoreRender",
32 | dependencies: ["CoreRenderObjC"]),
33 | .testTarget(
34 | name: "CoreRenderObjCTests",
35 | dependencies: ["CoreRenderObjC"]),
36 | .testTarget(
37 | name: "CoreRenderTests",
38 | dependencies: ["CoreRenderObjC", "CoreRender"]),
39 | ]
40 | )
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Moved to https://github.com/alexdrone/Render
2 |
--------------------------------------------------------------------------------
/Sources/CoreRender/Bridge.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import CoreRenderObjC
4 |
5 | // MARK: - Component
6 |
7 | /// A piece of user interface.
8 | /// This is a transient object that represent the description of a particulat subtree at a
9 | /// given state.
10 | ///
11 | /// You create custom views by declaring types that conform to the `Component`
12 | /// protocol. Implement the required `body` property to provide the content
13 | /// and behavior for your custom view.
14 | public struct Component: OpaqueNodeBuilderConvertible {
15 | private let context: Context
16 | private let key: String
17 | private let props: [AnyProp]
18 | private let body: (Context, C) -> OpaqueNodeBuilder
19 |
20 | public init(
21 | context: Context,
22 | key: String = NSStringFromClass(C.self),
23 | props: [AnyProp] = [],
24 | body: @escaping (Context, C) -> OpaqueNodeBuilder
25 | ) {
26 | self.context = context
27 | self.key = key
28 | self.props = props
29 | self.body = body
30 | }
31 |
32 | /// Forward the call to `build` to return the root node for this hierarchy.
33 | public func builder() -> OpaqueNodeBuilder {
34 | makeComponent(type: C.self, context: context, key: key, props: props, body: body)
35 | }
36 | }
37 |
38 | /// Pure function builder for `Component`.
39 | public func makeComponent(
40 | type: C.Type,
41 | context: Context,
42 | key: String = NSStringFromClass(C.self),
43 | props: [AnyProp] = [],
44 | body: (Context, C) -> OpaqueNodeBuilder
45 | ) -> OpaqueNodeBuilder {
46 | let reuseIdentifier = NSStringFromClass(C.self)
47 | let coordinator = context.coordinator(CoordinatorDescriptor(type: C.self, key: key)) as! C
48 | for setter in props {
49 | setter.apply(coordinator: coordinator)
50 | }
51 | return body(context, coordinator)
52 | .withReuseIdentifier(reuseIdentifier)
53 | .withCoordinator(coordinator)
54 | }
55 |
56 | // MARK: - OpaqueNodeBuilderConvertible
57 |
58 | public protocol OpaqueNodeBuilderConvertible {
59 | func builder() -> OpaqueNodeBuilder
60 | }
61 |
62 | extension OpaqueNodeBuilder: OpaqueNodeBuilderConvertible {
63 | public func builder() -> OpaqueNodeBuilder { self }
64 | }
65 |
66 | // MARK: - Function builders
67 |
68 | /// Node builder.
69 | ///
70 | /// - `withReuseIdentifier`: The reuse identifier for this node is its hierarchy.
71 | /// Identifiers help Render understand which items have changed.
72 | /// A custom *reuseIdentifier* is mandatory if the node has a custom creation closure.
73 | /// - `withKey`: A unique key for the component/node (necessary if the associated
74 | /// component is stateful).
75 | /// - `withViewInit`: Custom view initialization closure.
76 | /// - `withLayoutSpec`: This closure is invoked whenever the layout is performed.
77 | /// Configure your backing view by using the *UILayout* object (e.g.):
78 | /// ```
79 | /// ... { spec in
80 | /// spec.set(\UIView.backgroundColor, value: .green)
81 | /// spec.set(\UIView.layer.borderWidth, value: 1)
82 | /// ```
83 | /// You can also access to the view directly (this is less performant because the infrastructure
84 | /// can't keep tracks of these view changes, but necessary when coping with more complex view
85 | /// configuration methods).
86 | /// ```
87 | /// ... { spec in
88 | /// spec.view.backgroundColor = .green
89 | /// spec.view.setTitle("FOO", for: .normal)
90 | /// ```
91 | /// - `withCoordinatorDescriptor:initialState.props`: Associates a coordinator to this node.
92 | /// - `build`: Builds the concrete node.
93 | public func Node(
94 | _ type: V.Type = V.self,
95 | @ContentBuilder builder: () -> ChildrenBuilder = ChildrenBuilder.default
96 | ) -> NodeBuilder {
97 | let children = builder().children.compactMap { $0 as? ConcreteNode }
98 | return NodeBuilder(type: type).withChildren(children)
99 | }
100 |
101 | @_functionBuilder
102 | public struct ContentBuilder {
103 | public static func buildBlock(
104 | _ nodes: OpaqueNodeBuilderConvertible...
105 | ) -> ChildrenBuilder {
106 | return ChildrenBuilder(children: nodes.map { $0.builder().build() })
107 | }
108 | }
109 |
110 | /// Intermediate structure used as a return type from @_ContentBuilder.
111 | public struct ChildrenBuilder {
112 | /// Default (no children).
113 | public static let none = ChildrenBuilder(children: [])
114 | /// Returns an empty builder.
115 | public static let `default`: () -> ChildrenBuilder = {
116 | return ChildrenBuilder.none
117 | }
118 | /// The wrapped childrens.
119 | let children: [AnyNode]
120 | }
121 |
122 | // MARK: - Props
123 |
124 | public protocol AnyProp {
125 | /// Setup the coordinator with the given prop.
126 | func apply(coordinator: Coordinator)
127 | }
128 |
129 | /// Any custom-defined property in the coordinator, that is not internal state.
130 | public struct Prop: AnyProp {
131 | public typealias CoordinatorType = C
132 | public let keyPath: ReferenceWritableKeyPath
133 | public let value: V
134 |
135 | public init(_ keyPath: ReferenceWritableKeyPath, _ value: V) {
136 | self.keyPath = keyPath
137 | self.value = value
138 | }
139 | /// Setup the coordinator with the given prop.
140 | public func apply(coordinator: Coordinator) {
141 | guard let coordinator = coordinator as? C else { return }
142 | coordinator[keyPath: keyPath] = value
143 | }
144 | }
145 |
146 | /// Any custom-defined configuration closure for the coordinator.
147 | public struct BlockProp {
148 | public let block: (C) -> Void
149 |
150 | public init(_ block: @escaping (C) -> Void) {
151 | self.block = block
152 | }
153 | /// Setup the coordinator with the given prop.
154 | public func apply(coordinator: Coordinator) {
155 | guard let coordinator = coordinator as? C else { return }
156 | block(coordinator)
157 | }
158 | }
159 |
160 | // MARK: - Property setters
161 |
162 | /// Sets the value of a desired keypath using typesafe writable reference keypaths.
163 | /// - parameter spec: The *LayoutSpec* object that is currently handling the view configuration.
164 | /// - parameter keyPath: The target keypath.
165 | /// - parameter value: The new desired value.
166 | /// - parameter animator: Optional property animator for this change.
167 | public func withProperty(
168 | in spec: LayoutSpec,
169 | keyPath: ReferenceWritableKeyPath,
170 | value: T,
171 | animator: UIViewPropertyAnimator? = nil
172 | ) -> Void {
173 | guard let kvc = keyPath._kvcKeyPathString else {
174 | print("\(keyPath) is not a KVC property.")
175 | return
176 | }
177 | spec.set(kvc, value: value, animator: animator);
178 | }
179 |
180 | public func withProperty(
181 | in spec: LayoutSpec,
182 | keyPath: ReferenceWritableKeyPath,
183 | value: T,
184 | animator: UIViewPropertyAnimator? = nil
185 | ) -> Void {
186 | guard let kvc = keyPath._kvcKeyPathString else {
187 | print("\(keyPath) is not a KVC property.")
188 | return
189 | }
190 | let nsValue = NSNumber(value: value.rawValue)
191 | spec.set(kvc, value: nsValue, animator: animator)
192 | }
193 |
194 | // MARK: - Alias types
195 |
196 | // Drops the YG prefix.
197 | public typealias FlexDirection = YGFlexDirection
198 | public typealias Align = YGAlign
199 | public typealias Edge = YGEdge
200 | public typealias Wrap = YGWrap
201 | public typealias Display = YGDisplay
202 | public typealias Overflow = YGOverflow
203 |
204 | public typealias LayoutOptions = CRNodeLayoutOptions
205 |
206 | // Ensure that Yoga's C-enums are accessibly through KeyPathRefs.
207 | public protocol WritableKeyPathBoxableEnum {
208 | var rawValue: Int32 { get }
209 | }
210 |
211 | extension YGFlexDirection: WritableKeyPathBoxableEnum { }
212 | extension YGAlign: WritableKeyPathBoxableEnum { }
213 | extension YGEdge: WritableKeyPathBoxableEnum { }
214 | extension YGWrap: WritableKeyPathBoxableEnum { }
215 | extension YGDisplay: WritableKeyPathBoxableEnum { }
216 | extension YGOverflow: WritableKeyPathBoxableEnum { }
217 |
--------------------------------------------------------------------------------
/Sources/CoreRender/CommonNodeTypes.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import CoreRenderObjC
4 |
5 | // MARK: - Common Nodes
6 |
7 | public func ViewNode(
8 | @ContentBuilder builder: () -> ChildrenBuilder = ChildrenBuilder.default
9 | ) -> NodeBuilder {
10 | Node(UIView.self, builder: builder)
11 | }
12 |
13 | public func HStackNode (
14 | @ContentBuilder builder: () -> ChildrenBuilder = ChildrenBuilder.default
15 | ) -> NodeBuilder {
16 | Node(UIView.self, builder: builder).withLayoutSpec { spec in
17 | guard let yoga = spec.view?.yoga else { return }
18 | yoga.flexDirection = .row
19 | yoga.justifyContent = .flexStart
20 | yoga.alignItems = .flexStart
21 | yoga.flex()
22 | }
23 | }
24 |
25 | public func VStackNode(
26 | @ContentBuilder builder: () -> ChildrenBuilder = ChildrenBuilder.default
27 | ) -> NodeBuilder {
28 | Node(UIView.self, builder: builder).withLayoutSpec { spec in
29 | guard let yoga = spec.view?.yoga else { return }
30 | yoga.flexDirection = .column
31 | yoga.justifyContent = .flexStart
32 | yoga.alignItems = .flexStart
33 | yoga.flex()
34 | }
35 | }
36 |
37 | public func LabelNode(
38 | text: String,
39 | @ContentBuilder builder: () -> ChildrenBuilder = ChildrenBuilder.default
40 | ) -> NodeBuilder {
41 | Node(UILabel.self, builder: builder).withLayoutSpec { spec in
42 | guard let view = spec.view else { return }
43 | view.text = text
44 | }
45 | }
46 |
47 | public func ButtonNode(
48 | key: String,
49 | @ContentBuilder builder: () -> ChildrenBuilder = ChildrenBuilder.default
50 | ) -> NodeBuilder {
51 | Node(UIButton.self, builder: builder).withKey(key)
52 | }
53 |
54 | public func EmptyNode() -> NullNodeBuilder {
55 | return NullNodeBuilder()
56 | }
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Sources/CoreRender/Gestures.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import CoreRenderObjC
4 |
5 | // MARK: - NodeBuilder
6 |
7 | extension OpaqueNodeBuilder {
8 | public func onTouchUpInside(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
9 | withLayoutSpec { spec in spec.view?.onTouchUpInside(handler) }
10 | }
11 |
12 | public func onTouchDown(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
13 | withLayoutSpec { spec in spec.view?.onTouchDown(handler) }
14 | }
15 |
16 | public func onDoubleTap(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
17 | withLayoutSpec { spec in spec.view?.onDoubleTap(handler) }
18 | }
19 |
20 | public func onLongPress(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
21 | withLayoutSpec { spec in spec.view?.onLongPress(handler) }
22 | }
23 |
24 | public func onSwipeLeft(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
25 | withLayoutSpec { spec in spec.view?.onSwipeLeft(handler) }
26 | }
27 |
28 | public func onSwipeRight(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
29 | withLayoutSpec { spec in spec.view?.onSwipeRight(handler) }
30 | }
31 |
32 | public func onSwipeUp(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
33 | withLayoutSpec { spec in spec.view?.onSwipeUp(handler) }
34 | }
35 |
36 | public func onSwipeDown(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
37 | withLayoutSpec { spec in spec.view?.onSwipeDown(handler) }
38 | }
39 |
40 | public func onPan(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
41 | withLayoutSpec { spec in spec.view?.onPan(handler) }
42 | }
43 |
44 | public func onPinch(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
45 | withLayoutSpec { spec in spec.view?.onPinch(handler) }
46 | }
47 |
48 | public func onRotate(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
49 | withLayoutSpec { spec in spec.view?.onRotate(handler) }
50 | }
51 |
52 | public func onScreenEdgePan(_ handler: @escaping (UIGestureRecognizer) -> Void) -> Self {
53 | withLayoutSpec { spec in spec.view?.onScreenEdgePan(handler) }
54 | }
55 | }
56 |
57 | // MARK: - GestureRecognizer
58 |
59 | extension UIView {
60 | /// All of the gesture recognizers registered through the closure based api.
61 | var gestureRecognizerProxyDictionary: NSMutableDictionary {
62 | get {
63 | if let obj = objc_getAssociatedObject(self, &__handler) as? NSMutableDictionary {
64 | return obj
65 | }
66 | let obj = NSMutableDictionary()
67 | objc_setAssociatedObject(self, &__handler, obj, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
68 | return obj
69 | }
70 | set {
71 | objc_setAssociatedObject(self, &__handler, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
72 | }
73 | }
74 |
75 | /// Flush all of the existing gesture recognizers registered through the closure based api.
76 | public func flushGestureRecognizers() {
77 | guard let array = gestureRecognizerProxyDictionary.allValues as? [WeakGestureRecognizer] else {
78 | return
79 | }
80 | for obj in array {
81 | obj.handler = nil
82 | if let gesture = obj.object {
83 | gesture.removeTarget(nil, action: nil)
84 | gesture.view?.removeGestureRecognizer(gesture)
85 | }
86 | obj.object = nil
87 | }
88 | gestureRecognizerProxyDictionary = NSMutableDictionary()
89 | }
90 |
91 | /// Flush all of the existing gesture recognizers registered through the closure based api.
92 | public func flushGestureRecognizersRecursively() {
93 | flushGestureRecognizers()
94 | for subview in subviews {
95 | subview.flushGestureRecognizersRecursively()
96 | }
97 | }
98 | }
99 |
100 | extension UIView {
101 | func onGestureRecognizer(
102 | type: T.Type,
103 | key: NSString,
104 | numberOfTapsRequired: Int = 1,
105 | numberOfTouchesRequired: Int = 1,
106 | direction: UISwipeGestureRecognizer.Direction = UISwipeGestureRecognizer.Direction.down,
107 | _ handler: @escaping (UIGestureRecognizer) -> Void
108 | ) -> Void {
109 | let wrapper = WeakGestureRecognizer()
110 | wrapper.handler = handler
111 | let selector = #selector(WeakGestureRecognizer.handle(sender:))
112 | let gesture = T(target: wrapper, action: selector)
113 | wrapper.object = gesture
114 | if let tapGesture = gesture as? UITapGestureRecognizer {
115 | tapGesture.numberOfTapsRequired = numberOfTapsRequired
116 | tapGesture.numberOfTouchesRequired = numberOfTouchesRequired
117 | }
118 | if let swipeGesture = gesture as? UISwipeGestureRecognizer {
119 | swipeGesture.direction = direction
120 | }
121 | // Safely remove the old gesture recognizer.
122 | if let old = gestureRecognizerProxyDictionary.object(forKey: key) as? WeakGestureRecognizer,
123 | let oldGesture = old.object {
124 | old.handler = nil
125 | old.object = nil
126 | oldGesture.removeTarget(nil, action: nil)
127 | oldGesture.view?.removeGestureRecognizer(oldGesture)
128 | }
129 | gestureRecognizerProxyDictionary.setObject(wrapper, forKey: key)
130 | addGestureRecognizer(gesture)
131 | }
132 |
133 | public func onTouchUpInside(_ handler: @escaping (UIGestureRecognizer) -> Void) {
134 | onGestureRecognizer(
135 | type: UITapGestureRecognizer.self,
136 | key: "\(#function)" as NSString,
137 | handler)
138 | }
139 |
140 | public func onTouchDown(_ handler: @escaping (UIGestureRecognizer) -> Void) {
141 | onGestureRecognizer(
142 | type: TouchDownGestureRecognizer.self,
143 | key: "\(#function)" as NSString,
144 | handler)
145 | }
146 |
147 | public func onDoubleTap(_ handler: @escaping (UIGestureRecognizer) -> Void) {
148 | onGestureRecognizer(
149 | type: UITapGestureRecognizer.self,
150 | key: "\(#function)" as NSString,
151 | numberOfTapsRequired: 2,
152 | handler)
153 | }
154 |
155 | public func onLongPress(_ handler: @escaping (UIGestureRecognizer) -> Void) {
156 | onGestureRecognizer(
157 | type: UILongPressGestureRecognizer.self,
158 | key: "\(#function)" as NSString,
159 | handler)
160 | }
161 |
162 | public func onSwipeLeft(_ handler: @escaping (UIGestureRecognizer) -> Void) {
163 | onGestureRecognizer(
164 | type: UISwipeGestureRecognizer.self,
165 | key: "\(#function)" as NSString,
166 | direction: UISwipeGestureRecognizer.Direction.left,
167 | handler)
168 | }
169 |
170 | public func onSwipeRight(_ handler: @escaping (UIGestureRecognizer) -> Void) {
171 | onGestureRecognizer(
172 | type: UISwipeGestureRecognizer.self,
173 | key: "\(#function)" as NSString,
174 | direction: UISwipeGestureRecognizer.Direction.right,
175 | handler)
176 | }
177 |
178 | public func onSwipeUp(_ handler: @escaping (UIGestureRecognizer) -> Void) {
179 | onGestureRecognizer(
180 | type: UISwipeGestureRecognizer.self,
181 | key: "\(#function)" as NSString,
182 | direction: UISwipeGestureRecognizer.Direction.up,
183 | handler)
184 | }
185 |
186 | public func onSwipeDown(_ handler: @escaping (UIGestureRecognizer) -> Void) {
187 | onGestureRecognizer(
188 | type: UISwipeGestureRecognizer.self,
189 | key: "\(#function)" as NSString,
190 | direction: UISwipeGestureRecognizer.Direction.down,
191 | handler)
192 | }
193 |
194 | public func onPan(_ handler: @escaping (UIGestureRecognizer) -> Void) {
195 | onGestureRecognizer(
196 | type: UIPanGestureRecognizer.self,
197 | key: "\(#function)" as NSString,
198 | handler)
199 | }
200 |
201 | public func onPinch(_ handler: @escaping (UIGestureRecognizer) -> Void) {
202 | onGestureRecognizer(
203 | type: UIPinchGestureRecognizer.self,
204 | key: "\(#function)" as NSString,
205 | handler)
206 | }
207 |
208 | public func onRotate(_ handler: @escaping (UIGestureRecognizer) -> Void) {
209 | onGestureRecognizer(
210 | type: UIRotationGestureRecognizer.self,
211 | key: "\(#function)" as NSString,
212 | handler)
213 | }
214 |
215 | public func onScreenEdgePan(_ handler: @escaping (UIGestureRecognizer) -> Void) {
216 | onGestureRecognizer(
217 | type: UIScreenEdgePanGestureRecognizer.self,
218 | key: "\(#function)" as NSString,
219 | handler)
220 | }
221 | }
222 |
223 | // MARK: - Private
224 |
225 | fileprivate class WeakGestureRecognizer: NSObject {
226 | weak var object: UIGestureRecognizer?
227 | var handler: ((UIGestureRecognizer) -> Void)? = nil
228 |
229 | @objc func handle(sender: UIGestureRecognizer) {
230 | handler?(sender)
231 | }
232 | }
233 |
234 | fileprivate var __handler: UInt8 = 0
235 |
236 | class TouchDownGestureRecognizer: UIGestureRecognizer {
237 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
238 | if self.state == .possible {
239 | self.state = .recognized
240 | }
241 | }
242 | override func touchesMoved(_ touches: Set, with event: UIEvent) {
243 | self.state = .failed
244 | }
245 | override func touchesEnded(_ touches: Set, with event: UIEvent) {
246 | self.state = .failed
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRContext.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | NS_ASSUME_NONNULL_BEGIN
5 |
6 | @class CRCoordinator;
7 | @class CRNode;
8 | @class CRContext;
9 | ;
10 | @class CRContextReconciliationInfo;
11 |
12 | NS_SWIFT_NAME(CoordinatorDescriptor)
13 | @interface CRCoordinatorDescriptor : NSObject
14 | /// The coordinator type.
15 | @property(nonatomic, readonly) Class type;
16 | /// The coordinator unique key.
17 | @property(nonatomic, readonly) NSString *key;
18 |
19 | - (instancetype)init NS_UNAVAILABLE;
20 | /// Constructs a new coordinator descriptor.
21 | - (instancetype)initWithType:(Class)type key:(NSString *)key;
22 | @end
23 |
24 | NS_SWIFT_NAME(ContextDelegate)
25 | @protocol CRContextDelegate
26 | /// One of the coordinator is about to invoke @c setNeedReconciliate on the root node.
27 | - (void)context:(CRContext *)context willReconciliateHieararchy:(CRContextReconciliationInfo *)info;
28 | /// Node/View hierarchy reconciliation has just occurred.
29 | - (void)context:(CRContext *)context didReconciliateHieararchy:(CRContextReconciliationInfo *)info;
30 | @end
31 |
32 | NS_SWIFT_NAME(Context)
33 | @interface CRContext : NSObject
34 | /// Layout animator for the nodes registered to this context.
35 | @property(nonatomic, nullable) UIViewPropertyAnimator *layoutAnimator;
36 |
37 | /// Returns the coordinator (or instantiate a new one) of type @c type for the unique identifier
38 | /// passed as argument.
39 | /// @note: Returns @c nil if @c type is not a subclass of @c CRCoordinator (or if it's a statelss
40 | /// coordinator).
41 | - (__kindof CRCoordinator *)coordinator:(CRCoordinatorDescriptor *)descriptor;
42 |
43 | /// Add the object as delegate for this context.
44 | - (void)addDelegate:(id)delegate;
45 |
46 | /// Remove the object as delegate (if necessary).
47 | - (void)removeDelegate:(id)delegate;
48 |
49 | @end
50 |
51 | NS_SWIFT_NAME(ContextReconciliationInfo)
52 | @interface CRContextReconciliationInfo : NSObject
53 | /// Explictly inform the delegate that if the nodes are wrapped inside a @c UITableView or
54 | /// a @c UICollectionView, this must be invalidated and its data reloaded.
55 | @property(nonatomic, readonly) BOOL mustInvalidateLayout;
56 | /// The keys of all of the nodes that have had a rect change during the last reconciliation.
57 | @property(nonatomic, readonly) NSArray *keysForNodesWithMutatedSize;
58 | /// Layout animator that is going to be used for the upcoming reconciliation.
59 | @property(nonatomic, readonly, nullable) UIViewPropertyAnimator *layoutAnimator;
60 |
61 | @end
62 |
63 | NS_ASSUME_NONNULL_END
64 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRContext.mm:
--------------------------------------------------------------------------------
1 | #import "CRContext.h"
2 | #import "CRCoordinator+Private.h"
3 | #import "CRMacros.h"
4 | #import "CRNode.h"
5 |
6 | #pragma mark - CRCoordinatorDescriptor
7 |
8 | @implementation CRCoordinatorDescriptor
9 |
10 | - (instancetype)initWithType:(Class)type key:(NSString *)key {
11 | if (self = [super init]) {
12 | _type = type;
13 | _key = key;
14 | }
15 | return self;
16 | }
17 |
18 | - (BOOL)isEqual:(id)object {
19 | if (object == nil) return;
20 | if (![object isKindOfClass:CRCoordinatorDescriptor.class]) return;
21 | const auto rhs = CR_DYNAMIC_CAST(CRCoordinatorDescriptor, object);
22 | return [rhs.type isEqual:self.type] && [rhs.key isEqualToString:self.key];
23 | }
24 |
25 | @end
26 |
27 | #pragma mark - CRContext
28 |
29 | @implementation CRContext {
30 | NSMutableDictionary *>
31 | *_coordinators;
32 | NSPointerArray *_delegates;
33 | }
34 |
35 | - (instancetype)init {
36 | if (self = [super init]) {
37 | _coordinators = @{}.mutableCopy;
38 | _delegates = [NSPointerArray weakObjectsPointerArray];
39 | }
40 | return self;
41 | }
42 |
43 | - (__kindof CRCoordinator *)coordinator:(CRCoordinatorDescriptor *)desc {
44 | CR_ASSERT_ON_MAIN_THREAD();
45 | if (![desc.type isSubclassOfClass:CRCoordinator.self]) return nil;
46 | const auto container = [self _containerForType:desc.type];
47 | if (const auto coordinator = container[desc.key]) {
48 | return coordinator;
49 | }
50 | const auto coordinator = CR_DYNAMIC_CAST(CRCoordinator, [[desc.type alloc] initWithKey:desc.key]);
51 | coordinator.key = desc.key;
52 | coordinator.context = self;
53 | container[desc.key] = coordinator;
54 | return coordinator;
55 | }
56 |
57 | - (NSMutableDictionary *)_containerForType:(Class)type {
58 | const auto str = NSStringFromClass(type);
59 | if (const auto container = _coordinators[str]) return container;
60 | const auto container = [[NSMutableDictionary alloc] init];
61 | _coordinators[str] = container;
62 | return container;
63 | }
64 |
65 | - (void)addDelegate:(id)delegate {
66 | CR_ASSERT_ON_MAIN_THREAD();
67 | [_delegates compact];
68 | for (NSUInteger i = 0; i < _delegates.count; i++)
69 | if ([_delegates pointerAtIndex:i] == (__bridge void *)(delegate)) return;
70 | [_delegates addPointer:(__bridge void *)delegate];
71 | }
72 |
73 | - (void)removeDelegate:(id)delegate {
74 | CR_ASSERT_ON_MAIN_THREAD();
75 | [_delegates compact];
76 | NSUInteger removeIdx = NSNotFound;
77 | for (NSUInteger i = 0; i < _delegates.count; i++)
78 | if ([_delegates pointerAtIndex:i] == (__bridge void *)(delegate)) {
79 | removeIdx = i;
80 | break;
81 | }
82 | if (removeIdx != NSNotFound) [_delegates removePointerAtIndex:removeIdx];
83 | }
84 |
85 | @end
86 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRCoordinator+Private.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRCoordinator.h"
5 |
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | @class CRNode;
9 | @class CRContext;
10 |
11 | @interface CRCoordinator ()
12 | // Private setter modifiers
13 | @property(nonatomic, readwrite) NSString *key;
14 | @property(nonatomic, readwrite, nullable, weak) CRContext *context;
15 | @property(nonatomic, readwrite, nullable, weak) CRNode *node;
16 |
17 | /// @note: Never call the init method manually - coordinators are dynamically constructed,
18 | /// disposed and reused by @c CRContext.
19 | - (instancetype)initWithKey:(NSString *)key;
20 |
21 | @end
22 |
23 | NS_ASSUME_NONNULL_END
24 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRCoordinator.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | NS_ASSUME_NONNULL_BEGIN
5 |
6 | @class CRContext;
7 | @class CRNode;
8 | @class CRNodeHierarchy;
9 | @class CRCoordinatorDescriptor;
10 | @protocol CRNodeDelegate;
11 |
12 | NS_SWIFT_NAME(Coordinator)
13 | @interface CRCoordinator : NSObject
14 | /// The context associated with this coordinator.
15 | @property(nonatomic, readonly, nullable, weak) CRContext *context;
16 | /// The key for this coordinator.
17 | /// If this coordinator is @c transient the value of this property is @c CRCoordinatorStatelessKey.
18 | @property(nonatomic, readonly) NSString *key;
19 | /// The UI node assigned to this coordinator.
20 | @property(nonatomic, readonly, nullable, weak) CRNodeHierarchy *body;
21 | /// The UI node assigned to this coordinator.
22 | @property(nonatomic, readonly, nullable, weak) CRNode *node;
23 | /// Returns the coordinator descriptor.
24 | @property(nonatomic, readonly) CRCoordinatorDescriptor *prototype;
25 |
26 | /// Coordinators are instantiated from @c CRContext.
27 | - (instancetype)init;
28 |
29 | /// Constructs a new node hierarchy and reconciles it against the currently mounted view hierarchy.
30 | - (void)setNeedsReconcile;
31 |
32 | /// Tells the already mounted hierarchy must be re-layout.
33 | /// @note This is preferable to @c setNeedsReconcile whenever there's going to be no changes in
34 | /// the view hierarchy,
35 | - (void)setNeedsLayout;
36 |
37 | /// Overrides this method to manually configure the view hierarchy after it has been layed out.
38 | - (void)onLayout;
39 |
40 | /// Returns the view in the subtree of this node with the given @c key.
41 | - (nullable UIView *)viewWithKey:(NSString *)key;
42 |
43 | /// Returns all the views that have been registered with the given @c reuseIdentifier.
44 | - (NSArray *)viewsWithReuseIdentifier:(NSString *)reuseIdentifier;
45 |
46 | @end
47 |
48 | NS_ASSUME_NONNULL_END
49 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRCoordinator.mm:
--------------------------------------------------------------------------------
1 | #import "CRContext.h"
2 |
3 | #import "CRCoordinator+Private.h"
4 | #import "CRMacros.h"
5 | #import "CRNode.h"
6 | #import "CRNodeHierarchy.h"
7 |
8 | NSString *CRIllegalCoordinatorTypeExceptionName = @"IllegalCoordinatorType";
9 |
10 | #pragma mark - Coordinator
11 |
12 | @implementation CRCoordinator
13 |
14 | - (instancetype)init {
15 | if (self = [super init]) {
16 | }
17 | return self;
18 | }
19 |
20 | - (CRNodeHierarchy *)body {
21 | return _node.nodeHierarchy;
22 | }
23 |
24 | - (CRCoordinatorDescriptor *)prototype {
25 | return [[CRCoordinatorDescriptor alloc] initWithType:self.class key:self.key];
26 | }
27 |
28 | // Private constructor.
29 | - (instancetype)initWithKey:(NSString *)key {
30 | if (self = [super init]) {
31 | _key = key;
32 | }
33 | return self;
34 | }
35 |
36 | - (void)setNeedsReconcile {
37 | CR_ASSERT_ON_MAIN_THREAD();
38 | [self.body setNeedsReconcile];
39 | }
40 |
41 | - (void)setNeedsLayout {
42 | CR_ASSERT_ON_MAIN_THREAD();
43 | [self.body setNeedsLayout];
44 | }
45 |
46 | - (void)onLayout {
47 | CR_ASSERT_ON_MAIN_THREAD();
48 | }
49 |
50 | - (UIView *)viewWithKey:(NSString *)key {
51 | return [self.body.root viewWithKey:key];
52 | }
53 |
54 | - (NSArray *)viewsWithReuseIdentifier:(NSString *)reuseIdentifier {
55 | return [self.body.root viewsWithReuseIdentifier:reuseIdentifier];
56 | }
57 |
58 | @end
59 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRHostingView.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRNode.h"
5 |
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | @class CRContext;
9 | @class CRNodeHierarchy;
10 | @class CROpaqueNodeBuilder;
11 |
12 | NS_SWIFT_NAME(HostingView)
13 | @interface CRHostingView : UIView
14 | /// The exposed node hierarchy.
15 | @property(nonatomic, readonly) CRNodeHierarchy *body;
16 |
17 | - (instancetype)init NS_UNAVAILABLE;
18 | - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
19 | - (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
20 |
21 | /// Construct a new hosting view with the given reference context.
22 | - (instancetype)initWithContext:(CRContext *)context
23 | withOptions:(CRNodeLayoutOptions)options
24 | body:(CROpaqueNodeBuilder * (^)(CRContext *))buildBody
25 | NS_DESIGNATED_INITIALIZER;
26 |
27 | /// Tells the node that the node/view hierarchy must be reconciled.
28 | - (void)setNeedsReconcile;
29 |
30 | /// Tells the node that the node/view hierarchy must be re-layout.
31 | /// @note This is preferable to @c setNeedsReconcile whenever there's going to be no changes in
32 | /// the view hierarchy,
33 | - (void)setNeedsLayout;
34 |
35 | @end
36 |
37 | NS_ASSUME_NONNULL_END
38 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRHostingView.mm:
--------------------------------------------------------------------------------
1 | #include "CRHostingView.h"
2 |
3 | #include "CRContext.h"
4 | #include "CRMacros.h"
5 | #include "CRNode.h"
6 | #include "CRNodeBuilder.h"
7 | #include "CRNodeHierarchy.h"
8 |
9 | @implementation CRHostingView {
10 | __weak CRContext *_context;
11 | CRNodeLayoutOptions _options;
12 | CRNodeHierarchy *_body;
13 | }
14 |
15 | - (instancetype)initWithContext:(CRContext *)context
16 | withOptions:(CRNodeLayoutOptions)options
17 | body:(CROpaqueNodeBuilder * (^)(CRContext *))buildBody {
18 | if (self = [super initWithFrame:CGRectZero]) {
19 | if (@available(iOS 13, *)) {
20 | self.backgroundColor = [UIColor systemBackgroundColor];
21 | } else {
22 | self.backgroundColor = [UIColor whiteColor];
23 | }
24 | _context = context;
25 | _options = options;
26 | _body = [[CRNodeHierarchy alloc] initWithContext:context nodeHierarchyBuilder:buildBody];
27 | [_body buildHierarchyInView:self constrainedToSize:self.bounds.size withOptions:options];
28 | }
29 | return self;
30 | }
31 |
32 | - (void)setNeedsReconcile {
33 | CR_ASSERT_ON_MAIN_THREAD();
34 | [_body reconcileInView:self constrainedToSize:self.bounds.size withOptions:_options];
35 | }
36 |
37 | - (void)setNeedsLayout {
38 | CR_ASSERT_ON_MAIN_THREAD();
39 | [_body layoutConstrainedToSize:self.bounds.size withOptions:_options];
40 | }
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRMacros.h:
--------------------------------------------------------------------------------
1 | #ifndef CRInternalMacros_h
2 | #define CRInternalMacros_h
3 |
4 | #import
5 | #import
6 |
7 | #pragma mark - Type inference and dynamic casts
8 |
9 | // Type inference for local variables.
10 | #if defined(__cplusplus)
11 | #else
12 | #define auto __auto_type
13 | #endif
14 |
15 | // Equivalent to swift nil coalescing operator '??'.
16 | #if defined(__cplusplus)
17 | template
18 | static inline T *_Nonnull CRNilCoalescing(T *_Nullable value, T *_Nonnull defaultValue) {
19 | return value != nil ? value : defaultValue;
20 | }
21 | #define CR_NIL_COALESCING(VALUE, DEFAULT) CRNilCoalescing(VALUE, DEFAULT)
22 | #else
23 | #define CR_NIL_COALESCING(VALUE, DEFAULT) (VALUE != nil ? VALUE : DEFAULT)
24 | #endif
25 |
26 | /// Mirrors Swift's 'as?' operator.
27 | #if defined(__cplusplus)
28 | template
29 | static inline T *_Nullable CRDynamicCast(__unsafe_unretained id _Nullable obj,
30 | bool assert = false) {
31 | if ([(id)obj isKindOfClass:[T class]]) {
32 | return obj;
33 | }
34 | return nil;
35 | }
36 | template
37 | static inline T *_Nonnull CRDynamicCastOrAssert(__unsafe_unretained id _Nullable obj) {
38 | return (T * _Nonnull) CRDynamicCast(obj, true);
39 | }
40 | #define CR_DYNAMIC_CAST(TYPE, VALUE) CRDynamicCast(VALUE)
41 | #define CR_DYNAMIC_CAST_OR_ASSERT(TYPE, VALUE) CRDynamicCastOrAssert(VALUE)
42 | #else
43 | static inline id CRDynamicCast(__unsafe_unretained id obj, Class type, BOOL assert) {
44 | if ([(id)obj isKindOfClass:type]) {
45 | return obj;
46 | }
47 | if (assert) {
48 | NSCAssert(NO, @"failed to cast %@ to %@", obj, type);
49 | }
50 | return nil;
51 | }
52 | #define CR_DYNAMIC_CAST(TYPE, VALUE) ((TYPE * _Nullable) CRDynamicCast(VALUE, TYPE.class, NO))
53 | #define CR_DYNAMIC_CAST_OR_ASSERT(TYPE, VALUE) \
54 | ((TYPE * _Nonnull) CRDynamicCast(VALUE, TYPE.class, true))
55 | #endif
56 |
57 | #pragma mark - Weakify
58 |
59 | #define CR_WEAKNAME_(VAR) VAR##_weak_
60 |
61 | #define CR_WEAKIFY(VAR) __weak __typeof__(VAR) CR_WEAKNAME_(VAR) = (VAR)
62 |
63 | #define CR_STRONGIFY(VAR) \
64 | _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wshadow\"") \
65 | __strong __typeof__(VAR) VAR = CR_WEAKNAME_(VAR); \
66 | _Pragma("clang diagnostic pop")
67 |
68 | #define CR_STRONGIFY_AND_RETURN_IF_NIL(VAR) \
69 | CR_STRONGIFY(VAR); \
70 | if (!(VAR)) { \
71 | return; \
72 | }
73 |
74 | // Safe keypath litterals.
75 | #define CR_UNSAFE_KEYPATH(p) @ #p
76 |
77 | #if DEBUG
78 | #define CR_KEYPATH(o, p) ((void)(NO && ((void)o.p, NO)), @ #p)
79 | #else
80 | #define CR_KEYPATH(o, p) @ #p
81 | #endif
82 |
83 | #pragma mark - Misc
84 |
85 | // Equivalent to Swift's @noescape.
86 | #define CR_NOESCAPE __attribute__((noescape))
87 |
88 | // Ensure the caller method is being invoked on the main thread.
89 | #define CR_ASSERT_ON_MAIN_THREAD() NSAssert(NSThread.isMainThread, @"called off the main thread.")
90 |
91 | #pragma mark - Geometry
92 |
93 | #define CR_CLAMP(x, low, high) \
94 | ({ \
95 | __typeof__(x) __x = (x); \
96 | __typeof__(low) __low = (low); \
97 | __typeof__(high) __high = (high); \
98 | __x > __high ? __high : (__x < __low ? __low : __x); \
99 | })
100 |
101 | #define CR_CGFLOAT_MAX 32768
102 | #define CR_CGFLOAT_UNDEFINED YGUndefined
103 | #define CR_CGFLOAT_FLEXIBLE CR_CGFLOAT_MAX
104 | #define CR_NORMALIZE(value) (value >= 0.0 && value <= CR_CGFLOAT_MAX ? value : 0.0)
105 |
106 | #pragma mark - Boxable structs
107 |
108 | // Ensure the struct can be boxed in a NSValue by using the @ symbol.
109 | #define CR_OBJC_BOXABLE __attribute__((objc_boxable))
110 |
111 | typedef struct CR_OBJC_BOXABLE CGPoint CGPoint;
112 | typedef struct CR_OBJC_BOXABLE CGSize CGSize;
113 | typedef struct CR_OBJC_BOXABLE CGRect CGRect;
114 | typedef struct CR_OBJC_BOXABLE CGVector CGVector;
115 | typedef struct CR_OBJC_BOXABLE UIEdgeInsets UIEdgeInsets;
116 | typedef struct CR_OBJC_BOXABLE _NSRange NSRange;
117 | typedef struct CR_OBJC_BOXABLE CGAffineTransform CGAffineTransform;
118 |
119 | #pragma mark - Generics
120 |
121 | NS_ASSUME_NONNULL_BEGIN
122 |
123 | @protocol CRFastEnumeration
124 | - (id)CR_enumeratedType;
125 | @end
126 |
127 | // Usage: CR_FOREACH (s, strings) { ... }
128 | // For each loops using type inference.
129 | #define CR_FOREACH(element, collection) \
130 | for (typeof((collection).CR_enumeratedType) element in (collection))
131 |
132 | @interface NSArray (CRFastEnumeration)
133 | - (ElementType)CR_enumeratedType;
134 | @end
135 |
136 | @interface NSSet (CRFastEnumeration)
137 | - (ElementType)CR_enumeratedType;
138 | @end
139 |
140 | @interface NSDictionary (CRFastEnumeration)
141 | - (KeyType)CR_enumeratedType;
142 | @end
143 |
144 | /// This overrides the NSObject declaration of copy with specialized ones that retain
145 | // the generic type.
146 | // This is pure compiler sugar and will create additional warnings for type mismatches.
147 | // @note id-casted objects will create a warning when copy is called on them as there are multiple
148 | // declarations available. Either cast to specific type or to NSObject to work around this.
149 | @interface NSArray (CRSafeCopy)
150 | // Same as `copy` but retains the generic type.
151 | - (NSArray *)copy;
152 | // Same as `mutableCopy` but retains the generic type.
153 | - (NSMutableArray *)mutableCopy;
154 | @end
155 |
156 | @interface NSSet (CRSafeCopy)
157 | // Same as `copy` but retains the generic type.
158 | - (NSSet *)copy;
159 | // Same as `mutableCopy` but retains the generic type.
160 | - (NSMutableSet *)mutableCopy;
161 | @end
162 |
163 | @interface NSDictionary (CRSafeCopy)
164 | // Same as `copy` but retains the generic type.
165 | - (NSDictionary *)copy;
166 | // Same as `mutableCopy` but retains the generic type.
167 | - (NSMutableDictionary *)mutableCopy;
168 | @end
169 |
170 | @interface NSOrderedSet (CRSafeCopy)
171 | // Same as `copy` but retains the generic type.
172 | - (NSOrderedSet *)copy;
173 | // Same as `mutableCopy` but retains the generic type.
174 | - (NSMutableOrderedSet *)mutableCopy;
175 | @end
176 |
177 | @interface NSHashTable (CRSafeCopy)
178 | // Same as `copy` but retains the generic type.
179 | - (NSHashTable *)copy;
180 | @end
181 |
182 | @interface NSMapTable (CRSafeCopy)
183 | // Same as `copy` but retains the generic type.
184 | - (NSMapTable *)copy;
185 | @end
186 |
187 | NS_ASSUME_NONNULL_END
188 |
189 | #pragma mark - NSArray to std::vector and viceversa
190 |
191 | #if defined(__cplusplus)
192 | #include
193 |
194 | NS_ASSUME_NONNULL_BEGIN
195 |
196 | template
197 | static inline NSArray *CRArrayWithVector(const std::vector &vector,
198 | id (^block)(const T &value)) {
199 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:vector.size()];
200 | for (const T &value : vector) {
201 | [result addObject:block(value)];
202 | }
203 |
204 | return result;
205 | }
206 |
207 | template
208 | static inline std::vector CRVectorWithElements(id array,
209 | T (^_Nullable block)(id value)) {
210 | std::vector result;
211 | for (id value in array) {
212 | result.push_back(block(value));
213 | }
214 | return result;
215 | }
216 |
217 | NS_ASSUME_NONNULL_END
218 |
219 | #endif
220 |
221 | #pragma mark - Logging
222 |
223 | #define CR_NOT_REACHED() CR_LOG(@"Unexpected exec @ %s:%s ", __FILE__, __LINE__)
224 |
225 | #define CR_LOG_ENABLED 1
226 |
227 | #ifdef CR_LOG_ENABLED
228 | #define CR_LOG(fmt, ...) NSLog([NSString stringWithFormat:@"(client) %@", fmt], ##__VA_ARGS__)
229 | #else
230 | #define CR_LOG(...)
231 | #endif
232 |
233 | #endif /* CRMaCRos_h */
234 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNode.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | NS_ASSUME_NONNULL_BEGIN
5 |
6 | NS_SWIFT_NAME(AnyNode)
7 | @interface CRAnyNode : NSObject
8 | @end
9 |
10 | NS_SWIFT_NAME(NodeLayoutOptions)
11 | typedef NS_OPTIONS(NSUInteger, CRNodeLayoutOptions) {
12 | CRNodeLayoutOptionsNone = 1 << 0,
13 | CRNodeLayoutOptionsSizeContainerViewToFit = 1 << 1,
14 | CRNodeLayoutOptionsUseSafeAreaInsets = 1 << 2
15 | };
16 |
17 | @class CRNode;
18 | @class CRNodeHierarchy;
19 | @class CRContext;
20 | @class CRCoordinator;
21 | @class CRCoordinatorDescriptor;
22 | @class CRNodeLayoutSpec<__covariant V : UIView *>;
23 |
24 | NS_SWIFT_NAME(NodeDelegate)
25 | @protocol CRNodeDelegate
26 | @optional
27 | /// The root node for this hierarchy is being configured and layed out.
28 | /// Additional custom manual layout can be defined here.
29 | /// @note: Use @viewWithKey or @viewsWithReuseIdentifier to query the desired views in the
30 | /// installed view hierarchy.
31 | - (void)rootNodeDidLayout:(CRNode *)node;
32 | /// The node @renderedView just got inserted in the view hierarchy.
33 | - (void)rootNodeDidMount:(CRNode *)node;
34 | @end
35 |
36 | NS_SWIFT_NAME(ConcreteNode)
37 | @interface CRNode<__covariant V : UIView *> : CRAnyNode
38 | /// The context associated with this node hierarchy.
39 | @property(nonatomic, readonly, nullable, weak) CRContext *context;
40 | /// The node hierarchy this node belongs to (if applicable).
41 | @property(nonatomic, nullable, weak) CRNodeHierarchy *nodeHierarchy;
42 | /// The reuse identifier for this node is its hierarchy.
43 | /// Identifiers help Render understand which items have changed.
44 | /// A custom *reuseIdentifier* is mandatory if the node has a custom creation closure.
45 | @property(nonatomic, readonly) NSString *reuseIdentifier;
46 | /// A unique key for the component/node (necessary if the associated coordinator is stateful).
47 | @property(nonatomic, readonly, nullable) NSString *key;
48 | /// This component is the n-th children.
49 | @property(nonatomic, readonly) NSUInteger index;
50 | /// The subnodes of this node.
51 | @property(nonatomic, readonly) NSArray *children;
52 | /// The parent node (if this is not the root node in the hierarchy).
53 | @property(nonatomic, readonly, nullable, weak) CRNode *parent;
54 | /// Returns the root node for this node hierarchy.
55 | @property(nonatomic, readonly) CRNode *root;
56 | /// The type of the associated backing view.
57 | @property(nonatomic, readonly) Class viewType;
58 | /// Backing view for this node.
59 | @property(nonatomic, readonly, nullable) V renderedView;
60 | /// The layout delegate for this node.
61 | @property(nonatomic, nullable, weak) id delegate;
62 | /// Whether this node is a @c CRNullNode or not.
63 | @property(nonatomic, readonly) BOOL isNullNode;
64 | /// Returns the associated coordinator.
65 | /// @note: @c nil if this node hierarchy is not registered to any @c CRContext, or if
66 | /// @c coordinatorType is @c nil.
67 | @property(nonatomic, nullable, readonly) __kindof CRCoordinator *coordinator;
68 | /// The type of the associated coordinator.
69 | @property(nonatomic, nullable, readonly) CRCoordinatorDescriptor *coordinatorDescriptor;
70 |
71 | #pragma mark Constructors
72 |
73 | - (instancetype)initWithType:(Class)type
74 | reuseIdentifier:(nullable NSString *)reuseIdentifier
75 | key:(nullable NSString *)key
76 | viewInit:(UIView * (^_Nullable)(void))viewInit
77 | layoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec;
78 |
79 | + (instancetype)nodeWithType:(Class)type
80 | reuseIdentifier:(nullable NSString *)reuseIdentifier
81 | key:(nullable NSString *)key
82 | viewInit:(UIView * (^_Nullable)(void))viewInit
83 | layoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec;
84 |
85 | + (instancetype)nodeWithType:(Class)type layoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec;
86 |
87 | #pragma mark Setup
88 |
89 | /// Adds the nodes as children of this node.
90 | - (instancetype)appendChildren:(NSArray *)children;
91 |
92 | /// Bind this node to the @c CRCoordinator class passed as argument.
93 | - (instancetype)bindCoordinator:(CRCoordinatorDescriptor *)descriptor;
94 |
95 | /// Register the context for the root node of this node hierarchy.
96 | - (void)registerNodeHierarchyInContext:(CRContext *)context;
97 |
98 | #pragma mark Render
99 |
100 | /// Reconcile the view hierarchy with the one in the container view passed as argument.
101 | /// @note: This method also performs layout and configuration.
102 | - (void)reconcileInView:(nullable UIView *)view
103 | constrainedToSize:(CGSize)size
104 | withOptions:(CRNodeLayoutOptions)options;
105 |
106 | /// Layout and configure the views.
107 | - (void)layoutConstrainedToSize:(CGSize)size withOptions:(CRNodeLayoutOptions)options;
108 |
109 | /// Re-configure the node's backed view.
110 | /// @note This won't invalidate the layout.
111 | - (void)setNeedsConfigure;
112 |
113 | #pragma mark Querying
114 |
115 | /// Returns the view in the subtree of this node with the given @c key.
116 | - (nullable UIView *)viewWithKey:(NSString *)key;
117 |
118 | /// Returns all the views that have been registered with the given @c reuseIdentifier.
119 | - (NSArray *)viewsWithReuseIdentifier:(NSString *)reuseIdentifier;
120 |
121 | @end
122 |
123 | NS_SWIFT_NAME(NullNode)
124 | @interface CRNullNode : CRNode
125 |
126 | /// The default nil node instance.
127 | @property(class, readonly) CRNullNode *nullNode;
128 |
129 | @end
130 |
131 | NS_ASSUME_NONNULL_END
132 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNode.mm:
--------------------------------------------------------------------------------
1 | #import "CRNode.h"
2 | #import "CRContext.h"
3 | #import "CRCoordinator+Private.h"
4 | #import "CRMacros.h"
5 | #import "CRNodeBridge.h"
6 | #import "CRNodeHierarchy.h"
7 | #import "CRNodeLayoutSpec.h"
8 | #import "UIView+CRNode.h"
9 | #import "YGLayout.h"
10 |
11 | @implementation CRAnyNode
12 | @end
13 |
14 | @interface CRNode ()
15 | @property(nonatomic, readwrite) __kindof CRCoordinator *coordinator;
16 | @property(nonatomic, readwrite) NSUInteger index;
17 | @property(nonatomic, readwrite, nullable, weak) CRNode *parent;
18 | @property(nonatomic, readwrite, nullable) __kindof UIView *renderedView;
19 | /// The view initialization block.
20 | @property(nonatomic, copy) UIView * (^viewInit)(void);
21 | /// View configuration block.
22 | @property(nonatomic, copy, nonnull) void (^layoutSpec)(CRNodeLayoutSpec *);
23 | @end
24 |
25 | void CRIllegalCoordinatorTypeException(NSString *reason) {
26 | @throw [NSException exceptionWithName:@"IllegalCoordinatorTypeException"
27 | reason:reason
28 | userInfo:nil];
29 | }
30 |
31 | @implementation CRNode {
32 | NSMutableArray *_mutableChildren;
33 | __weak CRNodeHierarchy *_nodeHierarchy;
34 | __weak CRContext *_context;
35 | CGSize _size;
36 | struct {
37 | unsigned int shouldInvokeDidMount : 1;
38 | } __attribute__((packed, aligned(1))) _flags;
39 | }
40 |
41 | #pragma mark - Initializer
42 |
43 | - (instancetype)initWithType:(Class)type
44 | reuseIdentifier:(NSString *)reuseIdentifier
45 | key:(NSString *)key
46 | viewInit:(UIView * (^_Nullable)(void))viewInit
47 | layoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec {
48 | if (self = [super init]) {
49 | _reuseIdentifier = CR_NIL_COALESCING(reuseIdentifier, NSStringFromClass(type));
50 | _key = key;
51 | _viewType = type;
52 | _mutableChildren = [[NSMutableArray alloc] init];
53 | self.viewInit = viewInit;
54 | self.layoutSpec = layoutSpec;
55 | }
56 | return self;
57 | }
58 |
59 | #pragma mark - Convenience Initializer
60 |
61 | + (instancetype)nodeWithType:(Class)type
62 | reuseIdentifier:(NSString *)reuseIdentifier
63 | key:(nullable NSString *)key
64 | viewInit:(UIView * (^_Nullable)(void))viewInit
65 | layoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec {
66 | return [[CRNode alloc] initWithType:type
67 | reuseIdentifier:reuseIdentifier
68 | key:key
69 | viewInit:viewInit
70 | layoutSpec:layoutSpec];
71 | }
72 |
73 | + (instancetype)nodeWithType:(Class)type
74 | layoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec {
75 | return [[CRNode alloc] initWithType:type
76 | reuseIdentifier:nil
77 | key:nil
78 | viewInit:nil
79 | layoutSpec:layoutSpec];
80 | }
81 |
82 | #pragma mark - Context
83 |
84 | - (void)registerNodeHierarchyInContext:(CRContext *)context {
85 | CR_ASSERT_ON_MAIN_THREAD();
86 | if (!_parent) {
87 | _context = context;
88 | [self _recursivelyConfigureCoordinatorsInNodeHierarchy];
89 | } else
90 | [_parent registerNodeHierarchyInContext:context];
91 | }
92 |
93 | - (void)_recursivelyConfigureCoordinatorsInNodeHierarchy {
94 | self.coordinator.node = self;
95 | CR_FOREACH(child, _mutableChildren) { [child _recursivelyConfigureCoordinatorsInNodeHierarchy]; }
96 | }
97 |
98 | - (CRContext *)context {
99 | if (_context) return _context;
100 | return _parent.context;
101 | }
102 |
103 | - (CRNode *)root {
104 | if (!_parent) return self;
105 | return _parent.root;
106 | }
107 |
108 | - (__kindof CRCoordinator *)coordinator {
109 | const auto context = self.context;
110 | if (!context) return nil;
111 | if (!_coordinatorDescriptor) return _parent.coordinator;
112 | return [context coordinator:_coordinatorDescriptor];
113 | }
114 |
115 | - (CRNodeHierarchy *)nodeHierarchy {
116 | if (!_parent) return _nodeHierarchy;
117 | return _parent.nodeHierarchy;
118 | }
119 |
120 | - (void)setNodeHierarchy:(CRNodeHierarchy *)nodeHierarchy {
121 | if (!_parent) {
122 | _nodeHierarchy = nodeHierarchy;
123 | return;
124 | }
125 | [_parent setNodeHierarchy:nodeHierarchy];
126 | }
127 |
128 | #pragma mark - Children
129 |
130 | - (BOOL)isNullNode {
131 | return false;
132 | }
133 |
134 | - (NSArray *)children {
135 | return _mutableChildren;
136 | }
137 |
138 | - (instancetype)appendChildren:(NSArray *)children {
139 | CR_ASSERT_ON_MAIN_THREAD();
140 | auto lastIndex = _mutableChildren.lastObject.index;
141 | CR_FOREACH(child, children) {
142 | if (child.isNullNode) continue;
143 | child.index = lastIndex++;
144 | child.parent = self;
145 | [_mutableChildren addObject:child];
146 | }
147 | return self;
148 | }
149 |
150 | - (instancetype)bindCoordinator:(CRCoordinatorDescriptor *)descriptor {
151 | CR_ASSERT_ON_MAIN_THREAD();
152 | _coordinatorDescriptor = descriptor;
153 | return self;
154 | }
155 |
156 | #pragma mark - Querying
157 |
158 | - (UIView *)viewWithKey:(NSString *)key {
159 | if ([_key isEqualToString:key]) return _renderedView;
160 | CR_FOREACH(child, _mutableChildren) {
161 | if (const auto view = [child viewWithKey:key]) return view;
162 | }
163 | return nil;
164 | }
165 |
166 | - (NSArray *)viewsWithReuseIdentifier:(NSString *)reuseIdentifier {
167 | auto result = [[NSMutableArray alloc] init];
168 | [self _viewsWithReuseIdentifier:reuseIdentifier withArray:result];
169 | return result;
170 | }
171 |
172 | - (void)_viewsWithReuseIdentifier:(NSString *)reuseIdentifier
173 | withArray:(NSMutableArray *)array {
174 | if ([_key isEqualToString:reuseIdentifier] && _renderedView) {
175 | [array addObject:_renderedView];
176 | }
177 | CR_FOREACH(child, _mutableChildren) {
178 | [child _viewsWithReuseIdentifier:reuseIdentifier withArray:array];
179 | }
180 | }
181 |
182 | #pragma mark - Layout
183 |
184 | - (void)_constructViewWithReusableView:(nullable UIView *)reusableView {
185 | CR_ASSERT_ON_MAIN_THREAD();
186 | if (_renderedView != nil) return;
187 |
188 | if ([reusableView isKindOfClass:self.viewType]) {
189 | _renderedView = reusableView;
190 | _renderedView.cr_nodeBridge.node = self;
191 | } else {
192 | if (_viewInit) {
193 | _renderedView = _viewInit();
194 | } else {
195 | _renderedView = [[self.viewType alloc] initWithFrame:CGRectZero];
196 | }
197 | _renderedView.yoga.isEnabled = true;
198 | _renderedView.tag = _reuseIdentifier.hash;
199 | _renderedView.cr_nodeBridge.node = self;
200 | _flags.shouldInvokeDidMount = true;
201 | }
202 | }
203 |
204 | - (void)_configureConstrainedToSize:(CGSize)size withOptions:(CRNodeLayoutOptions)options {
205 | [self _constructViewWithReusableView:nil];
206 | [_renderedView.cr_nodeBridge storeViewSubTreeOldGeometry];
207 | const auto spec = [[CRNodeLayoutSpec alloc] initWithNode:self constrainedToSize:size];
208 | _layoutSpec(spec);
209 |
210 | CR_FOREACH(child, _mutableChildren) {
211 | [child _configureConstrainedToSize:size withOptions:options];
212 | }
213 |
214 | if (_renderedView.yoga.isEnabled && _renderedView.yoga.isLeaf &&
215 | _renderedView.yoga.isIncludedInLayout) {
216 | _renderedView.frame.size = CGSizeZero;
217 | [_renderedView.yoga markDirty];
218 | }
219 |
220 | if (spec.onLayoutSubviews) {
221 | spec.onLayoutSubviews(self, _renderedView, size);
222 | }
223 | }
224 |
225 | - (void)_computeFlexboxLayoutConstrainedToSize:(CGSize)size {
226 | auto rect = CGRectZero;
227 | rect.size = size;
228 | _renderedView.frame = rect;
229 | [_renderedView.yoga applyLayoutPreservingOrigin:NO];
230 | rect = _renderedView.frame;
231 | rect.size = _renderedView.yoga.intrinsicSize;
232 | _renderedView.frame = rect;
233 | [_renderedView.yoga applyLayoutPreservingOrigin:NO];
234 | rect = _renderedView.frame;
235 | [_renderedView cr_normalizeFrame];
236 | }
237 |
238 | - (void)_animateLayoutChangesIfNecessary {
239 | const auto animator = self.context.layoutAnimator;
240 | const auto view = _renderedView;
241 | if (!animator) return;
242 | [view.cr_nodeBridge storeViewSubTreeNewGeometry];
243 | [view.cr_nodeBridge applyViewSubTreeOldGeometry];
244 | [animator stopAnimation:YES];
245 | [animator addAnimations:^{
246 | [view.cr_nodeBridge applyViewSubTreeNewGeometry];
247 | }];
248 | [view.cr_nodeBridge fadeInNewlyCreatedViewsInViewSubTreeWithDelay:animator.duration];
249 | [animator startAnimation];
250 | }
251 |
252 | - (void)layoutConstrainedToSize:(CGSize)size withOptions:(CRNodeLayoutOptions)options {
253 | CR_ASSERT_ON_MAIN_THREAD();
254 | if (_parent != nil) return [_parent layoutConstrainedToSize:size withOptions:options];
255 |
256 | _size = size;
257 | auto safeAreaOffset = CGPointZero;
258 | if (@available(iOS 11, *)) {
259 | if (options & CRNodeLayoutOptionsUseSafeAreaInsets) {
260 | UIEdgeInsets safeArea = _renderedView.superview.safeAreaInsets;
261 | CGFloat heightInsets = safeArea.top + safeArea.bottom;
262 | CGFloat widthInsets = safeArea.left + safeArea.right;
263 | size.height -= heightInsets;
264 | size.width -= widthInsets;
265 | safeAreaOffset.x = safeArea.left;
266 | safeAreaOffset.y = safeArea.top;
267 | }
268 | }
269 | NSUInteger numberOfLayoutPasses = 1;
270 | for (NSUInteger pass = 0; pass < numberOfLayoutPasses; pass++) {
271 | [self _configureConstrainedToSize:size withOptions:options];
272 | [self _computeFlexboxLayoutConstrainedToSize:size];
273 | }
274 | auto frame = _renderedView.frame;
275 | frame.origin.x += safeAreaOffset.x;
276 | frame.origin.y += safeAreaOffset.y;
277 | _renderedView.frame = frame;
278 |
279 | if (options & CRNodeLayoutOptionsSizeContainerViewToFit) {
280 | auto superview = _renderedView.superview;
281 | UIEdgeInsets insets;
282 | insets.left = CR_NORMALIZE(_renderedView.yoga.marginLeft);
283 | insets.right = CR_NORMALIZE(_renderedView.yoga.marginRight);
284 | insets.top = CR_NORMALIZE(_renderedView.yoga.marginTop);
285 | insets.bottom = CR_NORMALIZE(_renderedView.yoga.marginBottom);
286 | auto rect = CGRectInset(_renderedView.bounds, -(insets.left + insets.right),
287 | -(insets.top + insets.bottom));
288 | rect.origin = superview.frame.origin;
289 | superview.frame = rect;
290 | }
291 | [_renderedView cr_adjustContentSizePostLayoutRecursivelyIfNeeded];
292 |
293 | [self.coordinator onLayout];
294 | [self _animateLayoutChangesIfNecessary];
295 | }
296 |
297 | - (void)_reconcileNode:(CRNode *)node
298 | inView:(UIView *)candidateView
299 | constrainedToSize:(CGSize)size
300 | withParentView:(UIView *)parentView {
301 | // The candidate view is a good match for reuse.
302 | if ([candidateView isKindOfClass:node.viewType] && candidateView.cr_hasNode &&
303 | candidateView.tag == node.reuseIdentifier.hash) {
304 | [node _constructViewWithReusableView:candidateView];
305 | candidateView.cr_nodeBridge.isNewlyCreated = false;
306 | // The view for this node needs to be created.
307 | } else {
308 | [candidateView removeFromSuperview];
309 | [node _constructViewWithReusableView:nil];
310 | node.renderedView.cr_nodeBridge.isNewlyCreated = true;
311 | [parentView insertSubview:node.renderedView atIndex:node.index];
312 | }
313 | const auto view = node.renderedView;
314 | // Get all of the subviews.
315 | const auto subviews = [[NSMutableArray alloc] initWithCapacity:view.subviews.count];
316 | CR_FOREACH(subview, view.subviews) {
317 | if (!subview.cr_hasNode) continue;
318 | [subviews addObject:subview];
319 | }
320 | // Iterate children.
321 | CR_FOREACH(child, node.children) {
322 | UIView *candidateView = nil;
323 | auto index = 0;
324 | CR_FOREACH(subview, subviews) {
325 | if ([subview isKindOfClass:child.viewType] && subview.tag == child.reuseIdentifier.hash) {
326 | candidateView = subview;
327 | break;
328 | }
329 | index++;
330 | }
331 | // Pops the candidate view from the collection.
332 | if (candidateView != nil) [subviews removeObjectAtIndex:index];
333 | // Recursively reconcile the subnode.
334 | [node _reconcileNode:child
335 | inView:candidateView
336 | constrainedToSize:size
337 | withParentView:node.renderedView];
338 | }
339 | // Remove all of the obsolete old views that couldn't be recycled.
340 | CR_FOREACH(subview, subviews) { [subview removeFromSuperview]; }
341 | }
342 |
343 | - (void)reconcileInView:(UIView *)view
344 | constrainedToSize:(CGSize)size
345 | withOptions:(CRNodeLayoutOptions)options {
346 | CR_ASSERT_ON_MAIN_THREAD();
347 | if (_parent != nil)
348 | return [_parent reconcileInView:view constrainedToSize:size withOptions:options];
349 |
350 | _size = size;
351 | const auto containerView = CR_NIL_COALESCING(view, _renderedView.superview);
352 | const auto bounds = CGSizeEqualToSize(size, CGSizeZero) ? containerView.bounds.size : size;
353 | [self _reconcileNode:self
354 | inView:containerView.subviews.firstObject
355 | constrainedToSize:bounds
356 | withParentView:containerView];
357 |
358 | [self layoutConstrainedToSize:size withOptions:options];
359 |
360 | if (_flags.shouldInvokeDidMount &&
361 | [self.delegate respondsToSelector:@selector(rootNodeDidMount:)]) {
362 | _flags.shouldInvokeDidMount = false;
363 | [self.delegate rootNodeDidMount:self];
364 | }
365 | }
366 |
367 | - (void)setNeedsConfigure {
368 | const auto spec = [[CRNodeLayoutSpec alloc] initWithNode:self constrainedToSize:_size];
369 | _layoutSpec(spec);
370 | }
371 |
372 | @end
373 |
374 | #pragma mark - nullNode
375 |
376 | @implementation CRNullNode
377 |
378 | + (instancetype)nullNode {
379 | static CRNullNode *sharedInstance = nil;
380 | static dispatch_once_t onceToken;
381 | dispatch_once(&onceToken, ^{
382 | sharedInstance = [[self alloc] init];
383 | });
384 | return sharedInstance;
385 | }
386 |
387 | - (BOOL)isNullNode {
388 | return true;
389 | }
390 |
391 | @end
392 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeBridge.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | NS_ASSUME_NONNULL_BEGIN
5 |
6 | @class CRNode;
7 | @class CRCoordinatorDescriptor;
8 |
9 | NS_SWIFT_NAME(NodeBridge)
10 | @interface CRNodeBridge : NSObject
11 | /// Whether the view has been created at the last render pass.
12 | @property(nonatomic) BOOL isNewlyCreated;
13 | /// The node associated to this view.
14 | @property(nonatomic, nullable) CRNode *node;
15 | /// The bridged view.
16 | @property(nonatomic, nullable, weak) UIView *view;
17 | /// Layout animator for this subtree.
18 | @property(nonatomic, nullable) UIViewPropertyAnimator *layoutAnimator;
19 |
20 | - (instancetype)initWithView:(UIView *)view;
21 |
22 | /// Stores the current (now considered old in the current run-loop) geometry for the associated
23 | /// view and all of its subviews recursively.
24 | - (void)storeViewSubTreeOldGeometry;
25 |
26 | /// Applies the stored old geometry to this view subtree.
27 | - (void)applyViewSubTreeOldGeometry;
28 |
29 | /// Stores the geometry for the associated view after the node has rendered at the end of the
30 | /// current run-loop.
31 | - (void)storeViewSubTreeNewGeometry;
32 |
33 | /// Applies the stored new geometry to this view subtree.
34 | - (void)applyViewSubTreeNewGeometry;
35 |
36 | /// Transition in all of the newly created view in the view hierarchy.
37 | - (void)fadeInNewlyCreatedViewsInViewSubTreeWithDelay:(NSTimeInterval)delay;
38 |
39 | /// Set the property at the given keyPath.nil
40 | - (void)setPropertyWithKeyPath:(NSString *)keyPath
41 | value:(id)value
42 | animator:(nullable UIViewPropertyAnimator *)animator;
43 |
44 | /// Restore the view to its initial state.
45 | - (void)restore;
46 |
47 | @end
48 |
49 | NS_ASSUME_NONNULL_END
50 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeBridge.mm:
--------------------------------------------------------------------------------
1 | #import "CRNodeBridge.h"
2 | #import "CRCoordinator.h"
3 | #import "CRMacros.h"
4 | #import "CRNode.h"
5 | #import "CRNodeBridge.h"
6 | #import "UIView+CRNode.h"
7 |
8 | @implementation CRNodeBridge {
9 | /// The previous rect for the associated view.
10 | CGRect _oldGeometry;
11 | /// The new rect for the associated view.
12 | CGRect _newGeometry;
13 | /// The alpha computed after the last render pass.
14 | CGFloat _targetAlpha;
15 | /// The initial property values for the associated view.
16 | NSMutableDictionary *_initialPropertyValues;
17 | }
18 |
19 | - (instancetype)initWithView:(UIView *)view {
20 | if (self = [super init]) {
21 | _view = view;
22 | _initialPropertyValues = [[NSMutableDictionary alloc] init];
23 | }
24 | return self;
25 | }
26 |
27 | - (void)storeViewSubTreeOldGeometry {
28 | if (!_view.cr_hasNode) return;
29 | _oldGeometry = _view.frame;
30 |
31 | CR_FOREACH(subview, _view.subviews) {
32 | if (!subview.cr_hasNode) continue;
33 | [subview.cr_nodeBridge storeViewSubTreeOldGeometry];
34 | }
35 | }
36 |
37 | - (void)applyViewSubTreeOldGeometry {
38 | CR_ASSERT_ON_MAIN_THREAD();
39 | if (!_view.cr_hasNode) return;
40 | if (!(_isNewlyCreated && CGRectEqualToRect(_oldGeometry, CGRectZero))) {
41 | _view.alpha = 0;
42 | } else {
43 | _view.frame = _oldGeometry;
44 | CR_FOREACH(subview, _view.subviews) {
45 | if (!subview.cr_hasNode) continue;
46 | [subview.cr_nodeBridge applyViewSubTreeOldGeometry];
47 | }
48 | }
49 | }
50 |
51 | - (void)storeViewSubTreeNewGeometry {
52 | if (!_view.cr_hasNode) return;
53 | _newGeometry = _view.frame;
54 | _targetAlpha = _view.alpha;
55 |
56 | CR_FOREACH(subview, _view.subviews) {
57 | if (!subview.cr_hasNode) continue;
58 | [subview.cr_nodeBridge storeViewSubTreeNewGeometry];
59 | }
60 | }
61 |
62 | - (void)applyViewSubTreeNewGeometry {
63 | CR_ASSERT_ON_MAIN_THREAD();
64 | if (!_view.cr_hasNode) return;
65 | _view.frame = _newGeometry;
66 | _view.alpha = _targetAlpha;
67 | CR_FOREACH(subview, _view.subviews) {
68 | if (!subview.cr_hasNode) continue;
69 | [subview.cr_nodeBridge applyViewSubTreeNewGeometry];
70 | }
71 | }
72 |
73 | - (void)fadeInNewlyCreatedViewsInViewSubTreeWithDelay:(NSTimeInterval)delay {
74 | CR_ASSERT_ON_MAIN_THREAD();
75 | static const auto duration = 0.16;
76 | const auto options =
77 | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut;
78 |
79 | CR_WEAKIFY(self);
80 | [UIView animateWithDuration:duration
81 | delay:delay
82 | options:options
83 | animations:^{
84 | CR_STRONGIFY_AND_RETURN_IF_NIL(self);
85 | [self _restoreAlphaRecursively];
86 | }
87 | completion:nil];
88 | }
89 |
90 | - (void)_restoreAlphaRecursively {
91 | if (!_view.cr_hasNode) return;
92 | if (fabs(_view.alpha - _targetAlpha) > FLT_EPSILON) _view.alpha = _targetAlpha;
93 | CR_FOREACH(subview, _view.subviews) { [subview.cr_nodeBridge _restoreAlphaRecursively]; }
94 | }
95 |
96 | - (void)setPropertyWithKeyPath:(NSString *)keyPath
97 | value:(id)value
98 | animator:(UIViewPropertyAnimator *)animator {
99 | CR_ASSERT_ON_MAIN_THREAD();
100 | if (!_view.cr_hasNode) return;
101 |
102 | const id currentValue = [_view valueForKeyPath:keyPath];
103 | if (!_initialPropertyValues[keyPath]) {
104 | _initialPropertyValues[keyPath] = currentValue;
105 | }
106 | if (![currentValue isEqual:value]) {
107 | CR_WEAKIFY(self);
108 | if (!animator) {
109 | [self.view setValue:value forKeyPath:keyPath];
110 | } else {
111 | [animator addAnimations:^{
112 | CR_STRONGIFY_AND_RETURN_IF_NIL(self);
113 | [self.view setValue:value forKeyPath:keyPath];
114 | }];
115 | }
116 | }
117 | }
118 |
119 | - (void)restore {
120 | CR_FOREACH(keyPath, _initialPropertyValues) {
121 | const id value = _initialPropertyValues[keyPath];
122 | [self setPropertyWithKeyPath:keyPath value:value animator:nil];
123 | }
124 | }
125 |
126 | @end
127 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeBuilder+Modifiers.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRNodeBuilder.h"
5 | #import "YGLayout.h"
6 |
7 | NS_ASSUME_NONNULL_BEGIN
8 |
9 | @interface CRNodeBuilder (Modifiers)
10 |
11 | /// Padding for the view.
12 | - (instancetype)padding:(CGFloat)padding;
13 |
14 | /// Pads the view using the specified edge insets.
15 | - (instancetype)paddingInsets:(UIEdgeInsets)padding;
16 |
17 | /// Margins for the view.
18 | - (instancetype)margin:(CGFloat)padding;
19 |
20 | /// Margins for the view using the specified edge insets.
21 | - (instancetype)marginInsets:(UIEdgeInsets)padding;
22 |
23 | /// Borders for the view using the specified edge insets.
24 | - (instancetype)border:(UIEdgeInsets)border;
25 |
26 | /// The view background color.
27 | - (instancetype)background:(UIColor *)color;
28 |
29 | /// Clips the view to its bounding frame, with the specified corner radius.
30 | - (instancetype)cornerRadius:(CGFloat)value;
31 |
32 | /// Clips the view to its bounding rectangular frame.
33 | - (instancetype)clipped:(BOOL)value;
34 |
35 | /// Whether the view is hidden or not.
36 | - (instancetype)hidden:(BOOL)value;
37 |
38 | /// Sets the transparency of the view.
39 | - (instancetype)opacity:(CGFloat)value;
40 |
41 | /// Controls the direction in which the children of a node are laid out.
42 | /// This is also referred to as the main axis. The cross axis is the axis perpendicular to
43 | /// the main axis, or the axis which the wrapping lines are laid out in.
44 | - (instancetype)flexDirection:(YGFlexDirection)value;
45 |
46 | /// Defines how to distribute space between and around content
47 | /// items along the main-axis of a flex container, and the inline axis of a grid container.
48 | - (instancetype)justifyContent:(YGJustify)value;
49 |
50 | /// Controls how rows align in the cross direction, overriding the @c alignContent
51 | /// of the parent.
52 | - (instancetype)alignContent:(YGAlign)value;
53 |
54 | /// Aligns children in the cross direction. For example, if children are flowing
55 | /// vertically, @c alignItems controls how they align horizontally.
56 | - (instancetype)alignItems:(YGAlign)value;
57 |
58 | /// Controls how a child aligns in the cross direction, overriding the
59 | /// @c alignItems of the parent.
60 | - (instancetype)alignSelf:(YGAlign)value;
61 |
62 | /// Absolute positioning is always relative to the parent.
63 | /// If you want to position a child using specific numbers of logical pixels relative to its
64 | /// parent, set the child to have absolute position.
65 | /// If you want to position a child relative to something that is not its parent, don't use
66 | /// styles for that. Use the component tree.
67 | - (instancetype)position:(YGPositionType)value;
68 |
69 | /// Controls whether children can wrap around after they hit the end of a flex container.
70 | - (instancetype)flexWrap:(YGWrap)value;
71 |
72 | /// Controls how children are measured and displayed
73 | - (instancetype)overflow:(YGOverflow)value;
74 |
75 | /// Default flex appearance.
76 | - (instancetype)flex;
77 |
78 | /// Sets the flex grow factor of a flex item main size. It specifies how much of the remaining
79 | /// space in the flex container should be assigned to the item (the flex grow factor).
80 | /// The main size is either width or height of the item which is dependent on the @c
81 | /// flexDirection value.
82 | /// The remaining space is the size of the flex container minus the size of all flex items' sizes
83 | /// together. If all sibling items have the same flex grow factor, then all items will receive
84 | /// the same share of remaining space, otherwise it is distributed according to the ratio
85 | /// defined by the different flex grow factors.
86 | - (instancetype)flexGrow:(CGFloat)value;
87 |
88 | /// Sets the flex shrink factor of a flex item.
89 | - (instancetype)flexShrink:(CGFloat)value;
90 |
91 | /// Sets the initial main size of a flex item.
92 | - (instancetype)flexBasis:(CGFloat)value;
93 |
94 | /// The view fixed width.
95 | - (instancetype)width:(CGFloat)value;
96 |
97 | /// The view fixed height.
98 | - (instancetype)height:(CGFloat)value;
99 |
100 | /// The view minimum width.
101 | - (instancetype)minWidth:(CGFloat)value;
102 |
103 | /// The view minimum height.
104 | - (instancetype)minHeight:(CGFloat)value;
105 |
106 | /// The view maximum width.
107 | - (instancetype)maxWidth:(CGFloat)value;
108 |
109 | /// The view maximum height.
110 | - (instancetype)maxHeight:(CGFloat)value;
111 |
112 | /// Matches the parent width.
113 | - (instancetype)matchHostingViewWidthWithMargin:(CGFloat)margin;
114 |
115 | /// Matches the parent height.
116 | - (instancetype)matchHostingViewHeightWithMargin:(CGFloat)margin;
117 |
118 | /// Determines whether user events are ignored and removed from the event queue.
119 | - (instancetype)userInteractionEnabled:(BOOL)userInteractionEnabled;
120 |
121 | /// Applies a transformation.
122 | - (instancetype)transform:(CGAffineTransform)transform animator:(UIViewPropertyAnimator *)animator;
123 |
124 | /// Adds an animator for the whole view layout.
125 | - (instancetype)layoutAnimator:(UIViewPropertyAnimator *)animator;
126 |
127 | @end
128 |
129 | @interface CRNodeBuilder (UIControl)
130 |
131 | /// A Boolean value indicating whether the control is enabled.
132 | - (instancetype)enabled:(BOOL)enabled;
133 |
134 | /// A Boolean value indicating whether the control is in the selected state.
135 | - (instancetype)selected:(BOOL)selected;
136 |
137 | /// A Boolean value indicating whether the control draws a highlight.
138 | - (instancetype)highlighted:(BOOL)selected;
139 |
140 | /// Associates a target object and action method with the control.
141 | - (instancetype)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)events;
142 |
143 | @end
144 |
145 | @interface CRNodeBuilder (UILabel)
146 |
147 | /// The current text that is displayed by the label.
148 | - (instancetype)text:(nullable NSString *)text;
149 |
150 | /// The current styled text that is displayed by the label.
151 | - (instancetype)attributedText:(nullable NSAttributedString *)attributedText;
152 |
153 | /// The current styled text that is displayed by the label.
154 | - (instancetype)font:(UIFont *)font;
155 |
156 | /// The color of the text.
157 | - (instancetype)textColor:(UIColor *)textColor;
158 |
159 | /// The technique to use for aligning the text.
160 | - (instancetype)textAlignment:(NSTextAlignment)textAlignment;
161 |
162 | /// The technique to use for wrapping and truncating the label’s text.
163 | - (instancetype)lineBreakMode:(NSLineBreakMode)lineBreakMode;
164 |
165 | /// The maximum number of lines to use for rendering text.
166 | - (instancetype)numberOfLines:(NSUInteger)numberOfLines;
167 |
168 | @end
169 |
170 | NS_ASSUME_NONNULL_END
171 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeBuilder+Modifiers.mm:
--------------------------------------------------------------------------------
1 | #import "CRContext.h"
2 | #import "CRCoordinator.h"
3 | #import "CRMacros.h"
4 | #import "CRNodeBuilder+Modifiers.h"
5 | #import "CRNodeLayoutSpec.h"
6 | #import "YGLayout.h"
7 |
8 | void _CRUnsafeSet(CRNodeLayoutSpec *spec, NSString *keyPath, id value) {
9 | const auto selector = NSSelectorFromString(keyPath);
10 | if (![spec.view respondsToSelector:selector]) {
11 | NSLog(@"warning: cannot find keyPath %@ in class %@", keyPath, spec.view.class);
12 | } else {
13 | [spec set:keyPath value:value];
14 | }
15 | }
16 |
17 | @implementation CRNodeBuilder (Modifiers)
18 |
19 | - (instancetype)padding:(CGFloat)padding {
20 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
21 | [spec set:CR_KEYPATH(spec.view, yoga.padding) value:@(padding)];
22 | }];
23 | }
24 |
25 | - (instancetype)paddingInsets:(UIEdgeInsets)padding {
26 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
27 | [spec set:CR_KEYPATH(spec.view, yoga.paddingTop) value:@(padding.top)];
28 | [spec set:CR_KEYPATH(spec.view, yoga.paddingBottom) value:@(padding.bottom)];
29 | [spec set:CR_KEYPATH(spec.view, yoga.paddingLeft) value:@(padding.left)];
30 | [spec set:CR_KEYPATH(spec.view, yoga.paddingRight) value:@(padding.right)];
31 | }];
32 | }
33 |
34 | - (instancetype)margin:(CGFloat)margin {
35 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
36 | [spec set:CR_KEYPATH(spec.view, yoga.margin) value:@(margin)];
37 | }];
38 | }
39 |
40 | - (instancetype)marginInsets:(UIEdgeInsets)margin {
41 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
42 | [spec set:CR_KEYPATH(spec.view, yoga.marginTop) value:@(margin.top)];
43 | [spec set:CR_KEYPATH(spec.view, yoga.marginBottom) value:@(margin.bottom)];
44 | [spec set:CR_KEYPATH(spec.view, yoga.marginLeft) value:@(margin.left)];
45 | [spec set:CR_KEYPATH(spec.view, yoga.marginRight) value:@(margin.right)];
46 | }];
47 | }
48 |
49 | - (instancetype)border:(UIEdgeInsets)border {
50 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
51 | [spec set:CR_KEYPATH(spec.view, yoga.borderTopWidth) value:@(border.top)];
52 | [spec set:CR_KEYPATH(spec.view, yoga.borderBottomWidth) value:@(border.bottom)];
53 | [spec set:CR_KEYPATH(spec.view, yoga.borderLeftWidth) value:@(border.left)];
54 | [spec set:CR_KEYPATH(spec.view, yoga.borderRightWidth) value:@(border.right)];
55 | }];
56 | }
57 |
58 | - (instancetype)background:(UIColor *)color {
59 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
60 | [spec set:CR_KEYPATH(spec.view, backgroundColor) value:color];
61 | }];
62 | }
63 |
64 | - (instancetype)cornerRadius:(CGFloat)value {
65 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
66 | [spec set:CR_KEYPATH(spec.view, clipsToBounds) value:@(YES)];
67 | [spec set:CR_KEYPATH(spec.view, layer.cornerRadius) value:@(value)];
68 | }];
69 | }
70 |
71 | - (instancetype)clipped:(BOOL)value {
72 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
73 | [spec set:CR_KEYPATH(spec.view, clipsToBounds) value:@(value)];
74 | }];
75 | }
76 |
77 | - (instancetype)hidden:(BOOL)value {
78 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
79 | [spec set:CR_KEYPATH(spec.view, hidden) value:@(value)];
80 | }];
81 | }
82 |
83 | - (instancetype)opacity:(CGFloat)value {
84 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
85 | [spec set:CR_KEYPATH(spec.view, alpha) value:@(YES)];
86 | }];
87 | }
88 |
89 | - (instancetype)flexDirection:(YGFlexDirection)value {
90 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
91 | [spec set:CR_KEYPATH(spec.view, yoga.flexDirection) value:@(value)];
92 | }];
93 | }
94 |
95 | - (instancetype)justifyContent:(YGJustify)value {
96 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
97 | [spec set:CR_KEYPATH(spec.view, yoga.justifyContent) value:@(value)];
98 | }];
99 | }
100 |
101 | - (instancetype)alignContent:(YGAlign)value {
102 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
103 | [spec set:CR_KEYPATH(spec.view, yoga.alignContent) value:@(value)];
104 | }];
105 | }
106 |
107 | - (instancetype)alignItems:(YGAlign)value {
108 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
109 | [spec set:CR_KEYPATH(spec.view, yoga.alignItems) value:@(value)];
110 | }];
111 | }
112 |
113 | - (instancetype)alignSelf:(YGAlign)value {
114 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
115 | [spec set:CR_KEYPATH(spec.view, yoga.alignSelf) value:@(value)];
116 | }];
117 | }
118 |
119 | - (instancetype)position:(YGPositionType)value {
120 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
121 | [spec set:CR_KEYPATH(spec.view, yoga.position) value:@(value)];
122 | }];
123 | }
124 |
125 | - (instancetype)flexWrap:(YGWrap)value {
126 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
127 | [spec set:CR_KEYPATH(spec.view, yoga.flexWrap) value:@(value)];
128 | }];
129 | }
130 |
131 | - (instancetype)overflow:(YGOverflow)value {
132 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
133 | [spec set:CR_KEYPATH(spec.view, yoga.overflow) value:@(value)];
134 | }];
135 | }
136 |
137 | - (instancetype)flex {
138 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
139 | [spec.view.yoga flex];
140 | }];
141 | }
142 |
143 | - (instancetype)flexGrow:(CGFloat)value {
144 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
145 | [spec set:CR_KEYPATH(spec.view, yoga.flexGrow) value:@(value)];
146 | }];
147 | }
148 |
149 | - (instancetype)flexShrink:(CGFloat)value {
150 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
151 | [spec set:CR_KEYPATH(spec.view, yoga.flexShrink) value:@(value)];
152 | }];
153 | }
154 |
155 | - (instancetype)flexBasis:(CGFloat)value {
156 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
157 | [spec set:CR_KEYPATH(spec.view, yoga.flexBasis) value:@(value)];
158 | }];
159 | }
160 |
161 | - (instancetype)width:(CGFloat)value {
162 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
163 | [spec set:CR_KEYPATH(spec.view, yoga.width) value:@(value)];
164 | }];
165 | }
166 |
167 | - (instancetype)height:(CGFloat)value {
168 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
169 | [spec set:CR_KEYPATH(spec.view, yoga.height) value:@(value)];
170 | }];
171 | }
172 |
173 | - (instancetype)minWidth:(CGFloat)value {
174 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
175 | [spec set:CR_KEYPATH(spec.view, yoga.minWidth) value:@(value)];
176 | }];
177 | }
178 |
179 | - (instancetype)minHeight:(CGFloat)value {
180 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
181 | [spec set:CR_KEYPATH(spec.view, yoga.minHeight) value:@(value)];
182 | }];
183 | }
184 |
185 | - (instancetype)maxWidth:(CGFloat)value {
186 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
187 | [spec set:CR_KEYPATH(spec.view, yoga.maxWidth) value:@(value)];
188 | }];
189 | }
190 |
191 | - (instancetype)maxHeight:(CGFloat)value {
192 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
193 | [spec set:CR_KEYPATH(spec.view, yoga.maxHeight) value:@(value)];
194 | }];
195 | }
196 |
197 | - (instancetype)matchHostingViewWidthWithMargin:(CGFloat)margin {
198 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
199 | [spec set:CR_KEYPATH(spec.view, yoga.width) value:@(spec.size.width - 2 * margin)];
200 | }];
201 | }
202 |
203 | - (instancetype)matchHostingViewHeightWithMargin:(CGFloat)margin {
204 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
205 | [spec set:CR_KEYPATH(spec.view, yoga.height) value:@(spec.size.height - 2 * margin)];
206 | }];
207 | }
208 |
209 | - (instancetype)userInteractionEnabled:(BOOL)userInteractionEnabled {
210 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
211 | [spec set:CR_KEYPATH(spec.view, userInteractionEnabled) value:@(userInteractionEnabled)];
212 | }];
213 | }
214 |
215 | - (instancetype)transform:(CGAffineTransform)transform animator:(UIViewPropertyAnimator *)animator {
216 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
217 | [spec set:CR_KEYPATH(spec.view, transform) value:@(transform) animator:animator];
218 | }];
219 | }
220 |
221 | /// Adds an animator for the whole view layout.
222 | - (instancetype)layoutAnimator:(UIViewPropertyAnimator *)animator {
223 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
224 | spec.context.layoutAnimator = animator;
225 | }];
226 | }
227 |
228 | @end
229 |
230 | @implementation CRNodeBuilder (UIControl)
231 |
232 | - (instancetype)enabled:(BOOL)enabled {
233 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
234 | const auto getter = NSSelectorFromString(CR_UNSAFE_KEYPATH(isEnabled));
235 | if (![spec.view respondsToSelector:getter]) return;
236 | [spec set:CR_UNSAFE_KEYPATH(enabled) value:@(enabled)];
237 | }];
238 | }
239 |
240 | - (instancetype)selected:(BOOL)selected {
241 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
242 | const auto getter = NSSelectorFromString(CR_UNSAFE_KEYPATH(isSelected));
243 | if (![spec.view respondsToSelector:getter]) return;
244 | [spec set:CR_UNSAFE_KEYPATH(selected) value:@(selected)];
245 | }];
246 | }
247 |
248 | - (instancetype)highlighted:(BOOL)highlighted {
249 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
250 | const auto getter = NSSelectorFromString(CR_UNSAFE_KEYPATH(isHighlighted));
251 | if (![spec.view respondsToSelector:getter]) return;
252 | [spec set:CR_UNSAFE_KEYPATH(highlighted) value:@(highlighted)];
253 | }];
254 | }
255 | - (instancetype)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)events {
256 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
257 | const auto control = CR_DYNAMIC_CAST(UIControl, spec.view);
258 | if (!control) return;
259 | [control removeTarget:nil action:nil forControlEvents:events];
260 | [control addTarget:target action:action forControlEvents:events];
261 | }];
262 | }
263 |
264 | @end
265 |
266 | @implementation CRNodeBuilder (UILabel)
267 |
268 | - (instancetype)text:(nullable NSString *)text {
269 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
270 | const auto button = CR_DYNAMIC_CAST(UIButton, spec.view);
271 | if (button) {
272 | [button setTitle:text forState:UIControlStateNormal];
273 | } else {
274 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(text), text);
275 | }
276 | }];
277 | }
278 |
279 | - (instancetype)attributedText:(nullable NSAttributedString *)attributedText {
280 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
281 | const auto button = CR_DYNAMIC_CAST(UIButton, spec.view);
282 | if (button) {
283 | [button setAttributedTitle:attributedText forState:UIControlStateNormal];
284 | } else {
285 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(attributedText), attributedText);
286 | }
287 | }];
288 | }
289 |
290 | - (instancetype)font:(UIFont *)font {
291 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
292 | const auto button = CR_DYNAMIC_CAST(UIButton, spec.view);
293 | if (button) {
294 | [spec set:CR_KEYPATH(button, titleLabel.font) value:font];
295 | } else {
296 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(font), font);
297 | }
298 | }];
299 | }
300 |
301 | - (instancetype)textColor:(UIColor *)textColor {
302 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
303 | const auto button = CR_DYNAMIC_CAST(UIButton, spec.view);
304 | if (button) {
305 | [button setTitleColor:textColor forState:UIControlStateNormal];
306 | } else {
307 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(textColor), textColor);
308 | }
309 | }];
310 | }
311 |
312 | - (instancetype)textAlignment:(NSTextAlignment)textAlignment {
313 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
314 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(textAlignment), @(textAlignment));
315 | }];
316 | }
317 |
318 | - (instancetype)lineBreakMode:(NSLineBreakMode)lineBreakMode {
319 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
320 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(lineBreakMode), @(lineBreakMode));
321 | }];
322 | }
323 |
324 | - (instancetype)numberOfLines:(NSUInteger)numberOfLines {
325 | return [self withLayoutSpec:^(CRNodeLayoutSpec *spec) {
326 | _CRUnsafeSet(spec, CR_UNSAFE_KEYPATH(numberOfLines), @(numberOfLines));
327 | }];
328 | }
329 |
330 | @end
331 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeBuilder.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRNode.h"
5 |
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | NS_SWIFT_NAME(OpaqueNodeBuilder)
9 | @interface CROpaqueNodeBuilder : NSObject
10 | /// Optional reuse identifier.
11 | /// @note: This is required if the node has a custom @c viewInit.
12 | - (instancetype)withReuseIdentifier:(NSString *)reuseIdentifier;
13 | /// Unique node key (required for stateful components).
14 | /// @note: This is required if @c coordinatorType or @c state is set.
15 | - (instancetype)withKey:(NSString *)key;
16 | /// The coordinator assigned to this node.
17 | - (instancetype)withCoordinator:(CRCoordinator *)coordinator;
18 | /// The coordinator type assigned to this node.
19 | - (instancetype)withCoordinatorDescriptor:(CRCoordinatorDescriptor *)descriptor;
20 | /// Defines the node configuration and layout.
21 | - (instancetype)withLayoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec;
22 | /// Build the concrete node.
23 | - (CRNode *)build;
24 | @end
25 |
26 | NS_SWIFT_NAME(NullNodeBuilder)
27 | @interface CRNullNodeBuilder : CROpaqueNodeBuilder
28 | /// Build the concrete node.
29 | - (CRNullNode *)build;
30 | @end
31 |
32 | NS_SWIFT_NAME(NodeBuilder)
33 | @interface CRNodeBuilder<__covariant V : UIView *> : CROpaqueNodeBuilder
34 | - (instancetype)init NS_UNAVAILABLE;
35 | /// The view type of the desired @c CRNode.
36 | - (instancetype)initWithType:(Class)type;
37 | /// Custom view initialization code.
38 | - (instancetype)withViewInit:(UIView * (^)(NSString *))viewInit;
39 | /// Defines the node configuration and layout.
40 | - (instancetype)withLayoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec;
41 | /// Assign the node children.
42 | - (instancetype)withChildren:(NSArray *)children;
43 | /// Add a child to the node children list.
44 | - (instancetype)addChild:(CRNode *)node;
45 | /// Build the concrete node.
46 | - (CRNode *)build;
47 | @end
48 |
49 | static CRNodeBuilder *CRBuildLeaf(Class type,
50 | void(NS_NOESCAPE ^ configure)(CRNodeBuilder *builder));
51 |
52 | static CRNodeBuilder *CRBuild(Class type, void(NS_NOESCAPE ^ configure)(CRNodeBuilder *builder),
53 | NSArray *children);
54 |
55 | NS_ASSUME_NONNULL_END
56 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeBuilder.mm:
--------------------------------------------------------------------------------
1 | #import "CRNodeBuilder.h"
2 | #import "CRContext.h"
3 | #import "CRCoordinator.h"
4 | #import "CRMacros.h"
5 |
6 | void CRNodeBuilderException(NSString *reason) {
7 | @throw [NSException exceptionWithName:@"NodeBuilderException" reason:reason userInfo:nil];
8 | }
9 |
10 | static CRNodeBuilder *CRBuildLeaf(Class type,
11 | void(NS_NOESCAPE ^ configure)(CRNodeBuilder *builder)) {
12 | return CRBuild(type, configure, @[]);
13 | }
14 |
15 | static CRNodeBuilder *CRBuild(Class type, void(NS_NOESCAPE ^ configure)(CRNodeBuilder *builder),
16 | NSArray *children) {
17 | const auto builder = [[CRNodeBuilder alloc] initWithType:type];
18 | CR_FOREACH(node, children) { [builder addChild:[node build]]; }
19 | configure(builder);
20 | return builder;
21 | }
22 |
23 | @implementation CRNullNodeBuilder
24 |
25 | - (CRNullNode *)build {
26 | return CRNullNode.nullNode;
27 | }
28 |
29 | @end
30 |
31 | @implementation CRNodeBuilder {
32 | Class _type;
33 | NSString *_reuseIdentifier;
34 | NSString *_key;
35 | UIView * (^_viewInit)(void);
36 | void (^_layoutSpec)(CRNodeLayoutSpec *);
37 | NSMutableArray *_mutableChildren;
38 | CRCoordinatorDescriptor *_coordinatorDescriptor;
39 | }
40 |
41 | - (instancetype)initWithType:(Class)type {
42 | CR_ASSERT_ON_MAIN_THREAD();
43 | if (self = [super init]) {
44 | _type = type;
45 | _mutableChildren = @[].mutableCopy;
46 | }
47 | return self;
48 | }
49 |
50 | - (instancetype)withReuseIdentifier:(NSString *)reuseIdentifier {
51 | CR_ASSERT_ON_MAIN_THREAD();
52 | _reuseIdentifier = reuseIdentifier;
53 | return self;
54 | }
55 |
56 | - (instancetype)withKey:(NSString *)key {
57 | CR_ASSERT_ON_MAIN_THREAD();
58 | _key = key;
59 | return self;
60 | }
61 |
62 | - (instancetype)withCoordinatorDescriptor:(CRCoordinatorDescriptor *)descriptor {
63 | CR_ASSERT_ON_MAIN_THREAD();
64 | _coordinatorDescriptor = descriptor;
65 | return self;
66 | }
67 |
68 | - (instancetype)withCoordinator:(CRCoordinator *)coordinator {
69 | CR_ASSERT_ON_MAIN_THREAD();
70 | const auto descriptor =
71 | [[CRCoordinatorDescriptor alloc] initWithType:coordinator.class key:coordinator.key];
72 | return [self withCoordinatorDescriptor:descriptor];
73 | }
74 |
75 | - (instancetype)withViewInit:(UIView * (^)(NSString *))viewInit {
76 | CR_ASSERT_ON_MAIN_THREAD();
77 | NSString *key = _key;
78 | _viewInit = ^UIView *(void) { return viewInit(key); };
79 | return self;
80 | }
81 |
82 | - (instancetype)withLayoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec {
83 | CR_ASSERT_ON_MAIN_THREAD();
84 | void (^oldBlock)(CRNodeLayoutSpec *) = [_layoutSpec copy];
85 | void (^newBlock)(CRNodeLayoutSpec *) = [layoutSpec copy];
86 | _layoutSpec = [^(CRNodeLayoutSpec *spec) {
87 | if (oldBlock != nil) oldBlock(spec);
88 | if (newBlock != nil) newBlock(spec);
89 | } copy];
90 | return self;
91 | }
92 |
93 | - (instancetype)withChildren:(NSArray *)children {
94 | CR_ASSERT_ON_MAIN_THREAD();
95 | _mutableChildren = children.mutableCopy;
96 | CR_FOREACH(child, _mutableChildren) { NSAssert([child isKindOfClass:CRNode.class], @""); }
97 | return self;
98 | }
99 |
100 | - (instancetype)addChild:(CRNode *)node {
101 | CR_ASSERT_ON_MAIN_THREAD();
102 | [_mutableChildren addObject:node];
103 | return self;
104 | }
105 |
106 | - (CRNode *)build {
107 | CR_ASSERT_ON_MAIN_THREAD();
108 | if (_viewInit && !_reuseIdentifier) {
109 | CRNodeBuilderException(@"The node has a custom view initializer but no reuse identifier.");
110 | return CRNullNode.nullNode;
111 | }
112 | const auto node = [[CRNode alloc] initWithType:_type
113 | reuseIdentifier:_reuseIdentifier
114 | key:_key
115 | viewInit:_viewInit
116 | layoutSpec:_layoutSpec];
117 | if (_coordinatorDescriptor) {
118 | [node bindCoordinator:_coordinatorDescriptor];
119 | }
120 | [node appendChildren:_mutableChildren];
121 | return node;
122 | }
123 |
124 | @end
125 |
126 | @implementation CROpaqueNodeBuilder
127 |
128 | - (instancetype)withReuseIdentifier:(NSString *)reuseIdentifier {
129 | NSAssert(NO, @"Called on abstract super class.");
130 | }
131 |
132 | - (instancetype)withKey:(NSString *)key {
133 | NSAssert(NO, @"Called on abstract super class.");
134 | }
135 |
136 | - (instancetype)withLayoutSpec:(void (^)(CRNodeLayoutSpec *))layoutSpec {
137 | NSAssert(NO, @"Called on abstract super class.");
138 | }
139 |
140 | - (instancetype)withCoordinator:(CRCoordinator *)coordinator {
141 | NSAssert(NO, @"Called on abstract super class.");
142 | }
143 |
144 | - (instancetype)withCoordinatorDescriptor:(CRCoordinatorDescriptor *)descriptor {
145 | NSAssert(NO, @"Called on abstract super class.");
146 | }
147 |
148 | - (CRNode *)build {
149 | NSAssert(NO, @"Called on abstract super class.");
150 | }
151 |
152 | @end
153 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeHierarchy.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRNode.h"
5 | @class CROpaqueNodeBuilder;
6 |
7 | NS_ASSUME_NONNULL_BEGIN
8 |
9 | @class CRContext;
10 |
11 | NS_SWIFT_NAME(NodeHierarchy)
12 | @interface CRNodeHierarchy : NSObject
13 | /// The current root node.
14 | @property(nonatomic, readonly) CRNode *root;
15 |
16 | - (instancetype)init NS_UNAVAILABLE;
17 |
18 | /// Instantiate a new node hierarchy.
19 | - (instancetype)initWithContext:(CRContext *)context
20 | nodeHierarchyBuilder:(CROpaqueNodeBuilder * (^)(CRContext *))buildNodeHierarchy;
21 |
22 | #pragma mark Render
23 |
24 | /// Constructs a new node hierarchy by invoking the @c buildNodeHierarchy block and reconciles it
25 | /// against the view passed as argument.
26 | - (void)buildHierarchyInView:(UIView *)view
27 | constrainedToSize:(CGSize)size
28 | withOptions:(CRNodeLayoutOptions)options;
29 |
30 | /// See @c CRNode.reconcileInView:constrainedToSize:withOptions:.
31 | - (void)reconcileInView:(nullable UIView *)view
32 | constrainedToSize:(CGSize)size
33 | withOptions:(CRNodeLayoutOptions)options;
34 |
35 | /// See @c CRNode.slayoutConstrainedToSize:withOptions:.
36 | - (void)layoutConstrainedToSize:(CGSize)size withOptions:(CRNodeLayoutOptions)options;
37 |
38 | /// Constructs a new node hierarchy by invoking the @c buildNodeHierarchy block and reconciles it
39 | /// against the currently mounted view hierarchy.
40 | - (void)setNeedsReconcile;
41 |
42 | /// Tells the node that the node/view hierarchy must be re-layout.
43 | /// @note This is preferable to @c setNeedsReconcile whenever there's going to be no changes in
44 | /// the view hierarchy,
45 | - (void)setNeedsLayout;
46 |
47 | @end
48 |
49 | NS_ASSUME_NONNULL_END
50 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeHierarchy.mm:
--------------------------------------------------------------------------------
1 | #import "CRNodeHierarchy.h"
2 |
3 | #import "CRContext.h"
4 | #import "CRMacros.h"
5 | #include "CRNodeBuilder.h"
6 |
7 | @implementation CRNodeHierarchy {
8 | __weak CRContext *_context;
9 | __weak UIView *_containerView;
10 | CGSize _size;
11 | CRNodeLayoutOptions _options;
12 | CROpaqueNodeBuilder * (^_buildNodeHierarchy)(CRContext *);
13 | }
14 |
15 | - (instancetype)initWithContext:(CRContext *)context
16 | nodeHierarchyBuilder:(CROpaqueNodeBuilder * (^)(CRContext *))buildNodeHierarchy {
17 | if (self = [super init]) {
18 | _context = context;
19 | _buildNodeHierarchy = buildNodeHierarchy;
20 | }
21 | return self;
22 | }
23 |
24 | #pragma mark Render
25 |
26 | - (void)buildHierarchyInView:(UIView *)view
27 | constrainedToSize:(CGSize)size
28 | withOptions:(CRNodeLayoutOptions)options {
29 | CR_ASSERT_ON_MAIN_THREAD();
30 | _containerView = view;
31 | _size = size;
32 | _options = options;
33 | _root = [_buildNodeHierarchy(_context) build];
34 | [_root registerNodeHierarchyInContext:_context];
35 | [_root setNodeHierarchy:self];
36 | [_root reconcileInView:view constrainedToSize:size withOptions:options];
37 | }
38 |
39 | - (void)reconcileInView:(nullable UIView *)view
40 | constrainedToSize:(CGSize)size
41 | withOptions:(CRNodeLayoutOptions)options {
42 | CR_ASSERT_ON_MAIN_THREAD();
43 | _containerView = view;
44 | _size = size;
45 | _options = options;
46 | [_root reconcileInView:view constrainedToSize:size withOptions:options];
47 | }
48 |
49 | - (void)layoutConstrainedToSize:(CGSize)size withOptions:(CRNodeLayoutOptions)options {
50 | _size = size;
51 | _options = options;
52 | CR_ASSERT_ON_MAIN_THREAD();
53 | [_root layoutConstrainedToSize:size withOptions:options];
54 | }
55 |
56 | - (void)setNeedsReconcile {
57 | CR_ASSERT_ON_MAIN_THREAD();
58 | [self buildHierarchyInView:_containerView constrainedToSize:_size withOptions:_options];
59 | }
60 |
61 | - (void)setNeedsLayout {
62 | CR_ASSERT_ON_MAIN_THREAD();
63 | [self layoutConstrainedToSize:_size withOptions:_options];
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeLayoutSpec.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | NS_ASSUME_NONNULL_BEGIN
5 |
6 | @class CRNode;
7 | @class CRContext;
8 | @class CRCoordinator;
9 |
10 | NS_SWIFT_NAME(LayoutSpec)
11 | @interface CRNodeLayoutSpec<__covariant V : UIView *> : NSObject
12 | /// Backing view for this node.
13 | @property(nonatomic, readonly, nullable, weak) V view;
14 | /// The associated node.
15 | @property(nonatomic, readonly, nullable, weak) CRNode *node;
16 | /// The context for this node hierarchy.
17 | @property(nonatomic, readonly, nullable, weak) CRContext *context;
18 |
19 | /// Lays out the view subtree.
20 | /// @note: The layout directives are executed top down and *after* the Yoga layout has been
21 | /// computed (if applicable).
22 | @property(nonatomic, copy, nullable) void (^onLayoutSubviews)(CRNode *, UIView *, CGSize);
23 | /// The boundaries of this node.
24 | @property(nonatomic, readonly) CGSize size;
25 |
26 | - (instancetype)initWithNode:(CRNode *)node constrainedToSize:(CGSize)size;
27 |
28 | - (void)set:(NSString *)keyPath value:(id)value;
29 | - (void)set:(NSString *)keyPath
30 | value:(id)value
31 | animator:(nullable UIViewPropertyAnimator *)animator;
32 |
33 | /// Restore the view to its initial state.
34 | - (void)restore;
35 |
36 | /// Reset all of the view action targets.
37 | /// @note: Applicate to @c UIControl views only.
38 | - (void)resetAllTargets;
39 |
40 | /// Returns the the first coordinator of type @c coordinatorType in the current subtree.
41 | - (nullable __kindof CRCoordinator *)coordinatorOfType:(Class)coordinatorType;
42 |
43 | @end
44 |
45 | NS_SWIFT_NAME(LayoutSpecProperty)
46 | @interface CRNodeLayoutSpecProperty : NSObject
47 | /// The target keyPath in the node view.
48 | @property(nonatomic, readonly) NSString *keyPath;
49 | /// The new value for this property.
50 | @property(nonatomic, readonly) id value;
51 | /// Optional property animator.
52 | @property(nonatomic, readonly, nullable) UIViewPropertyAnimator *animator;
53 |
54 | - (instancetype)initWithKeyPath:(NSString *)keyPath
55 | value:(id)value
56 | animator:(UIViewPropertyAnimator *)animator;
57 |
58 | @end
59 |
60 | NS_ASSUME_NONNULL_END
61 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRNodeLayoutSpec.mm:
--------------------------------------------------------------------------------
1 | #import "CRNodeLayoutSpec.h"
2 | #import "CRContext.h"
3 | #import "CRCoordinator.h"
4 | #import "CRMacros.h"
5 | #import "CRNode.h"
6 | #import "CRNodeBridge.h"
7 | #import "UIView+CRNode.h"
8 |
9 | @implementation CRNodeLayoutSpec {
10 | NSMutableDictionary *_properties;
11 | }
12 |
13 | - (void)set:(NSString *)keyPath value:(id)value {
14 | [self set:keyPath value:value animator:nil];
15 | }
16 |
17 | - (void)set:(NSString *)keyPath value:(id)value animator:(UIViewPropertyAnimator *)animator {
18 | CR_ASSERT_ON_MAIN_THREAD();
19 | static Class swiftValueClass;
20 | static dispatch_once_t onceToken;
21 | dispatch_once(&onceToken, ^() {
22 | swiftValueClass = NSClassFromString(@"__SwiftValue");
23 | });
24 | if ([value isKindOfClass:swiftValueClass]) {
25 | CR_LOG(@"__SwiftValue passed for key %@. Make sure your enum conforms to "
26 | @"WritableKeyPathBoxableEnum. ",
27 | keyPath);
28 | return;
29 | }
30 | const auto property = [[CRNodeLayoutSpecProperty alloc] initWithKeyPath:keyPath
31 | value:value
32 | animator:animator];
33 | _properties[keyPath] = property;
34 | [_view.cr_nodeBridge setPropertyWithKeyPath:keyPath value:value animator:animator];
35 | }
36 |
37 | - (instancetype)initWithNode:(CRNode *)node constrainedToSize:(CGSize)size {
38 | if (self = [super init]) {
39 | _node = node;
40 | _view = node.renderedView;
41 | _context = node.context;
42 | _size = size;
43 | }
44 | return self;
45 | }
46 |
47 | - (__kindof CRCoordinator *)coordinatorOfType:(Class)coordinatorType {
48 | if (![coordinatorType isSubclassOfClass:CRCoordinator.class]) return nil;
49 |
50 | auto coordinator = (CRCoordinator *)nil;
51 | auto node = self.node;
52 | auto context = self.context;
53 | NSAssert(node, @"Called when *node* is nil.");
54 | NSAssert(context, @"Called when *context* is nil.");
55 | while (node) {
56 | if (node.coordinatorDescriptor.type == coordinatorType) {
57 | coordinator = node.coordinator;
58 | break;
59 | }
60 | node = node.parent;
61 | }
62 | return coordinator;
63 | }
64 |
65 | - (void)restore {
66 | [_view.cr_nodeBridge restore];
67 | }
68 |
69 | - (void)resetAllTargets {
70 | [_view cr_resetAllTargets];
71 | }
72 |
73 | @end
74 |
75 | @implementation CRNodeLayoutSpecProperty
76 |
77 | - (instancetype)initWithKeyPath:(NSString *)keyPath
78 | value:(id)value
79 | animator:(UIViewPropertyAnimator *)animator {
80 | if (self = [super init]) {
81 | _keyPath = keyPath;
82 | _value = value;
83 | _animator = animator;
84 | }
85 | return self;
86 | }
87 |
88 | @end
89 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/CRUmbrellaHeader.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRContext.h"
5 | #import "CRCoordinator.h"
6 | #import "CRMacros.h"
7 | #import "CRNode.h"
8 | #import "CRNodeBridge.h"
9 | #import "CRNodeBuilder.h"
10 | #import "CRNodeHierarchy.h"
11 | #import "CRNodeLayoutSpec.h"
12 | #import "UIView+CRNode.h"
13 | #import "YGLayout.h"
14 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/UIView+CRNode.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "CRNode.h"
5 |
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | @class CRNode;
9 | @class CRNodeBridge;
10 |
11 | @interface UIView (CRNode)
12 | /// Whether this view has a node currently associated to it or not.
13 | @property(nonatomic, readonly) BOOL cr_hasNode;
14 | /// Transient node configuration for this view.
15 | @property(nonatomic) CRNodeBridge *cr_nodeBridge;
16 | /// Remove all of the registered targets if this view is a subclass of *UIControl*.
17 | - (void)cr_resetAllTargets;
18 |
19 | - (void)cr_normalizeFrame;
20 |
21 | - (void)cr_adjustContentSizePostLayoutRecursivelyIfNeeded;
22 |
23 | @end
24 |
25 | @interface UIScrollView (CRNode)
26 | /// Set the scroll view content size (if necessary).
27 | - (void)cr_adjustContentSizePostLayout;
28 |
29 | @end
30 |
31 | NS_ASSUME_NONNULL_END
32 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/UIView+CRNode.mm:
--------------------------------------------------------------------------------
1 | #import
2 | #import "CRMacros.h"
3 | #import "CRNode.h"
4 | #import "CRNodeBridge.h"
5 | #import "UIView+CRNode.h"
6 | #import "YGLayout.h"
7 |
8 | @implementation UIView (CRNode)
9 | @dynamic cr_nodeBridge;
10 |
11 | - (BOOL)cr_hasNode {
12 | return self.cr_nodeBridge.node != nil;
13 | }
14 |
15 | - (void)setCr_nodeBridge:(CRNodeBridge *)obj {
16 | objc_setAssociatedObject(self, @selector(cr_nodeBridge), obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
17 | }
18 |
19 | - (CRNodeBridge *)cr_nodeBridge {
20 | auto bridge =
21 | CR_DYNAMIC_CAST(CRNodeBridge, objc_getAssociatedObject(self, @selector(cr_nodeBridge)));
22 | const auto ret = CR_NIL_COALESCING(bridge, [[CRNodeBridge alloc] initWithView:self]);
23 | if (ret != bridge) self.cr_nodeBridge = ret;
24 | return ret;
25 | }
26 |
27 | - (void)cr_resetAllTargets {
28 | CR_ASSERT_ON_MAIN_THREAD();
29 | const auto control = CR_DYNAMIC_CAST(UIControl, self);
30 | CR_FOREACH(target, control.allTargets) {
31 | [control removeTarget:target action:nil forControlEvents:UIControlEventAllEvents];
32 | }
33 | }
34 |
35 | - (void)cr_normalizeFrame {
36 | auto rect = self.frame;
37 | rect.origin.x = CR_NORMALIZE(rect.origin.x);
38 | rect.origin.y = CR_NORMALIZE(rect.origin.y);
39 | rect.size.width = CR_NORMALIZE(rect.size.width);
40 | rect.size.height = CR_NORMALIZE(rect.size.height);
41 | self.frame = rect;
42 | }
43 |
44 | - (void)cr_adjustContentSizePostLayoutRecursivelyIfNeeded {
45 | if (!self.cr_hasNode) return;
46 | if ([self isKindOfClass:UIScrollView.class]) {
47 | [(UIScrollView *)self cr_adjustContentSizePostLayout];
48 | }
49 | CR_FOREACH(subview, self.subviews) {
50 | [subview cr_adjustContentSizePostLayoutRecursivelyIfNeeded];
51 | }
52 | }
53 |
54 | @end
55 |
56 | @implementation UIScrollView (CRNode)
57 |
58 | - (void)cr_adjustContentSizePostLayout {
59 | if ([self isKindOfClass:UITableView.class]) return;
60 | if ([self isKindOfClass:UICollectionView.class]) return;
61 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC));
62 | dispatch_after(time, dispatch_get_main_queue(), ^{
63 | CGFloat x = 0;
64 | CGFloat y = 0;
65 | CR_FOREACH(subview, self.subviews) {
66 | x = MAX(x, CGRectGetMaxX(subview.frame));
67 | y = MAX(y, CGRectGetMaxY(subview.frame));
68 | }
69 | if (self.yoga.flexDirection == YGFlexDirectionColumn ||
70 | self.yoga.flexDirection == YGFlexDirectionRowReverse) {
71 | self.contentSize = CGSizeMake(self.contentSize.width, y);
72 | } else {
73 | self.contentSize = CGSizeMake(x, self.contentSize.height);
74 | }
75 | self.scrollEnabled = true;
76 | });
77 | }
78 |
79 | @end
80 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/YGLayout.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 | #import
12 | #import "Yoga.h"
13 |
14 | static CGSize YGNaNSize = {
15 | .width = YGUndefined,
16 | .height = YGUndefined,
17 | };
18 |
19 | typedef NS_OPTIONS(NSInteger, YGDimensionFlexibility) {
20 | YGDimensionFlexibilityFlexibleWidth = 1 << 0,
21 | YGDimensionFlexibilityFlexibleHeigth = 1 << 1,
22 | };
23 |
24 | @interface YGLayout : NSObject
25 |
26 | /**
27 | The property that decides if we should include this view when calculating layout. Defaults totrue.
28 | */
29 | @property(nonatomic, readwrite, assign, setter=setIncludedInLayout:) BOOL isIncludedInLayout;
30 |
31 | /**
32 | The property that decides during layout/sizing whether or not styling properties should be applied.
33 | Defaults to NO.
34 | */
35 | @property(nonatomic, readwrite, assign, setter=setEnabled:) BOOL isEnabled;
36 |
37 | @property(nonatomic, readwrite, assign) YGDirection direction;
38 | @property(nonatomic, readwrite, assign) YGFlexDirection flexDirection;
39 | @property(nonatomic, readwrite, assign) YGJustify justifyContent;
40 | @property(nonatomic, readwrite, assign) YGAlign alignContent;
41 | @property(nonatomic, readwrite, assign) YGAlign alignItems;
42 | @property(nonatomic, readwrite, assign) YGAlign alignSelf;
43 | @property(nonatomic, readwrite, assign) YGPositionType position;
44 | @property(nonatomic, readwrite, assign) YGWrap flexWrap;
45 | @property(nonatomic, readwrite, assign) YGOverflow overflow;
46 | @property(nonatomic, readwrite, assign) YGDisplay display;
47 |
48 | @property(nonatomic, readwrite, assign) CGFloat flexGrow;
49 | @property(nonatomic, readwrite, assign) CGFloat flexShrink;
50 | @property(nonatomic, readwrite, assign) CGFloat flexBasis;
51 |
52 | @property(nonatomic, readwrite, assign) CGFloat left;
53 | @property(nonatomic, readwrite, assign) CGFloat top;
54 | @property(nonatomic, readwrite, assign) CGFloat right;
55 | @property(nonatomic, readwrite, assign) CGFloat bottom;
56 | @property(nonatomic, readwrite, assign) CGFloat start;
57 | @property(nonatomic, readwrite, assign) CGFloat end;
58 |
59 | @property(nonatomic, readwrite, assign) CGFloat marginLeft;
60 | @property(nonatomic, readwrite, assign) CGFloat marginTop;
61 | @property(nonatomic, readwrite, assign) CGFloat marginRight;
62 | @property(nonatomic, readwrite, assign) CGFloat marginBottom;
63 | @property(nonatomic, readwrite, assign) CGFloat marginStart;
64 | @property(nonatomic, readwrite, assign) CGFloat marginEnd;
65 | @property(nonatomic, readwrite, assign) CGFloat marginHorizontal;
66 | @property(nonatomic, readwrite, assign) CGFloat marginVertical;
67 | @property(nonatomic, readwrite, assign) CGFloat margin;
68 |
69 | @property(nonatomic, readwrite, assign) CGFloat paddingLeft;
70 | @property(nonatomic, readwrite, assign) CGFloat paddingTop;
71 | @property(nonatomic, readwrite, assign) CGFloat paddingRight;
72 | @property(nonatomic, readwrite, assign) CGFloat paddingBottom;
73 | @property(nonatomic, readwrite, assign) CGFloat paddingStart;
74 | @property(nonatomic, readwrite, assign) CGFloat paddingEnd;
75 | @property(nonatomic, readwrite, assign) CGFloat paddingHorizontal;
76 | @property(nonatomic, readwrite, assign) CGFloat paddingVertical;
77 | @property(nonatomic, readwrite, assign) CGFloat padding;
78 |
79 | @property(nonatomic, readwrite, assign) CGFloat borderLeftWidth;
80 | @property(nonatomic, readwrite, assign) CGFloat borderTopWidth;
81 | @property(nonatomic, readwrite, assign) CGFloat borderRightWidth;
82 | @property(nonatomic, readwrite, assign) CGFloat borderBottomWidth;
83 | @property(nonatomic, readwrite, assign) CGFloat borderStartWidth;
84 | @property(nonatomic, readwrite, assign) CGFloat borderEndWidth;
85 | @property(nonatomic, readwrite, assign) CGFloat borderWidth;
86 |
87 | @property(nonatomic, readwrite, assign) CGFloat width;
88 | @property(nonatomic, readwrite, assign) CGFloat height;
89 | @property(nonatomic, readwrite, assign) CGFloat minWidth;
90 | @property(nonatomic, readwrite, assign) CGFloat minHeight;
91 | @property(nonatomic, readwrite, assign) CGFloat maxWidth;
92 | @property(nonatomic, readwrite, assign) CGFloat maxHeight;
93 |
94 | // Yoga specific properties, not compatible with flexbox specification
95 | @property(nonatomic, readwrite, assign) CGFloat aspectRatio;
96 |
97 | /**
98 | Get the resolved direction of this node. This won't be YGDirectionInherit
99 | */
100 | @property(nonatomic, readonly, assign) YGDirection resolvedDirection;
101 |
102 | /**
103 | Perform a layout calculation and update the frames of the views in the hierarchy with the results.
104 | If the origin is not preserved, the root view's layout results will applied from {0,0}.
105 | */
106 | - (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
107 | NS_SWIFT_NAME(applyLayout(preservingOrigin:));
108 |
109 | /**
110 | Perform a layout calculation and update the frames of the views in the hierarchy with the results.
111 | If the origin is not preserved, the root view's layout results will applied from {0,0}.
112 | */
113 | - (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
114 | dimensionFlexibility:(YGDimensionFlexibility)dimensionFlexibility
115 | NS_SWIFT_NAME(applyLayout(preservingOrigin:dimensionFlexibility:));
116 |
117 | /**
118 | Returns the size of the view if no constraints were given. This could equivalent to calling [self
119 | sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
120 | */
121 | @property(nonatomic, readonly, assign) CGSize intrinsicSize;
122 |
123 | /**
124 | Returns the number of children that are using Flexbox.
125 | */
126 | @property(nonatomic, readonly, assign) NSUInteger numberOfChildren;
127 |
128 | /**
129 | Return a BOOL indiciating whether or not we this node contains any subviews that are included in
130 | Yoga's layout.
131 | */
132 | @property(nonatomic, readonly, assign) BOOL isLeaf;
133 |
134 | /**
135 | Return's a BOOL indicating if a view is dirty. When a node is dirty
136 | it usually indicates that it will be remeasured on the next layout pass.
137 | */
138 | @property(nonatomic, readonly, assign) BOOL isDirty;
139 |
140 | /** Analogous to flexShrink = 1 and flexGrow = 1 */
141 | - (void)flex;
142 |
143 | /**
144 | Mark that a view's layout needs to be recalculated. Only works for leaf views.
145 | */
146 | - (void)markDirty;
147 |
148 | @end
149 |
150 | @interface YGLayout ()
151 | /** Reference to the yoga node. */
152 | @property(nonatomic, assign, nonnull, readonly) YGNodeRef node;
153 | /** Constructs a new layout object associated to the view passed as argument. */
154 | - (instancetype)initWithView:(UIView *)view;
155 | @end
156 |
157 | // UIView+Yoga
158 |
159 | NS_ASSUME_NONNULL_BEGIN
160 |
161 | typedef void (^YGLayoutConfigurationBlock)(YGLayout *);
162 |
163 | @interface UIView (Yoga)
164 | /** The YGLayout that is attached to this view. It is lazily created. */
165 | @property(nonatomic, readonly, strong) YGLayout *yoga;
166 | /** Indicates whether or not Yoga is enabled */
167 | @property(nonatomic, readonly, assign) BOOL isYogaEnabled;
168 | /**
169 | In ObjC land, every time you access `view.yoga.*` you are adding another `objc_msgSend`
170 | to your code. If you plan on making multiple changes to YGLayout, it's more performant
171 | to use this method, which uses a single objc_msgSend call.
172 | */
173 | - (void)configureLayoutWithBlock:(YGLayoutConfigurationBlock)block
174 | NS_SWIFT_NAME(configureLayout(block:));
175 | @end
176 |
177 | #pragma mark - Categories
178 |
179 | @interface UIView (YGAdditions)
180 | /// Redirects to 'layer.cornerRadius'
181 | @property(nonatomic, assign) CGFloat cornerRadius;
182 | /// Redirects to 'layer.borderWidth'
183 | @property(nonatomic, assign) CGFloat borderWidth;
184 | /// Redirects to 'layer.borderColor'
185 | @property(nonatomic, strong) UIColor *borderColor;
186 | /// The opacity of the shadow. Defaults to 0. Specifying a value outside the
187 | @property(nonatomic, assign) CGFloat shadowOpacity;
188 | /// The blur radius used to create the shadow. Defaults to 3.
189 | @property(nonatomic, assign) CGFloat shadowRadius;
190 | /// The shadow offset. Defaults to (0, -3)
191 | @property(nonatomic, assign) CGSize shadowOffset;
192 | /// The color of the shadow. Defaults to opaque black.
193 | @property(nonatomic, strong) UIColor *shadowColor;
194 | @end
195 |
196 | @interface UIButton (YGAdditions)
197 | ////Symeetrical to -[UIButton titleForState:]
198 | @property(nonatomic, strong) NSString *text;
199 | @property(nonatomic, strong) NSString *highlightedText;
200 | @property(nonatomic, strong) NSString *selectedText;
201 | @property(nonatomic, strong) NSString *disabledText;
202 | // Symeetrical to -[UIButton titleColorForState:]
203 | @property(nonatomic, strong) UIColor *textColor;
204 | @property(nonatomic, strong) UIColor *highlightedTextColor;
205 | @property(nonatomic, strong) UIColor *selectedTextColor;
206 | @property(nonatomic, strong) UIColor *disabledTextColor;
207 | @property(nonatomic, strong) UIColor *backgroundColorImage;
208 | ////Symmetrical to -[UIButton backgroundImageForState:]
209 | @property(nonatomic, strong) UIImage *backgroundImage;
210 | @property(nonatomic, strong) UIImage *highlightedBackgroundImage;
211 | @property(nonatomic, strong) UIImage *selectedBackgroundImage;
212 | @property(nonatomic, strong) UIImage *disabledBackgroundImage;
213 | // Symmetrical to -[UIButton imageForState:]
214 | @property(nonatomic, strong) UIImage *image;
215 | @property(nonatomic, strong) UIImage *highlightedImage;
216 | @property(nonatomic, strong) UIImage *selectedImage;
217 | @property(nonatomic, strong) UIImage *disabledImage;
218 | @end
219 |
220 | @interface UIImage (YGAdditions)
221 | + (UIImage *)yg_imageWithColor:(UIColor *)color;
222 | + (UIImage *)yg_imageWithColor:(UIColor *)color size:(CGSize)size;
223 | + (UIImage *)yg_imageFromString:(NSString *)string
224 | color:(UIColor *)color
225 | font:(UIFont *)font
226 | size:(CGSize)size;
227 | @end
228 |
229 | @interface UIViewController (YGAdditions)
230 | /** Whether the controller is being modally presented or not. */
231 | - (BOOL)isModal;
232 | @end
233 |
234 | /** Returns the top-most view controller in the hierarchy. */
235 | extern UIViewController *UIGetTopmostViewController(void);
236 |
237 | NS_ASSUME_NONNULL_END
238 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/YGLayout.mm:
--------------------------------------------------------------------------------
1 | #import "YGLayout.h"
2 | #import "Yoga.h"
3 |
4 | #define YG_PROPERTY(type, lowercased_name, capitalized_name) \
5 | -(type)lowercased_name { \
6 | return YGNodeStyleGet##capitalized_name(self.node); \
7 | } \
8 | \
9 | -(void)set##capitalized_name : (type)lowercased_name { \
10 | YGNodeStyleSet##capitalized_name(self.node, lowercased_name); \
11 | }
12 |
13 | #define YG_VALUE_PROPERTY(lowercased_name, capitalized_name) \
14 | -(CGFloat)lowercased_name { \
15 | YGValue value = YGNodeStyleGet##capitalized_name(self.node); \
16 | if (value.unit == YGUnitPoint) { \
17 | return value.value; \
18 | } else { \
19 | return YGUndefined; \
20 | } \
21 | } \
22 | \
23 | -(void)set##capitalized_name : (CGFloat)lowercased_name { \
24 | YGNodeStyleSet##capitalized_name(self.node, lowercased_name); \
25 | }
26 |
27 | #define YG_EDGE_PROPERTY_GETTER(lowercased_name, capitalized_name, property, edge) \
28 | -(CGFloat)lowercased_name { \
29 | return YGNodeStyleGet##property(self.node, edge); \
30 | }
31 |
32 | #define YG_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge) \
33 | -(void)set##capitalized_name : (CGFloat)lowercased_name { \
34 | YGNodeStyleSet##property(self.node, edge, lowercased_name); \
35 | }
36 |
37 | #define YG_EDGE_PROPERTY(lowercased_name, capitalized_name, property, edge) \
38 | YG_EDGE_PROPERTY_GETTER(lowercased_name, capitalized_name, property, edge) \
39 | YG_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge)
40 |
41 | #define YG_VALUE_EDGE_PROPERTY_GETTER(objc_lowercased_name, objc_capitalized_name, c_name, edge) \
42 | -(CGFloat)objc_lowercased_name { \
43 | YGValue value = YGNodeStyleGet##c_name(self.node, edge); \
44 | if (value.unit == YGUnitPoint) { \
45 | return value.value; \
46 | } else { \
47 | return YGUndefined; \
48 | } \
49 | }
50 |
51 | #define YG_VALUE_EDGE_PROPERTY_SETTER(objc_lowercased_name, objc_capitalized_name, c_name, edge) \
52 | -(void)set##objc_capitalized_name : (CGFloat)objc_lowercased_name { \
53 | YGNodeStyleSet##c_name(self.node, edge, objc_lowercased_name); \
54 | }
55 |
56 | #define YG_VALUE_EDGE_PROPERTY(lowercased_name, capitalized_name, property, edge) \
57 | YG_VALUE_EDGE_PROPERTY_GETTER(lowercased_name, capitalized_name, property, edge) \
58 | YG_VALUE_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge)
59 |
60 | #define YG_VALUE_EDGES_PROPERTIES(lowercased_name, capitalized_name) \
61 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Left, capitalized_name##Left, capitalized_name, \
62 | YGEdgeLeft) \
63 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Top, capitalized_name##Top, capitalized_name, YGEdgeTop) \
64 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Right, capitalized_name##Right, capitalized_name, \
65 | YGEdgeRight) \
66 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Bottom, capitalized_name##Bottom, capitalized_name, \
67 | YGEdgeBottom) \
68 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Start, capitalized_name##Start, capitalized_name, \
69 | YGEdgeStart) \
70 | YG_VALUE_EDGE_PROPERTY(lowercased_name##End, capitalized_name##End, capitalized_name, YGEdgeEnd) \
71 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Horizontal, capitalized_name##Horizontal, \
72 | capitalized_name, YGEdgeHorizontal) \
73 | YG_VALUE_EDGE_PROPERTY(lowercased_name##Vertical, capitalized_name##Vertical, capitalized_name, \
74 | YGEdgeVertical) \
75 | YG_VALUE_EDGE_PROPERTY(lowercased_name, capitalized_name, capitalized_name, YGEdgeAll)
76 |
77 | static YGConfigRef globalConfig;
78 |
79 | @interface YGLayout ()
80 |
81 | @property(nonatomic, weak, readonly) UIView *view;
82 |
83 | @end
84 |
85 | @implementation YGLayout
86 |
87 | @synthesize isEnabled = _isEnabled;
88 | @synthesize isIncludedInLayout = _isIncludedInLayout;
89 | @synthesize node = _node;
90 |
91 | + (void)initialize {
92 | globalConfig = YGConfigNew();
93 | YGConfigSetExperimentalFeatureEnabled(globalConfig, YGExperimentalFeatureWebFlexBasis, true);
94 | }
95 |
96 | - (instancetype)initWithView:(UIView *)view {
97 | if (self = [super init]) {
98 | _view = view;
99 | _node = YGNodeNewWithConfig(globalConfig);
100 | YGNodeSetContext(_node, (__bridge void *)view);
101 | _isEnabled = false;
102 | _isIncludedInLayout = true;
103 | }
104 | return self;
105 | }
106 |
107 | - (void)dealloc {
108 | YGNodeFree(self.node);
109 | }
110 |
111 | - (void)flex {
112 | self.flexGrow = 1;
113 | self.flexShrink = 1;
114 | }
115 |
116 | - (BOOL)isDirty {
117 | return YGNodeIsDirty(self.node);
118 | }
119 |
120 | - (void)markDirty {
121 | if (self.isDirty || !self.isLeaf) {
122 | return;
123 | }
124 | // Yoga is not happy if we try to mark a node as "dirty" before we have set
125 | // the measure function. Since we already know that this is a leaf,
126 | // this *should* be fine. Forgive me Hack Gods.
127 | const YGNodeRef node = self.node;
128 | if (YGNodeGetMeasureFunc(node) == nil) {
129 | YGNodeSetMeasureFunc(node, YGMeasureView);
130 | }
131 | YGNodeMarkDirty(node);
132 | }
133 |
134 | - (NSUInteger)numberOfChildren {
135 | return YGNodeGetChildCount(self.node);
136 | }
137 |
138 | - (BOOL)isLeaf {
139 | NSAssert([NSThread isMainThread], @"This method must be called on the main thread.");
140 | if (self.isEnabled) {
141 | for (UIView *subview in self.view.subviews) {
142 | YGLayout *const yoga = subview.yoga;
143 | if (yoga.isEnabled && yoga.isIncludedInLayout) {
144 | return false;
145 | }
146 | }
147 | }
148 | return true;
149 | }
150 |
151 | #pragma mark - Style
152 |
153 | - (YGPositionType)position {
154 | return YGNodeStyleGetPositionType(self.node);
155 | }
156 |
157 | - (void)setPosition:(YGPositionType)position {
158 | YGNodeStyleSetPositionType(self.node, position);
159 | }
160 |
161 | YG_PROPERTY(YGDirection, direction, Direction)
162 | YG_PROPERTY(YGFlexDirection, flexDirection, FlexDirection)
163 | YG_PROPERTY(YGJustify, justifyContent, JustifyContent)
164 | YG_PROPERTY(YGAlign, alignContent, AlignContent)
165 | YG_PROPERTY(YGAlign, alignItems, AlignItems)
166 | YG_PROPERTY(YGAlign, alignSelf, AlignSelf)
167 | YG_PROPERTY(YGWrap, flexWrap, FlexWrap)
168 | YG_PROPERTY(YGOverflow, overflow, Overflow)
169 | YG_PROPERTY(YGDisplay, display, Display)
170 |
171 | YG_PROPERTY(CGFloat, flexGrow, FlexGrow)
172 | YG_PROPERTY(CGFloat, flexShrink, FlexShrink)
173 | YG_VALUE_PROPERTY(flexBasis, FlexBasis)
174 |
175 | YG_VALUE_EDGE_PROPERTY(left, Left, Position, YGEdgeLeft)
176 | YG_VALUE_EDGE_PROPERTY(top, Top, Position, YGEdgeTop)
177 | YG_VALUE_EDGE_PROPERTY(right, Right, Position, YGEdgeRight)
178 | YG_VALUE_EDGE_PROPERTY(bottom, Bottom, Position, YGEdgeBottom)
179 | YG_VALUE_EDGE_PROPERTY(start, Start, Position, YGEdgeStart)
180 | YG_VALUE_EDGE_PROPERTY(end, End, Position, YGEdgeEnd)
181 | YG_VALUE_EDGES_PROPERTIES(margin, Margin)
182 | YG_VALUE_EDGES_PROPERTIES(padding, Padding)
183 |
184 | YG_EDGE_PROPERTY(borderLeftWidth, BorderLeftWidth, Border, YGEdgeLeft)
185 | YG_EDGE_PROPERTY(borderTopWidth, BorderTopWidth, Border, YGEdgeTop)
186 | YG_EDGE_PROPERTY(borderRightWidth, BorderRightWidth, Border, YGEdgeRight)
187 | YG_EDGE_PROPERTY(borderBottomWidth, BorderBottomWidth, Border, YGEdgeBottom)
188 | YG_EDGE_PROPERTY(borderStartWidth, BorderStartWidth, Border, YGEdgeStart)
189 | YG_EDGE_PROPERTY(borderEndWidth, BorderEndWidth, Border, YGEdgeEnd)
190 | YG_EDGE_PROPERTY(borderWidth, BorderWidth, Border, YGEdgeAll)
191 |
192 | YG_VALUE_PROPERTY(width, Width)
193 | YG_VALUE_PROPERTY(height, Height)
194 | YG_VALUE_PROPERTY(minWidth, MinWidth)
195 | YG_VALUE_PROPERTY(minHeight, MinHeight)
196 | YG_VALUE_PROPERTY(maxWidth, MaxWidth)
197 | YG_VALUE_PROPERTY(maxHeight, MaxHeight)
198 | YG_PROPERTY(CGFloat, aspectRatio, AspectRatio)
199 |
200 | #pragma mark - Layout and Sizing
201 |
202 | - (YGDirection)resolvedDirection {
203 | return YGNodeLayoutGetDirection(self.node);
204 | }
205 |
206 | - (void)applyLayout {
207 | [self calculateLayoutWithSize:self.view.bounds.size];
208 | YGApplyLayoutToViewHierarchy(self.view, NO);
209 | }
210 |
211 | - (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin {
212 | [self calculateLayoutWithSize:self.view.bounds.size];
213 | YGApplyLayoutToViewHierarchy(self.view, preserveOrigin);
214 | }
215 |
216 | - (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
217 | dimensionFlexibility:(YGDimensionFlexibility)dimensionFlexibility {
218 | CGSize size = self.view.bounds.size;
219 | if (dimensionFlexibility & YGDimensionFlexibilityFlexibleWidth) {
220 | size.width = YGUndefined;
221 | }
222 | if (dimensionFlexibility & YGDimensionFlexibilityFlexibleHeigth) {
223 | size.height = YGUndefined;
224 | }
225 | [self calculateLayoutWithSize:size];
226 | YGApplyLayoutToViewHierarchy(self.view, preserveOrigin);
227 | }
228 |
229 | - (CGSize)intrinsicSize {
230 | const CGSize constrainedSize = {
231 | .width = YGUndefined,
232 | .height = YGUndefined,
233 | };
234 | return [self calculateLayoutWithSize:constrainedSize];
235 | }
236 |
237 | #pragma mark - Private
238 |
239 | - (CGSize)calculateLayoutWithSize:(CGSize)size {
240 | NSAssert([NSThread isMainThread], @"Yoga calculation must be done on main.");
241 | YGAttachNodesFromViewHierachy(self.view);
242 | const YGNodeRef node = self.node;
243 | YGNodeCalculateLayout(node, size.width, size.height, YGNodeStyleGetDirection(node));
244 | return (CGSize){
245 | .width = YGNodeLayoutGetWidth(node),
246 | .height = YGNodeLayoutGetHeight(node),
247 | };
248 | }
249 |
250 | static YGSize YGMeasureView(YGNodeRef node, float width, YGMeasureMode widthMode, float height,
251 | YGMeasureMode heightMode) {
252 | const CGFloat constrainedWidth = (widthMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : width;
253 | const CGFloat constrainedHeight = (heightMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : height;
254 | UIView *view = (__bridge UIView *)YGNodeGetContext(node);
255 | const CGSize sizeThatFits = [view sizeThatFits:(CGSize){
256 | .width = constrainedWidth,
257 | .height = constrainedHeight,
258 | }];
259 | return (YGSize){
260 | .width = static_cast(
261 | YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode)),
262 | .height = static_cast(
263 | YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode)),
264 | };
265 | }
266 |
267 | static CGFloat YGSanitizeMeasurement(CGFloat constrainedSize, CGFloat measuredSize,
268 | YGMeasureMode measureMode) {
269 | CGFloat result;
270 | if (measureMode == YGMeasureModeExactly) {
271 | result = constrainedSize;
272 | } else if (measureMode == YGMeasureModeAtMost) {
273 | result = MIN(constrainedSize, measuredSize);
274 | } else {
275 | result = measuredSize;
276 | }
277 |
278 | return result;
279 | }
280 |
281 | static BOOL YGNodeHasExactSameChildren(const YGNodeRef node, NSArray *subviews) {
282 | if (YGNodeGetChildCount(node) != subviews.count) {
283 | return false;
284 | }
285 | for (int i = 0; i < subviews.count; i++) {
286 | if (YGNodeGetChild(node, i) != subviews[i].yoga.node) {
287 | return false;
288 | }
289 | }
290 | return true;
291 | }
292 |
293 | static void YGAttachNodesFromViewHierachy(UIView *const view) {
294 | YGLayout *const yoga = view.yoga;
295 | const YGNodeRef node = yoga.node;
296 | // Only leaf nodes should have a measure function
297 | if (yoga.isLeaf) {
298 | YGRemoveAllChildren(node);
299 | YGNodeSetMeasureFunc(node, YGMeasureView);
300 | } else {
301 | YGNodeSetMeasureFunc(node, nil);
302 | NSMutableArray *subviewsToInclude =
303 | [[NSMutableArray alloc] initWithCapacity:view.subviews.count];
304 | for (UIView *subview in view.subviews) {
305 | if (subview.yoga.isIncludedInLayout) {
306 | [subviewsToInclude addObject:subview];
307 | }
308 | }
309 | if (!YGNodeHasExactSameChildren(node, subviewsToInclude)) {
310 | YGRemoveAllChildren(node);
311 | for (int i = 0; i < subviewsToInclude.count; i++) {
312 | YGNodeInsertChild(node, subviewsToInclude[i].yoga.node, i);
313 | }
314 | }
315 | for (UIView *const subview in subviewsToInclude) {
316 | YGAttachNodesFromViewHierachy(subview);
317 | }
318 | }
319 | }
320 |
321 | static void YGRemoveAllChildren(const YGNodeRef node) {
322 | if (node == nil) {
323 | return;
324 | }
325 | while (YGNodeGetChildCount(node) > 0) {
326 | YGNodeRemoveChild(node, YGNodeGetChild(node, YGNodeGetChildCount(node) - 1));
327 | }
328 | }
329 |
330 | static CGFloat YGRoundPixelValue(CGFloat value) {
331 | static CGFloat scale;
332 | static dispatch_once_t onceToken;
333 | dispatch_once(&onceToken, ^() {
334 | scale = [UIScreen mainScreen].scale;
335 | });
336 | return roundf(value * scale) / scale;
337 | }
338 |
339 | static void YGApplyLayoutToViewHierarchy(UIView *view, BOOL preserveOrigin) {
340 | NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread.");
341 | const YGLayout *yoga = view.yoga;
342 | if (!yoga.isIncludedInLayout) {
343 | return;
344 | }
345 | YGNodeRef node = yoga.node;
346 | const CGPoint topLeft = {
347 | YGNodeLayoutGetLeft(node),
348 | YGNodeLayoutGetTop(node),
349 | };
350 | const CGPoint bottomRight = {
351 | topLeft.x + YGNodeLayoutGetWidth(node),
352 | topLeft.y + YGNodeLayoutGetHeight(node),
353 | };
354 | const CGPoint origin = preserveOrigin ? view.frame.origin : CGPointZero;
355 | view.frame = (CGRect){
356 | .origin =
357 | {
358 | .x = YGRoundPixelValue(topLeft.x + origin.x),
359 | .y = YGRoundPixelValue(topLeft.y + origin.y),
360 | },
361 | .size =
362 | {
363 | .width = YGRoundPixelValue(bottomRight.x) - YGRoundPixelValue(topLeft.x),
364 | .height = YGRoundPixelValue(bottomRight.y) - YGRoundPixelValue(topLeft.y),
365 | },
366 | };
367 | if (!yoga.isLeaf) {
368 | for (NSUInteger i = 0; i < view.subviews.count; i++) {
369 | YGApplyLayoutToViewHierarchy(view.subviews[i], NO);
370 | }
371 | }
372 | }
373 |
374 | @end
375 |
376 | // UIView+Yoga
377 |
378 | #import
379 |
380 | static const void *kYGYogaAssociatedKey = &kYGYogaAssociatedKey;
381 |
382 | @implementation UIView (YogaKit)
383 |
384 | - (YGLayout *)yoga {
385 | YGLayout *yoga = objc_getAssociatedObject(self, kYGYogaAssociatedKey);
386 | if (!yoga) {
387 | yoga = [[YGLayout alloc] initWithView:self];
388 | objc_setAssociatedObject(self, kYGYogaAssociatedKey, yoga, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
389 | }
390 | return yoga;
391 | }
392 |
393 | - (BOOL)isYogaEnabled {
394 | return objc_getAssociatedObject(self, kYGYogaAssociatedKey) != nil;
395 | }
396 |
397 | - (void)configureLayoutWithBlock:(YGLayoutConfigurationBlock)block {
398 | if (block) {
399 | block(self.yoga);
400 | }
401 | }
402 |
403 | @end
404 |
405 | #pragma mark - Categories
406 |
407 | @implementation UIView (YGAdditions)
408 |
409 | - (CGFloat)cornerRadius {
410 | return self.layer.cornerRadius;
411 | }
412 |
413 | - (void)setCornerRadius:(CGFloat)cornerRadius {
414 | self.clipsToBounds = true;
415 | self.layer.cornerRadius = cornerRadius;
416 | }
417 |
418 | - (CGFloat)borderWidth {
419 | return self.layer.borderWidth;
420 | }
421 |
422 | - (void)setBorderWidth:(CGFloat)borderWidth {
423 | self.layer.borderWidth = borderWidth;
424 | }
425 |
426 | - (UIColor *)borderColor {
427 | return [UIColor colorWithCGColor:self.layer.borderColor];
428 | }
429 |
430 | - (void)setBorderColor:(UIColor *)borderColor {
431 | self.layer.borderColor = borderColor.CGColor;
432 | }
433 |
434 | - (CGFloat)shadowOpacity {
435 | return self.layer.shadowOpacity;
436 | }
437 |
438 | - (void)setShadowOpacity:(CGFloat)shadowOpacity {
439 | self.layer.shadowOpacity = shadowOpacity;
440 | }
441 |
442 | - (CGFloat)shadowRadius {
443 | return self.layer.shadowRadius;
444 | }
445 |
446 | - (void)setShadowRadius:(CGFloat)shadowRadius {
447 | self.layer.shadowRadius = shadowRadius;
448 | }
449 |
450 | - (CGSize)shadowOffset {
451 | return self.layer.shadowOffset;
452 | }
453 |
454 | - (void)setShadowOffset:(CGSize)shadowOffset {
455 | self.layer.shadowOffset = shadowOffset;
456 | }
457 |
458 | - (UIColor *)shadowColor {
459 | return [UIColor colorWithCGColor:self.layer.shadowColor];
460 | }
461 |
462 | - (void)setShadowColor:(UIColor *)shadowColor {
463 | self.layer.shadowColor = shadowColor.CGColor;
464 | }
465 |
466 | @end
467 |
468 | #pragma mark - UIButton
469 |
470 | @implementation UIButton (YGAdditions)
471 |
472 | - (NSString *)text {
473 | return [self titleForState:UIControlStateNormal];
474 | }
475 |
476 | - (void)setText:(NSString *)text {
477 | [self setTitle:text forState:UIControlStateNormal];
478 | }
479 |
480 | - (NSString *)highlightedText {
481 | return [self titleForState:UIControlStateHighlighted];
482 | }
483 |
484 | - (void)setHighlightedText:(NSString *)highlightedText {
485 | [self setTitle:highlightedText forState:UIControlStateHighlighted];
486 | }
487 |
488 | - (NSString *)selectedText {
489 | return [self titleForState:UIControlStateSelected];
490 | }
491 |
492 | - (void)setSelectedText:(NSString *)selectedText {
493 | [self setTitle:selectedText forState:UIControlStateSelected];
494 | }
495 |
496 | - (NSString *)disabledText {
497 | return [self titleForState:UIControlStateDisabled];
498 | }
499 |
500 | - (void)setDisabledText:(NSString *)disabledText {
501 | [self setTitle:disabledText forState:UIControlStateDisabled];
502 | }
503 |
504 | - (UIColor *)textColor {
505 | return [self titleColorForState:UIControlStateNormal];
506 | }
507 |
508 | - (void)setTextColor:(UIColor *)textColor {
509 | [self setTitleColor:textColor forState:UIControlStateNormal];
510 | }
511 |
512 | - (UIColor *)highlightedTextColor {
513 | return [self titleColorForState:UIControlStateHighlighted];
514 | }
515 |
516 | - (void)setHighlightedTextColor:(UIColor *)highlightedTextColor {
517 | [self setTitleColor:highlightedTextColor forState:UIControlStateHighlighted];
518 | }
519 |
520 | - (UIColor *)selectedTextColor {
521 | return [self titleColorForState:UIControlStateSelected];
522 | }
523 |
524 | - (void)setSelectedTextColor:(UIColor *)selectedTextColor {
525 | [self setTitleColor:selectedTextColor forState:UIControlStateSelected];
526 | }
527 |
528 | - (UIColor *)disabledTextColor {
529 | return [self titleColorForState:UIControlStateDisabled];
530 | }
531 |
532 | - (void)setDisabledTextColor:(UIColor *)disabledTextColor {
533 | [self setTitleColor:disabledTextColor forState:UIControlStateDisabled];
534 | }
535 |
536 | - (UIColor *)backgroundColorImage {
537 | return nil;
538 | }
539 |
540 | - (void)setBackgroundColorImage:(UIColor *)backgroundColor {
541 | UIImage *image = [UIImage yg_imageWithColor:backgroundColor];
542 | self.backgroundImage = image;
543 | }
544 |
545 | - (UIImage *)backgroundImage {
546 | return [self backgroundImageForState:UIControlStateNormal];
547 | }
548 |
549 | - (void)setBackgroundImage:(UIImage *)backgroundImage {
550 | [self setBackgroundImage:backgroundImage forState:UIControlStateNormal];
551 | }
552 |
553 | - (UIImage *)highlightedBackgroundImage {
554 | return [self backgroundImageForState:UIControlStateHighlighted];
555 | }
556 |
557 | - (void)setHighlightedBackgroundImage:(UIImage *)highlightedBackgroundImage {
558 | [self setBackgroundImage:highlightedBackgroundImage forState:UIControlStateHighlighted];
559 | }
560 |
561 | - (UIImage *)selectedBackgroundImage {
562 | return [self backgroundImageForState:UIControlStateSelected];
563 | }
564 |
565 | - (void)setSelectedBackgroundImage:(UIImage *)selectedBackgroundImage {
566 | [self setBackgroundImage:selectedBackgroundImage forState:UIControlStateSelected];
567 | }
568 |
569 | - (UIImage *)disabledBackgroundImage {
570 | return [self backgroundImageForState:UIControlStateDisabled];
571 | }
572 |
573 | - (void)setDisabledBackgroundImage:(UIImage *)disabledBackgroundImage {
574 | [self setBackgroundImage:disabledBackgroundImage forState:UIControlStateDisabled];
575 | }
576 |
577 | - (UIImage *)image {
578 | return [self imageForState:UIControlStateNormal];
579 | }
580 |
581 | - (void)setImage:(UIImage *)image {
582 | [self setImage:image forState:UIControlStateNormal];
583 | }
584 |
585 | - (UIImage *)highlightedImage {
586 | return [self imageForState:UIControlStateHighlighted];
587 | }
588 |
589 | - (void)setHighlightedImage:(UIImage *)highlightedImage {
590 | [self setImage:highlightedImage forState:UIControlStateHighlighted];
591 | }
592 |
593 | - (UIImage *)selectedImage {
594 | return [self imageForState:UIControlStateSelected];
595 | }
596 |
597 | - (void)setSelectedImage:(UIImage *)selectedImage {
598 | [self setImage:selectedImage forState:UIControlStateSelected];
599 | }
600 |
601 | - (UIImage *)disabledImage {
602 | return [self imageForState:UIControlStateDisabled];
603 | }
604 |
605 | - (void)setDisabledImage:(UIImage *)disabledImage {
606 | [self setImage:disabledImage forState:UIControlStateDisabled];
607 | }
608 |
609 | @end
610 |
611 | #pragma mark - UIImage
612 |
613 | @implementation UIImage (YGAdditions)
614 |
615 | + (UIImage *)yg_imageWithColor:(UIColor *)color {
616 | return [self yg_imageWithColor:color size:(CGSize){1, 1}];
617 | }
618 |
619 | + (UIImage *)yg_imageWithColor:(UIColor *)color size:(CGSize)size {
620 | CGRect rect = (CGRect){CGPointZero, size};
621 | UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
622 | CGContextRef context = UIGraphicsGetCurrentContext();
623 | CGContextSetFillColorWithColor(context, [color CGColor]);
624 | CGContextFillRect(context, rect);
625 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
626 | UIGraphicsEndImageContext();
627 | return image;
628 | }
629 |
630 | + (UIImage *)yg_imageFromString:(NSString *)string
631 | color:(UIColor *)color
632 | font:(UIFont *)font
633 | size:(CGSize)size {
634 | UIGraphicsBeginImageContextWithOptions(size, NO, 0);
635 |
636 | NSDictionary *attributes = @{NSFontAttributeName : font, NSForegroundColorAttributeName : color};
637 | [string drawInRect:CGRectMake(0, 0, size.width, size.height) withAttributes:attributes];
638 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
639 | UIGraphicsEndImageContext();
640 |
641 | return image;
642 | }
643 |
644 | @end
645 |
646 | @implementation UIViewController (YGAdditions)
647 |
648 | - (BOOL)isModal {
649 | if ([self presentingViewController]) return true;
650 | if ([[[self navigationController] presentingViewController] presentedViewController] ==
651 | [self navigationController])
652 | return true;
653 | if ([[[self tabBarController] presentingViewController] isKindOfClass:[UITabBarController class]])
654 | return true;
655 | return false;
656 | }
657 |
658 | @end
659 |
660 | UIViewController *_Nullable UIGetTopmostViewController() {
661 | UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
662 | if ([baseVC isKindOfClass:[UINavigationController class]]) {
663 | return ((UINavigationController *)baseVC).visibleViewController;
664 | }
665 | if ([baseVC isKindOfClass:[UITabBarController class]]) {
666 | UIViewController *selectedTVC = ((UITabBarController *)baseVC).selectedViewController;
667 | if (selectedTVC) {
668 | return selectedTVC;
669 | }
670 | }
671 | if (baseVC.presentedViewController) {
672 | return baseVC.presentedViewController;
673 | }
674 | return baseVC;
675 | }
676 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/Yoga.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #pragma once
11 |
12 | #ifdef __cplusplus
13 | #define YG_EXTERN_C_BEGIN extern "C" {
14 | #define YG_EXTERN_C_END }
15 | #else
16 | #define YG_EXTERN_C_BEGIN
17 | #define YG_EXTERN_C_END
18 | #endif
19 |
20 | #ifdef _WINDLL
21 | #define WIN_EXPORT __declspec(dllexport)
22 | #else
23 | #define WIN_EXPORT
24 | #endif
25 |
26 | #ifdef WINARMDLL
27 | #define WIN_STRUCT(type) type *
28 | #define WIN_STRUCT_REF(value) &value
29 | #else
30 | #define WIN_STRUCT(type) type
31 | #define WIN_STRUCT_REF(value) value
32 | #endif
33 |
34 | #ifndef FB_ASSERTIONS_ENABLED
35 | #define FB_ASSERTIONS_ENABLED 1
36 | #endif
37 |
38 | #ifdef NS_ENUM
39 | // Cannot use NSInteger as NSInteger has a different size than int (which is the default type of a
40 | // enum).
41 | // Therefor when linking the Yoga C library into obj-c the header is a missmatch for the Yoga ABI.
42 | #define YG_ENUM_BEGIN(name) NS_ENUM(int, name)
43 | #define YG_ENUM_END(name)
44 | #else
45 | #define YG_ENUM_BEGIN(name) enum name
46 | #define YG_ENUM_END(name) name
47 | #endif
48 |
49 | #pragma once
50 |
51 | YG_EXTERN_C_BEGIN
52 |
53 | #define YGAlignCount 8
54 | typedef YG_ENUM_BEGIN(YGAlign){
55 | YGAlignAuto, YGAlignFlexStart, YGAlignCenter, YGAlignFlexEnd,
56 | YGAlignStretch, YGAlignBaseline, YGAlignSpaceBetween, YGAlignSpaceAround,
57 | } YG_ENUM_END(YGAlign);
58 | WIN_EXPORT const char *YGAlignToString(const YGAlign value);
59 |
60 | #define YGDimensionCount 2
61 | typedef YG_ENUM_BEGIN(YGDimension){
62 | YGDimensionWidth,
63 | YGDimensionHeight,
64 | } YG_ENUM_END(YGDimension);
65 | WIN_EXPORT const char *YGDimensionToString(const YGDimension value);
66 |
67 | #define YGDirectionCount 3
68 | typedef YG_ENUM_BEGIN(YGDirection){
69 | YGDirectionInherit,
70 | YGDirectionLTR,
71 | YGDirectionRTL,
72 | } YG_ENUM_END(YGDirection);
73 | WIN_EXPORT const char *YGDirectionToString(const YGDirection value);
74 |
75 | #define YGDisplayCount 2
76 | typedef YG_ENUM_BEGIN(YGDisplay){
77 | YGDisplayFlex,
78 | YGDisplayNone,
79 | } YG_ENUM_END(YGDisplay);
80 | WIN_EXPORT const char *YGDisplayToString(const YGDisplay value);
81 |
82 | #define YGEdgeCount 9
83 | typedef YG_ENUM_BEGIN(YGEdge){
84 | YGEdgeLeft, YGEdgeTop, YGEdgeRight, YGEdgeBottom, YGEdgeStart,
85 | YGEdgeEnd, YGEdgeHorizontal, YGEdgeVertical, YGEdgeAll,
86 | } YG_ENUM_END(YGEdge);
87 | WIN_EXPORT const char *YGEdgeToString(const YGEdge value);
88 |
89 | #define YGExperimentalFeatureCount 1
90 | typedef YG_ENUM_BEGIN(YGExperimentalFeature){
91 | YGExperimentalFeatureWebFlexBasis,
92 | } YG_ENUM_END(YGExperimentalFeature);
93 | WIN_EXPORT const char *YGExperimentalFeatureToString(const YGExperimentalFeature value);
94 |
95 | #define YGFlexDirectionCount 4
96 | typedef YG_ENUM_BEGIN(YGFlexDirection){
97 | YGFlexDirectionColumn,
98 | YGFlexDirectionColumnReverse,
99 | YGFlexDirectionRow,
100 | YGFlexDirectionRowReverse,
101 | } YG_ENUM_END(YGFlexDirection);
102 | WIN_EXPORT const char *YGFlexDirectionToString(const YGFlexDirection value);
103 |
104 | #define YGJustifyCount 5
105 | typedef YG_ENUM_BEGIN(YGJustify){
106 | YGJustifyFlexStart, YGJustifyCenter, YGJustifyFlexEnd,
107 | YGJustifySpaceBetween, YGJustifySpaceAround,
108 | } YG_ENUM_END(YGJustify);
109 | WIN_EXPORT const char *YGJustifyToString(const YGJustify value);
110 |
111 | #define YGLogLevelCount 6
112 | typedef YG_ENUM_BEGIN(YGLogLevel){
113 | YGLogLevelError, YGLogLevelWarn, YGLogLevelInfo,
114 | YGLogLevelDebug, YGLogLevelVerbose, YGLogLevelFatal,
115 | } YG_ENUM_END(YGLogLevel);
116 | WIN_EXPORT const char *YGLogLevelToString(const YGLogLevel value);
117 |
118 | #define YGMeasureModeCount 3
119 | typedef YG_ENUM_BEGIN(YGMeasureMode){
120 | YGMeasureModeUndefined,
121 | YGMeasureModeExactly,
122 | YGMeasureModeAtMost,
123 | } YG_ENUM_END(YGMeasureMode);
124 | WIN_EXPORT const char *YGMeasureModeToString(const YGMeasureMode value);
125 |
126 | #define YGNodeTypeCount 2
127 | typedef YG_ENUM_BEGIN(YGNodeType){
128 | YGNodeTypeDefault,
129 | YGNodeTypeText,
130 | } YG_ENUM_END(YGNodeType);
131 | WIN_EXPORT const char *YGNodeTypeToString(const YGNodeType value);
132 |
133 | #define YGOverflowCount 3
134 | typedef YG_ENUM_BEGIN(YGOverflow){
135 | YGOverflowVisible,
136 | YGOverflowHidden,
137 | YGOverflowScroll,
138 | } YG_ENUM_END(YGOverflow);
139 | WIN_EXPORT const char *YGOverflowToString(const YGOverflow value);
140 |
141 | #define YGPositionTypeCount 2
142 | typedef YG_ENUM_BEGIN(YGPositionType){
143 | YGPositionTypeRelative,
144 | YGPositionTypeAbsolute,
145 | } YG_ENUM_END(YGPositionType);
146 | WIN_EXPORT const char *YGPositionTypeToString(const YGPositionType value);
147 |
148 | #define YGPrintOptionsCount 3
149 | typedef YG_ENUM_BEGIN(YGPrintOptions){
150 | YGPrintOptionsLayout = 1,
151 | YGPrintOptionsStyle = 2,
152 | YGPrintOptionsChildren = 4,
153 | } YG_ENUM_END(YGPrintOptions);
154 | WIN_EXPORT const char *YGPrintOptionsToString(const YGPrintOptions value);
155 |
156 | #define YGUnitCount 4
157 | typedef YG_ENUM_BEGIN(YGUnit){
158 | YGUnitUndefined,
159 | YGUnitPoint,
160 | YGUnitPercent,
161 | YGUnitAuto,
162 | } YG_ENUM_END(YGUnit);
163 | WIN_EXPORT const char *YGUnitToString(const YGUnit value);
164 |
165 | #define YGWrapCount 3
166 | typedef YG_ENUM_BEGIN(YGWrap){
167 | YGWrapNoWrap,
168 | YGWrapWrap,
169 | YGWrapWrapReverse,
170 | } YG_ENUM_END(YGWrap);
171 | WIN_EXPORT const char *YGWrapToString(const YGWrap value);
172 |
173 | YG_EXTERN_C_END
174 |
175 | #pragma once
176 |
177 | #include
178 | #include
179 | #include
180 | #include
181 | #include
182 | #include
183 |
184 | #ifndef __cplusplus
185 | #include
186 | #endif
187 |
188 | // Not defined in MSVC++
189 | #ifndef NAN
190 | static const unsigned long __nan[2] = {0xffffffff, 0x7fffffff};
191 | #define NAN (*(const float *)__nan)
192 | #endif
193 |
194 | #define YGUndefined NAN
195 |
196 | YG_EXTERN_C_BEGIN
197 |
198 | typedef struct YGSize {
199 | float width;
200 | float height;
201 | } YGSize;
202 |
203 | typedef struct YGValue {
204 | float value;
205 | YGUnit unit;
206 | } YGValue;
207 |
208 | typedef struct __attribute__((objc_boxable)) YGValue YGValue;
209 |
210 | static const YGValue YGValueUndefined = {YGUndefined, YGUnitUndefined};
211 | static const YGValue YGValueAuto = {YGUndefined, YGUnitAuto};
212 |
213 | typedef struct YGConfig *YGConfigRef;
214 | typedef struct YGNode *YGNodeRef;
215 | typedef YGSize (*YGMeasureFunc)(YGNodeRef node, float width, YGMeasureMode widthMode, float height,
216 | YGMeasureMode heightMode);
217 | typedef float (*YGBaselineFunc)(YGNodeRef node, const float width, const float height);
218 | typedef void (*YGPrintFunc)(YGNodeRef node);
219 | typedef int (*YGLogger)(const YGConfigRef config, const YGNodeRef node, YGLogLevel level,
220 | const char *format, va_list args);
221 | typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode, YGNodeRef newNode, YGNodeRef parent,
222 | int childIndex);
223 |
224 | typedef void *(*YGMalloc)(size_t size);
225 | typedef void *(*YGCalloc)(size_t count, size_t size);
226 | typedef void *(*YGRealloc)(void *ptr, size_t size);
227 | typedef void (*YGFree)(void *ptr);
228 |
229 | // YGNode
230 | WIN_EXPORT YGNodeRef YGNodeNew(void);
231 | WIN_EXPORT YGNodeRef YGNodeNewWithConfig(const YGConfigRef config);
232 | WIN_EXPORT YGNodeRef YGNodeClone(const YGNodeRef node);
233 | WIN_EXPORT void YGNodeFree(const YGNodeRef node);
234 | WIN_EXPORT void YGNodeFreeRecursive(const YGNodeRef node);
235 | WIN_EXPORT void YGNodeReset(const YGNodeRef node);
236 | WIN_EXPORT int32_t YGNodeGetInstanceCount(void);
237 |
238 | WIN_EXPORT void YGNodeInsertChild(const YGNodeRef node, const YGNodeRef child,
239 | const uint32_t index);
240 | WIN_EXPORT void YGNodeRemoveChild(const YGNodeRef node, const YGNodeRef child);
241 | WIN_EXPORT void YGNodeRemoveAllChildren(const YGNodeRef node);
242 | WIN_EXPORT YGNodeRef YGNodeGetChild(const YGNodeRef node, const uint32_t index);
243 | WIN_EXPORT YGNodeRef YGNodeGetParent(const YGNodeRef node);
244 | WIN_EXPORT uint32_t YGNodeGetChildCount(const YGNodeRef node);
245 |
246 | WIN_EXPORT void YGNodeCalculateLayout(const YGNodeRef node, const float availableWidth,
247 | const float availableHeight,
248 | const YGDirection parentDirection);
249 |
250 | // Mark a node as dirty. Only valid for nodes with a custom measure function
251 | // set.
252 | // YG knows when to mark all other nodes as dirty but because nodes with
253 | // measure functions
254 | // depends on information not known to YG they must perform this dirty
255 | // marking manually.
256 | WIN_EXPORT void YGNodeMarkDirty(const YGNodeRef node);
257 | WIN_EXPORT bool YGNodeIsDirty(const YGNodeRef node);
258 |
259 | WIN_EXPORT void YGNodePrint(const YGNodeRef node, const YGPrintOptions options);
260 |
261 | WIN_EXPORT bool YGFloatIsUndefined(const float value);
262 |
263 | WIN_EXPORT bool YGNodeCanUseCachedMeasurement(const YGMeasureMode widthMode, const float width,
264 | const YGMeasureMode heightMode, const float height,
265 | const YGMeasureMode lastWidthMode,
266 | const float lastWidth,
267 | const YGMeasureMode lastHeightMode,
268 | const float lastHeight, const float lastComputedWidth,
269 | const float lastComputedHeight, const float marginRow,
270 | const float marginColumn, const YGConfigRef config);
271 |
272 | WIN_EXPORT void YGNodeCopyStyle(const YGNodeRef dstNode, const YGNodeRef srcNode);
273 |
274 | #define YG_NODE_PROPERTY(type, name, paramName) \
275 | WIN_EXPORT void YGNodeSet##name(const YGNodeRef node, type paramName); \
276 | WIN_EXPORT type YGNodeGet##name(const YGNodeRef node);
277 |
278 | #define YG_NODE_STYLE_PROPERTY(type, name, paramName) \
279 | WIN_EXPORT void YGNodeStyleSet##name(const YGNodeRef node, const type paramName); \
280 | WIN_EXPORT type YGNodeStyleGet##name(const YGNodeRef node);
281 |
282 | #define YG_NODE_STYLE_PROPERTY_UNIT(type, name, paramName) \
283 | WIN_EXPORT void YGNodeStyleSet##name(const YGNodeRef node, const float paramName); \
284 | WIN_EXPORT void YGNodeStyleSet##name##Percent(const YGNodeRef node, const float paramName); \
285 | WIN_EXPORT type YGNodeStyleGet##name(const YGNodeRef node);
286 |
287 | #define YG_NODE_STYLE_PROPERTY_UNIT_AUTO(type, name, paramName) \
288 | YG_NODE_STYLE_PROPERTY_UNIT(type, name, paramName) \
289 | WIN_EXPORT void YGNodeStyleSet##name##Auto(const YGNodeRef node);
290 |
291 | #define YG_NODE_STYLE_EDGE_PROPERTY(type, name, paramName) \
292 | WIN_EXPORT void YGNodeStyleSet##name(const YGNodeRef node, const YGEdge edge, \
293 | const type paramName); \
294 | WIN_EXPORT type YGNodeStyleGet##name(const YGNodeRef node, const YGEdge edge);
295 |
296 | #define YG_NODE_STYLE_EDGE_PROPERTY_UNIT(type, name, paramName) \
297 | WIN_EXPORT void YGNodeStyleSet##name(const YGNodeRef node, const YGEdge edge, \
298 | const float paramName); \
299 | WIN_EXPORT void YGNodeStyleSet##name##Percent(const YGNodeRef node, const YGEdge edge, \
300 | const float paramName); \
301 | WIN_EXPORT WIN_STRUCT(type) YGNodeStyleGet##name(const YGNodeRef node, const YGEdge edge);
302 |
303 | #define YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO(type, name) \
304 | WIN_EXPORT void YGNodeStyleSet##name##Auto(const YGNodeRef node, const YGEdge edge);
305 |
306 | #define YG_NODE_LAYOUT_PROPERTY(type, name) \
307 | WIN_EXPORT type YGNodeLayoutGet##name(const YGNodeRef node);
308 |
309 | #define YG_NODE_LAYOUT_EDGE_PROPERTY(type, name) \
310 | WIN_EXPORT type YGNodeLayoutGet##name(const YGNodeRef node, const YGEdge edge);
311 |
312 | YG_NODE_PROPERTY(void *, Context, context);
313 | YG_NODE_PROPERTY(YGMeasureFunc, MeasureFunc, measureFunc);
314 | YG_NODE_PROPERTY(YGBaselineFunc, BaselineFunc, baselineFunc)
315 | YG_NODE_PROPERTY(YGPrintFunc, PrintFunc, printFunc);
316 | YG_NODE_PROPERTY(bool, HasNewLayout, hasNewLayout);
317 | YG_NODE_PROPERTY(YGNodeType, NodeType, nodeType);
318 |
319 | YG_NODE_STYLE_PROPERTY(YGDirection, Direction, direction);
320 | YG_NODE_STYLE_PROPERTY(YGFlexDirection, FlexDirection, flexDirection);
321 | YG_NODE_STYLE_PROPERTY(YGJustify, JustifyContent, justifyContent);
322 | YG_NODE_STYLE_PROPERTY(YGAlign, AlignContent, alignContent);
323 | YG_NODE_STYLE_PROPERTY(YGAlign, AlignItems, alignItems);
324 | YG_NODE_STYLE_PROPERTY(YGAlign, AlignSelf, alignSelf);
325 | YG_NODE_STYLE_PROPERTY(YGPositionType, PositionType, positionType);
326 | YG_NODE_STYLE_PROPERTY(YGWrap, FlexWrap, flexWrap);
327 | YG_NODE_STYLE_PROPERTY(YGOverflow, Overflow, overflow);
328 | YG_NODE_STYLE_PROPERTY(YGDisplay, Display, display);
329 |
330 | YG_NODE_STYLE_PROPERTY(float, Flex, flex);
331 | YG_NODE_STYLE_PROPERTY(float, FlexGrow, flexGrow);
332 | YG_NODE_STYLE_PROPERTY(float, FlexShrink, flexShrink);
333 | YG_NODE_STYLE_PROPERTY_UNIT_AUTO(YGValue, FlexBasis, flexBasis);
334 |
335 | YG_NODE_STYLE_EDGE_PROPERTY_UNIT(YGValue, Position, position);
336 | YG_NODE_STYLE_EDGE_PROPERTY_UNIT(YGValue, Margin, margin);
337 | YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO(YGValue, Margin);
338 | YG_NODE_STYLE_EDGE_PROPERTY_UNIT(YGValue, Padding, padding);
339 | YG_NODE_STYLE_EDGE_PROPERTY(float, Border, border);
340 |
341 | YG_NODE_STYLE_PROPERTY_UNIT_AUTO(YGValue, Width, width);
342 | YG_NODE_STYLE_PROPERTY_UNIT_AUTO(YGValue, Height, height);
343 | YG_NODE_STYLE_PROPERTY_UNIT(YGValue, MinWidth, minWidth);
344 | YG_NODE_STYLE_PROPERTY_UNIT(YGValue, MinHeight, minHeight);
345 | YG_NODE_STYLE_PROPERTY_UNIT(YGValue, MaxWidth, maxWidth);
346 | YG_NODE_STYLE_PROPERTY_UNIT(YGValue, MaxHeight, maxHeight);
347 |
348 | // Yoga specific properties, not compatible with flexbox specification
349 | // Aspect ratio control the size of the undefined dimension of a node.
350 | // Aspect ratio is encoded as a floating point value width/height. e.g. A value of 2 leads to a node
351 | // with a width twice the size of its height while a value of 0.5 gives the opposite effect.
352 | //
353 | // - On a node with a set width/height aspect ratio control the size of the unset dimension
354 | // - On a node with a set flex basis aspect ratio controls the size of the node in the cross axis if
355 | // unset
356 | // - On a node with a measure function aspect ratio works as though the measure function measures
357 | // the flex basis
358 | // - On a node with flex grow/shrink aspect ratio controls the size of the node in the cross axis if
359 | // unset
360 | // - Aspect ratio takes min/max dimensions into account
361 | YG_NODE_STYLE_PROPERTY(float, AspectRatio, aspectRatio);
362 |
363 | YG_NODE_LAYOUT_PROPERTY(float, Left);
364 | YG_NODE_LAYOUT_PROPERTY(float, Top);
365 | YG_NODE_LAYOUT_PROPERTY(float, Right);
366 | YG_NODE_LAYOUT_PROPERTY(float, Bottom);
367 | YG_NODE_LAYOUT_PROPERTY(float, Width);
368 | YG_NODE_LAYOUT_PROPERTY(float, Height);
369 | YG_NODE_LAYOUT_PROPERTY(YGDirection, Direction);
370 | YG_NODE_LAYOUT_PROPERTY(bool, HadOverflow);
371 |
372 | // Get the computed values for these nodes after performing layout. If they were set using
373 | // point values then the returned value will be the same as YGNodeStyleGetXXX. However if
374 | // they were set using a percentage value then the returned value is the computed value used
375 | // during layout.
376 | YG_NODE_LAYOUT_EDGE_PROPERTY(float, Margin);
377 | YG_NODE_LAYOUT_EDGE_PROPERTY(float, Border);
378 | YG_NODE_LAYOUT_EDGE_PROPERTY(float, Padding);
379 |
380 | WIN_EXPORT void YGConfigSetLogger(const YGConfigRef config, YGLogger logger);
381 | WIN_EXPORT void YGLog(const YGNodeRef node, YGLogLevel level, const char *message, ...);
382 | WIN_EXPORT void YGLogWithConfig(const YGConfigRef config, YGLogLevel level, const char *format,
383 | ...);
384 | WIN_EXPORT void YGAssert(const bool condition, const char *message);
385 | WIN_EXPORT void YGAssertWithNode(const YGNodeRef node, const bool condition, const char *message);
386 | WIN_EXPORT void YGAssertWithConfig(const YGConfigRef config, const bool condition,
387 | const char *message);
388 |
389 | // Set this to number of pixels in 1 point to round calculation results
390 | // If you want to avoid rounding - set PointScaleFactor to 0
391 | WIN_EXPORT void YGConfigSetPointScaleFactor(const YGConfigRef config, const float pixelsInPoint);
392 |
393 | // Yoga previously had an error where containers would take the maximum space possible instead of
394 | // the minimum
395 | // like they are supposed to. In practice this resulted in implicit behaviour similar to align-self:
396 | // stretch;
397 | // Because this was such a long-standing bug we must allow legacy users to switch back to this
398 | // behaviour.
399 | WIN_EXPORT void YGConfigSetUseLegacyStretchBehaviour(const YGConfigRef config,
400 | const bool useLegacyStretchBehaviour);
401 |
402 | // YGConfig
403 | WIN_EXPORT YGConfigRef YGConfigNew(void);
404 | WIN_EXPORT void YGConfigFree(const YGConfigRef config);
405 | WIN_EXPORT void YGConfigCopy(const YGConfigRef dest, const YGConfigRef src);
406 | WIN_EXPORT int32_t YGConfigGetInstanceCount(void);
407 |
408 | WIN_EXPORT void YGConfigSetExperimentalFeatureEnabled(const YGConfigRef config,
409 | const YGExperimentalFeature feature,
410 | const bool enabled);
411 | WIN_EXPORT bool YGConfigIsExperimentalFeatureEnabled(const YGConfigRef config,
412 | const YGExperimentalFeature feature);
413 |
414 | // Using the web defaults is the prefered configuration for new projects.
415 | // Usage of non web defaults should be considered as legacy.
416 | WIN_EXPORT void YGConfigSetUseWebDefaults(const YGConfigRef config, const bool enabled);
417 | WIN_EXPORT bool YGConfigGetUseWebDefaults(const YGConfigRef config);
418 |
419 | WIN_EXPORT void YGConfigSetNodeClonedFunc(const YGConfigRef config,
420 | const YGNodeClonedFunc callback);
421 |
422 | // Export only for C#
423 | WIN_EXPORT YGConfigRef YGConfigGetDefault(void);
424 |
425 | WIN_EXPORT void YGConfigSetContext(const YGConfigRef config, void *context);
426 | WIN_EXPORT void *YGConfigGetContext(const YGConfigRef config);
427 |
428 | WIN_EXPORT void YGSetMemoryFuncs(YGMalloc ygmalloc, YGCalloc yccalloc, YGRealloc ygrealloc,
429 | YGFree ygfree);
430 |
431 | WIN_EXPORT float YGRoundValueToPixelGrid(const float value, const float pointScaleFactor,
432 | const bool forceCeil, const bool forceFloor);
433 |
434 | YG_EXTERN_C_END
435 |
436 | #pragma once
437 |
438 | #include
439 | #include
440 | #include
441 | #include
442 |
443 | #include "Yoga.h"
444 |
445 | YG_EXTERN_C_BEGIN
446 |
447 | typedef struct YGNodeList *YGNodeListRef;
448 |
449 | YGNodeListRef YGNodeListNew(const uint32_t initialCapacity);
450 | void YGNodeListFree(const YGNodeListRef list);
451 | uint32_t YGNodeListCount(const YGNodeListRef list);
452 | void YGNodeListAdd(YGNodeListRef *listp, const YGNodeRef node);
453 | void YGNodeListInsert(YGNodeListRef *listp, const YGNodeRef node, const uint32_t index);
454 | void YGNodeListReplace(const YGNodeListRef list, const uint32_t index, const YGNodeRef newNode);
455 | void YGNodeListRemoveAll(const YGNodeListRef list);
456 | YGNodeRef YGNodeListRemove(const YGNodeListRef list, const uint32_t index);
457 | YGNodeRef YGNodeListDelete(const YGNodeListRef list, const YGNodeRef node);
458 | YGNodeRef YGNodeListGet(const YGNodeListRef list, const uint32_t index);
459 | YGNodeListRef YGNodeListClone(YGNodeListRef list);
460 |
461 | YG_EXTERN_C_END
462 |
--------------------------------------------------------------------------------
/Sources/CoreRenderObjC/include/CoreRenderObjC.h:
--------------------------------------------------------------------------------
1 | #import "../CRContext.h"
2 | #import "../CRCoordinator.h"
3 | #import "../CRHostingView.h"
4 | #import "../CRMacros.h"
5 | #import "../CRNode.h"
6 | #import "../CRNodeBridge.h"
7 | #import "../CRNodeBuilder.h"
8 | #import "../CRNodeBuilder+Modifiers.h"
9 | #import "../CRNodeHierarchy.h"
10 | #import "../CRNodeLayoutSpec.h"
11 | #import "../UIView+CRNode.h"
12 | #import "../YGLayout.h"
13 | #import "../Yoga.h"
14 | #import
15 |
16 | FOUNDATION_EXPORT double CoreRenderObjCVersionNumber;
17 | FOUNDATION_EXPORT const unsigned char CoreRenderObjCVersionString[];
18 |
--------------------------------------------------------------------------------
/Tests/CoreRenderObjCTests/CRNodeTests.m:
--------------------------------------------------------------------------------
1 | @import CoreRenderObjC;
2 | @import XCTest;
3 |
4 | @interface CRNodeTests : XCTestCase
5 | @property(nonatomic, weak) UILabel *testOutlet;
6 | @end
7 |
8 | @interface TestCoordinator : CRCoordinator
9 | @end
10 |
11 | @interface TestStatelessCoordinator : CRCoordinator
12 | @end
13 |
14 | @implementation CRNodeTests
15 |
16 | - (CRNode *)buildLabelNode {
17 | const auto node = [CRNode nodeWithType:UILabel.class
18 | layoutSpec:^(CRNodeLayoutSpec *spec) {
19 | [spec set:CR_KEYPATH(spec.view, text) value:@"test"];
20 | [spec set:CR_KEYPATH(spec.view, textColor) value:UIColor.redColor];
21 | }];
22 | return node;
23 | }
24 |
25 | - (void)assertLabelDidLayout:(UIView *)view {
26 | const auto label = CR_DYNAMIC_CAST(UILabel, view);
27 | XCTAssertNotNil(label);
28 | XCTAssert([label.text isEqualToString:@"test"]);
29 | XCTAssert([label.textColor isEqual:UIColor.redColor]);
30 | const auto rect = label.frame;
31 | XCTAssert(!CGRectEqualToRect(rect, CGRectZero));
32 | XCTAssert(!CGRectEqualToRect(rect, CGRectZero));
33 | }
34 |
35 | - (void)testTrivialLayout {
36 | const auto node = [self buildLabelNode];
37 | const auto view = [[UIView alloc] init];
38 | [node reconcileInView:view
39 | constrainedToSize:CGSizeMake(320, CR_CGFLOAT_FLEXIBLE)
40 | withOptions:CRNodeLayoutOptionsSizeContainerViewToFit];
41 | XCTAssert(view.subviews.count == 1);
42 | [self assertLabelDidLayout:view.subviews.firstObject];
43 | }
44 |
45 | - (void)testTrivialLayoutAnimated {
46 | const auto node = [self buildLabelNode];
47 | const auto view = [[UIView alloc] init];
48 | const auto context = [[CRContext alloc] init];
49 | context.layoutAnimator = [[UIViewPropertyAnimator alloc] init];
50 | [node reconcileInView:view
51 | constrainedToSize:CGSizeMake(320, CR_CGFLOAT_FLEXIBLE)
52 | withOptions:CRNodeLayoutOptionsSizeContainerViewToFit];
53 | [node registerNodeHierarchyInContext:context];
54 | XCTAssert(view.subviews.count == 1);
55 | [self assertLabelDidLayout:view.subviews.firstObject];
56 | }
57 |
58 | - (void)testNestedLayout {
59 | const auto node = [CRNode nodeWithType:UIView.self
60 | layoutSpec:^(CRNodeLayoutSpec *spec) {
61 | [spec set:CR_KEYPATH(spec.view, backgroundColor)
62 | value:UIColor.redColor];
63 | [spec set:CR_KEYPATH(spec.view, yoga.padding) value:@42];
64 | }];
65 | [node appendChildren:@[ [self buildLabelNode], [self buildLabelNode], [self buildLabelNode] ]];
66 | const auto containerView = [[UIView alloc] init];
67 | const auto context = [[CRContext alloc] init];
68 | [node registerNodeHierarchyInContext:context];
69 |
70 | [node reconcileInView:containerView
71 | constrainedToSize:CGSizeMake(320, CR_CGFLOAT_FLEXIBLE)
72 | withOptions:CRNodeLayoutOptionsSizeContainerViewToFit];
73 |
74 | const auto view = containerView.subviews.firstObject;
75 | const auto sv = view.subviews;
76 |
77 | XCTAssertNotNil(view);
78 | [self assertLabelDidLayout:sv[0]];
79 | [self assertLabelDidLayout:sv[1]];
80 | [self assertLabelDidLayout:sv[2]];
81 |
82 | XCTAssert(fabs(CGRectGetMaxY(sv[0].frame) - sv[1].frame.origin.y) <= 1.0);
83 | XCTAssert(fabs(CGRectGetMaxY(sv[1].frame) - sv[2].frame.origin.y) <= 1.0);
84 | }
85 |
86 | - (void)testThatCoordinatorIsPassedDownToNodeSubtree {
87 | __block auto expectRootNodeHasCoordinator = NO;
88 | __block auto expectLeafNodeHasCoordinator = NO;
89 | const auto root =
90 | [CRNode nodeWithType:UIView.class
91 | reuseIdentifier:nil
92 | key:@"foo"
93 | viewInit:nil
94 | layoutSpec:^(CRNodeLayoutSpec *spec) {
95 | const auto coordinator = [spec coordinatorOfType:TestCoordinator.class];
96 | expectRootNodeHasCoordinator = CR_DYNAMIC_CAST(TestCoordinator, coordinator);
97 | }];
98 | [root bindCoordinator:self.testDescriptor];
99 |
100 | const auto leaf =
101 | [CRNode nodeWithType:UIView.class
102 | layoutSpec:^(CRNodeLayoutSpec *spec) {
103 | const auto coordinator = [spec coordinatorOfType:TestCoordinator.class];
104 | expectLeafNodeHasCoordinator = CR_DYNAMIC_CAST(TestCoordinator, coordinator);
105 | }];
106 | [root appendChildren:@[ leaf ]];
107 |
108 | const auto context = [[CRContext alloc] init];
109 | [root registerNodeHierarchyInContext:context];
110 |
111 | const auto view = [[UIView alloc] init];
112 | [root reconcileInView:view
113 | constrainedToSize:CGSizeMake(320, CR_CGFLOAT_FLEXIBLE)
114 | withOptions:CRNodeLayoutOptionsSizeContainerViewToFit];
115 | XCTAssertTrue(expectRootNodeHasCoordinator);
116 | XCTAssertTrue(expectLeafNodeHasCoordinator);
117 | }
118 |
119 | - (CRCoordinatorDescriptor *)testDescriptor {
120 | return [[CRCoordinatorDescriptor alloc] initWithType:TestCoordinator.class key:@"test"];
121 | }
122 |
123 | @end
124 |
125 | @implementation TestCoordinator
126 | @end
127 |
128 | @implementation TestStatelessCoordinator
129 | @end
130 |
--------------------------------------------------------------------------------
/Tests/CoreRenderTests/BridgeTest.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import CoreRenderObjC
3 | import CoreRender
4 |
5 | class FooCoordinator: Coordinator {
6 | var count: Int = 0
7 | func increase() { count += 1 }
8 | }
9 |
10 | class BarCoordinator: Coordinator {
11 | }
12 |
13 | class CRSwiftInteropTests: XCTestCase {
14 |
15 | func testGetCoordinator() {
16 | let context = Context()
17 |
18 | Component(context: context) { _, _ in
19 | VStackNode {
20 | LabelNode(text: "Foor")
21 | LabelNode(text: "Bar")
22 | Component(context: context) { _, _ in
23 | ButtonNode(key: "Hi")
24 | }
25 | }
26 | }
27 | }
28 |
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/Tests/CoreRenderTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [ ]
6 | }
7 | #endif
8 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import CoreRenderTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += CoreRenderTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/docs/assets/carbon_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexdrone/CoreRender/96736a47fbaf771f0d79a24a5e8e45cfac477aff/docs/assets/carbon_4.png
--------------------------------------------------------------------------------
/docs/assets/logo_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexdrone/CoreRender/96736a47fbaf771f0d79a24a5e8e45cfac477aff/docs/assets/logo_new.png
--------------------------------------------------------------------------------
/docs/assets/screen_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexdrone/CoreRender/96736a47fbaf771f0d79a24a5e8e45cfac477aff/docs/assets/screen_2.png
--------------------------------------------------------------------------------
/format_objc.sh:
--------------------------------------------------------------------------------
1 |
2 | #!/bin/bash
3 | format() {
4 | PATH=$(pwd);
5 | echo "✓ $PATH";
6 | /usr/local/bin/clang-format -i *.h &>/dev/null
7 | /usr/local/bin/clang-format -i *.m &>/dev/null
8 | /usr/local/bin/clang-format -i *.mm &>/dev/null
9 | /usr/local/bin/clang-format -i *.c &>/dev/null
10 | }
11 |
12 | echo "Running clang-format..."
13 | cd Sources/CoreRenderObjC && format;
14 | cd ../../;
15 | cd Tests/CoreRenderObjCTests && format;
16 |
--------------------------------------------------------------------------------