├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── CocoaUI.xcscheme
├── CocoaUI.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Example
├── Example.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Example.xcscheme
├── Example
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ContentView.swift
│ ├── Example.entitlements
│ ├── ExampleApp.swift
│ └── Preview Content
│ │ └── Preview Assets.xcassets
│ │ └── Contents.json
├── ExampleTests
│ └── ExampleTests.swift
├── ExampleUITests
│ ├── ExampleUITests.swift
│ └── ExampleUITestsLaunchTests.swift
└── Package.swift
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── CocoaUI
│ ├── CocoaBridgeView.swift
│ ├── CocoaBridging.swift
│ ├── CocoaUI.swift
│ ├── Extension
│ └── View+.swift
│ ├── OverlayHostingController.swift
│ ├── OverlayView.swift
│ └── Util
│ └── typealias.swift
└── Tests
└── CocoaUITests
└── CocoaUITests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
11 | ## Build generated
12 | build/
13 | DerivedData/
14 |
15 | ## Various settings
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata/
25 | node_modules/
26 |
27 | ## Other
28 | *.moved-aside
29 | *.xccheckout
30 | *.xcscmblueprint
31 | *.DS_Store
32 |
33 | ## Obj-C/Swift specific
34 | *.hmap
35 | *.ipa
36 | *.dSYM.zip
37 | *.dSYM
38 | *.generated.swift
39 |
40 |
41 | Carthage/
42 | Pods/
43 |
44 | # CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 |
51 | # bundler
52 | vendor/bundle
53 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/CocoaUI.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 |
--------------------------------------------------------------------------------
/CocoaUI.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/CocoaUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 429EDC6D29D742F000D75C2E /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429EDC6C29D742F000D75C2E /* ExampleApp.swift */; };
11 | 429EDC6F29D742F000D75C2E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429EDC6E29D742F000D75C2E /* ContentView.swift */; };
12 | 429EDC7129D742F300D75C2E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 429EDC7029D742F300D75C2E /* Assets.xcassets */; };
13 | 429EDC7529D742F300D75C2E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 429EDC7429D742F300D75C2E /* Preview Assets.xcassets */; };
14 | 429EDC7F29D742F300D75C2E /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429EDC7E29D742F300D75C2E /* ExampleTests.swift */; };
15 | 429EDC8929D742F300D75C2E /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429EDC8829D742F300D75C2E /* ExampleUITests.swift */; };
16 | 429EDC8B29D742F300D75C2E /* ExampleUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429EDC8A29D742F300D75C2E /* ExampleUITestsLaunchTests.swift */; };
17 | 429EDC9B29D7431800D75C2E /* CocoaUI in Frameworks */ = {isa = PBXBuildFile; productRef = 429EDC9A29D7431800D75C2E /* CocoaUI */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXContainerItemProxy section */
21 | 429EDC7B29D742F300D75C2E /* PBXContainerItemProxy */ = {
22 | isa = PBXContainerItemProxy;
23 | containerPortal = 429EDC6129D742F000D75C2E /* Project object */;
24 | proxyType = 1;
25 | remoteGlobalIDString = 429EDC6829D742F000D75C2E;
26 | remoteInfo = Example;
27 | };
28 | 429EDC8529D742F300D75C2E /* PBXContainerItemProxy */ = {
29 | isa = PBXContainerItemProxy;
30 | containerPortal = 429EDC6129D742F000D75C2E /* Project object */;
31 | proxyType = 1;
32 | remoteGlobalIDString = 429EDC6829D742F000D75C2E;
33 | remoteInfo = Example;
34 | };
35 | /* End PBXContainerItemProxy section */
36 |
37 | /* Begin PBXFileReference section */
38 | 429EDC6929D742F000D75C2E /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 429EDC6C29D742F000D75C2E /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; };
40 | 429EDC6E29D742F000D75C2E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
41 | 429EDC7029D742F300D75C2E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
42 | 429EDC7229D742F300D75C2E /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; };
43 | 429EDC7429D742F300D75C2E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
44 | 429EDC7A29D742F300D75C2E /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 429EDC7E29D742F300D75C2E /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; };
46 | 429EDC8429D742F300D75C2E /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 429EDC8829D742F300D75C2E /* ExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITests.swift; sourceTree = ""; };
48 | 429EDC8A29D742F300D75C2E /* ExampleUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITestsLaunchTests.swift; sourceTree = ""; };
49 | 429EDC9829D7430B00D75C2E /* CocoaUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CocoaUI; path = ..; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | 429EDC6629D742F000D75C2E /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | 429EDC9B29D7431800D75C2E /* CocoaUI in Frameworks */,
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | 429EDC7729D742F300D75C2E /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | );
66 | runOnlyForDeploymentPostprocessing = 0;
67 | };
68 | 429EDC8129D742F300D75C2E /* Frameworks */ = {
69 | isa = PBXFrameworksBuildPhase;
70 | buildActionMask = 2147483647;
71 | files = (
72 | );
73 | runOnlyForDeploymentPostprocessing = 0;
74 | };
75 | /* End PBXFrameworksBuildPhase section */
76 |
77 | /* Begin PBXGroup section */
78 | 429EDC6029D742F000D75C2E = {
79 | isa = PBXGroup;
80 | children = (
81 | 429EDC9729D7430B00D75C2E /* Packages */,
82 | 429EDC6B29D742F000D75C2E /* Example */,
83 | 429EDC7D29D742F300D75C2E /* ExampleTests */,
84 | 429EDC8729D742F300D75C2E /* ExampleUITests */,
85 | 429EDC6A29D742F000D75C2E /* Products */,
86 | 429EDC9929D7431800D75C2E /* Frameworks */,
87 | );
88 | sourceTree = "";
89 | };
90 | 429EDC6A29D742F000D75C2E /* Products */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 429EDC6929D742F000D75C2E /* Example.app */,
94 | 429EDC7A29D742F300D75C2E /* ExampleTests.xctest */,
95 | 429EDC8429D742F300D75C2E /* ExampleUITests.xctest */,
96 | );
97 | name = Products;
98 | sourceTree = "";
99 | };
100 | 429EDC6B29D742F000D75C2E /* Example */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 429EDC6C29D742F000D75C2E /* ExampleApp.swift */,
104 | 429EDC6E29D742F000D75C2E /* ContentView.swift */,
105 | 429EDC7029D742F300D75C2E /* Assets.xcassets */,
106 | 429EDC7229D742F300D75C2E /* Example.entitlements */,
107 | 429EDC7329D742F300D75C2E /* Preview Content */,
108 | );
109 | path = Example;
110 | sourceTree = "";
111 | };
112 | 429EDC7329D742F300D75C2E /* Preview Content */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 429EDC7429D742F300D75C2E /* Preview Assets.xcassets */,
116 | );
117 | path = "Preview Content";
118 | sourceTree = "";
119 | };
120 | 429EDC7D29D742F300D75C2E /* ExampleTests */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 429EDC7E29D742F300D75C2E /* ExampleTests.swift */,
124 | );
125 | path = ExampleTests;
126 | sourceTree = "";
127 | };
128 | 429EDC8729D742F300D75C2E /* ExampleUITests */ = {
129 | isa = PBXGroup;
130 | children = (
131 | 429EDC8829D742F300D75C2E /* ExampleUITests.swift */,
132 | 429EDC8A29D742F300D75C2E /* ExampleUITestsLaunchTests.swift */,
133 | );
134 | path = ExampleUITests;
135 | sourceTree = "";
136 | };
137 | 429EDC9729D7430B00D75C2E /* Packages */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 429EDC9829D7430B00D75C2E /* CocoaUI */,
141 | );
142 | name = Packages;
143 | sourceTree = "";
144 | };
145 | 429EDC9929D7431800D75C2E /* Frameworks */ = {
146 | isa = PBXGroup;
147 | children = (
148 | );
149 | name = Frameworks;
150 | sourceTree = "";
151 | };
152 | /* End PBXGroup section */
153 |
154 | /* Begin PBXNativeTarget section */
155 | 429EDC6829D742F000D75C2E /* Example */ = {
156 | isa = PBXNativeTarget;
157 | buildConfigurationList = 429EDC8E29D742F300D75C2E /* Build configuration list for PBXNativeTarget "Example" */;
158 | buildPhases = (
159 | 429EDC6529D742F000D75C2E /* Sources */,
160 | 429EDC6629D742F000D75C2E /* Frameworks */,
161 | 429EDC6729D742F000D75C2E /* Resources */,
162 | );
163 | buildRules = (
164 | );
165 | dependencies = (
166 | );
167 | name = Example;
168 | packageProductDependencies = (
169 | 429EDC9A29D7431800D75C2E /* CocoaUI */,
170 | );
171 | productName = Example;
172 | productReference = 429EDC6929D742F000D75C2E /* Example.app */;
173 | productType = "com.apple.product-type.application";
174 | };
175 | 429EDC7929D742F300D75C2E /* ExampleTests */ = {
176 | isa = PBXNativeTarget;
177 | buildConfigurationList = 429EDC9129D742F300D75C2E /* Build configuration list for PBXNativeTarget "ExampleTests" */;
178 | buildPhases = (
179 | 429EDC7629D742F300D75C2E /* Sources */,
180 | 429EDC7729D742F300D75C2E /* Frameworks */,
181 | 429EDC7829D742F300D75C2E /* Resources */,
182 | );
183 | buildRules = (
184 | );
185 | dependencies = (
186 | 429EDC7C29D742F300D75C2E /* PBXTargetDependency */,
187 | );
188 | name = ExampleTests;
189 | productName = ExampleTests;
190 | productReference = 429EDC7A29D742F300D75C2E /* ExampleTests.xctest */;
191 | productType = "com.apple.product-type.bundle.unit-test";
192 | };
193 | 429EDC8329D742F300D75C2E /* ExampleUITests */ = {
194 | isa = PBXNativeTarget;
195 | buildConfigurationList = 429EDC9429D742F300D75C2E /* Build configuration list for PBXNativeTarget "ExampleUITests" */;
196 | buildPhases = (
197 | 429EDC8029D742F300D75C2E /* Sources */,
198 | 429EDC8129D742F300D75C2E /* Frameworks */,
199 | 429EDC8229D742F300D75C2E /* Resources */,
200 | );
201 | buildRules = (
202 | );
203 | dependencies = (
204 | 429EDC8629D742F300D75C2E /* PBXTargetDependency */,
205 | );
206 | name = ExampleUITests;
207 | productName = ExampleUITests;
208 | productReference = 429EDC8429D742F300D75C2E /* ExampleUITests.xctest */;
209 | productType = "com.apple.product-type.bundle.ui-testing";
210 | };
211 | /* End PBXNativeTarget section */
212 |
213 | /* Begin PBXProject section */
214 | 429EDC6129D742F000D75C2E /* Project object */ = {
215 | isa = PBXProject;
216 | attributes = {
217 | BuildIndependentTargetsInParallel = 1;
218 | LastSwiftUpdateCheck = 1420;
219 | LastUpgradeCheck = 1420;
220 | TargetAttributes = {
221 | 429EDC6829D742F000D75C2E = {
222 | CreatedOnToolsVersion = 14.2;
223 | };
224 | 429EDC7929D742F300D75C2E = {
225 | CreatedOnToolsVersion = 14.2;
226 | TestTargetID = 429EDC6829D742F000D75C2E;
227 | };
228 | 429EDC8329D742F300D75C2E = {
229 | CreatedOnToolsVersion = 14.2;
230 | TestTargetID = 429EDC6829D742F000D75C2E;
231 | };
232 | };
233 | };
234 | buildConfigurationList = 429EDC6429D742F000D75C2E /* Build configuration list for PBXProject "Example" */;
235 | compatibilityVersion = "Xcode 14.0";
236 | developmentRegion = en;
237 | hasScannedForEncodings = 0;
238 | knownRegions = (
239 | en,
240 | Base,
241 | );
242 | mainGroup = 429EDC6029D742F000D75C2E;
243 | packageReferences = (
244 | );
245 | productRefGroup = 429EDC6A29D742F000D75C2E /* Products */;
246 | projectDirPath = "";
247 | projectRoot = "";
248 | targets = (
249 | 429EDC6829D742F000D75C2E /* Example */,
250 | 429EDC7929D742F300D75C2E /* ExampleTests */,
251 | 429EDC8329D742F300D75C2E /* ExampleUITests */,
252 | );
253 | };
254 | /* End PBXProject section */
255 |
256 | /* Begin PBXResourcesBuildPhase section */
257 | 429EDC6729D742F000D75C2E /* Resources */ = {
258 | isa = PBXResourcesBuildPhase;
259 | buildActionMask = 2147483647;
260 | files = (
261 | 429EDC7529D742F300D75C2E /* Preview Assets.xcassets in Resources */,
262 | 429EDC7129D742F300D75C2E /* Assets.xcassets in Resources */,
263 | );
264 | runOnlyForDeploymentPostprocessing = 0;
265 | };
266 | 429EDC7829D742F300D75C2E /* Resources */ = {
267 | isa = PBXResourcesBuildPhase;
268 | buildActionMask = 2147483647;
269 | files = (
270 | );
271 | runOnlyForDeploymentPostprocessing = 0;
272 | };
273 | 429EDC8229D742F300D75C2E /* Resources */ = {
274 | isa = PBXResourcesBuildPhase;
275 | buildActionMask = 2147483647;
276 | files = (
277 | );
278 | runOnlyForDeploymentPostprocessing = 0;
279 | };
280 | /* End PBXResourcesBuildPhase section */
281 |
282 | /* Begin PBXSourcesBuildPhase section */
283 | 429EDC6529D742F000D75C2E /* Sources */ = {
284 | isa = PBXSourcesBuildPhase;
285 | buildActionMask = 2147483647;
286 | files = (
287 | 429EDC6F29D742F000D75C2E /* ContentView.swift in Sources */,
288 | 429EDC6D29D742F000D75C2E /* ExampleApp.swift in Sources */,
289 | );
290 | runOnlyForDeploymentPostprocessing = 0;
291 | };
292 | 429EDC7629D742F300D75C2E /* Sources */ = {
293 | isa = PBXSourcesBuildPhase;
294 | buildActionMask = 2147483647;
295 | files = (
296 | 429EDC7F29D742F300D75C2E /* ExampleTests.swift in Sources */,
297 | );
298 | runOnlyForDeploymentPostprocessing = 0;
299 | };
300 | 429EDC8029D742F300D75C2E /* Sources */ = {
301 | isa = PBXSourcesBuildPhase;
302 | buildActionMask = 2147483647;
303 | files = (
304 | 429EDC8929D742F300D75C2E /* ExampleUITests.swift in Sources */,
305 | 429EDC8B29D742F300D75C2E /* ExampleUITestsLaunchTests.swift in Sources */,
306 | );
307 | runOnlyForDeploymentPostprocessing = 0;
308 | };
309 | /* End PBXSourcesBuildPhase section */
310 |
311 | /* Begin PBXTargetDependency section */
312 | 429EDC7C29D742F300D75C2E /* PBXTargetDependency */ = {
313 | isa = PBXTargetDependency;
314 | target = 429EDC6829D742F000D75C2E /* Example */;
315 | targetProxy = 429EDC7B29D742F300D75C2E /* PBXContainerItemProxy */;
316 | };
317 | 429EDC8629D742F300D75C2E /* PBXTargetDependency */ = {
318 | isa = PBXTargetDependency;
319 | target = 429EDC6829D742F000D75C2E /* Example */;
320 | targetProxy = 429EDC8529D742F300D75C2E /* PBXContainerItemProxy */;
321 | };
322 | /* End PBXTargetDependency section */
323 |
324 | /* Begin XCBuildConfiguration section */
325 | 429EDC8C29D742F300D75C2E /* Debug */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | ALWAYS_SEARCH_USER_PATHS = NO;
329 | CLANG_ANALYZER_NONNULL = YES;
330 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
332 | CLANG_ENABLE_MODULES = YES;
333 | CLANG_ENABLE_OBJC_ARC = YES;
334 | CLANG_ENABLE_OBJC_WEAK = YES;
335 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
336 | CLANG_WARN_BOOL_CONVERSION = YES;
337 | CLANG_WARN_COMMA = YES;
338 | CLANG_WARN_CONSTANT_CONVERSION = YES;
339 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
341 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
342 | CLANG_WARN_EMPTY_BODY = YES;
343 | CLANG_WARN_ENUM_CONVERSION = YES;
344 | CLANG_WARN_INFINITE_RECURSION = YES;
345 | CLANG_WARN_INT_CONVERSION = YES;
346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
347 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
348 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
350 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
352 | CLANG_WARN_STRICT_PROTOTYPES = YES;
353 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
354 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
355 | CLANG_WARN_UNREACHABLE_CODE = YES;
356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
357 | COPY_PHASE_STRIP = NO;
358 | DEBUG_INFORMATION_FORMAT = dwarf;
359 | ENABLE_STRICT_OBJC_MSGSEND = YES;
360 | ENABLE_TESTABILITY = YES;
361 | GCC_C_LANGUAGE_STANDARD = gnu11;
362 | GCC_DYNAMIC_NO_PIC = NO;
363 | GCC_NO_COMMON_BLOCKS = YES;
364 | GCC_OPTIMIZATION_LEVEL = 0;
365 | GCC_PREPROCESSOR_DEFINITIONS = (
366 | "DEBUG=1",
367 | "$(inherited)",
368 | );
369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
371 | GCC_WARN_UNDECLARED_SELECTOR = YES;
372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
373 | GCC_WARN_UNUSED_FUNCTION = YES;
374 | GCC_WARN_UNUSED_VARIABLE = YES;
375 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
376 | MTL_FAST_MATH = YES;
377 | ONLY_ACTIVE_ARCH = YES;
378 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
379 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
380 | };
381 | name = Debug;
382 | };
383 | 429EDC8D29D742F300D75C2E /* Release */ = {
384 | isa = XCBuildConfiguration;
385 | buildSettings = {
386 | ALWAYS_SEARCH_USER_PATHS = NO;
387 | CLANG_ANALYZER_NONNULL = YES;
388 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
390 | CLANG_ENABLE_MODULES = YES;
391 | CLANG_ENABLE_OBJC_ARC = YES;
392 | CLANG_ENABLE_OBJC_WEAK = YES;
393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
394 | CLANG_WARN_BOOL_CONVERSION = YES;
395 | CLANG_WARN_COMMA = YES;
396 | CLANG_WARN_CONSTANT_CONVERSION = YES;
397 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
398 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
399 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
400 | CLANG_WARN_EMPTY_BODY = YES;
401 | CLANG_WARN_ENUM_CONVERSION = YES;
402 | CLANG_WARN_INFINITE_RECURSION = YES;
403 | CLANG_WARN_INT_CONVERSION = YES;
404 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
405 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
406 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
408 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
409 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
410 | CLANG_WARN_STRICT_PROTOTYPES = YES;
411 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
412 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
413 | CLANG_WARN_UNREACHABLE_CODE = YES;
414 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
415 | COPY_PHASE_STRIP = NO;
416 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
417 | ENABLE_NS_ASSERTIONS = NO;
418 | ENABLE_STRICT_OBJC_MSGSEND = YES;
419 | GCC_C_LANGUAGE_STANDARD = gnu11;
420 | GCC_NO_COMMON_BLOCKS = YES;
421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
423 | GCC_WARN_UNDECLARED_SELECTOR = YES;
424 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
425 | GCC_WARN_UNUSED_FUNCTION = YES;
426 | GCC_WARN_UNUSED_VARIABLE = YES;
427 | MTL_ENABLE_DEBUG_INFO = NO;
428 | MTL_FAST_MATH = YES;
429 | SWIFT_COMPILATION_MODE = wholemodule;
430 | SWIFT_OPTIMIZATION_LEVEL = "-O";
431 | };
432 | name = Release;
433 | };
434 | 429EDC8F29D742F300D75C2E /* Debug */ = {
435 | isa = XCBuildConfiguration;
436 | buildSettings = {
437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
438 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
439 | CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements;
440 | CODE_SIGN_STYLE = Automatic;
441 | CURRENT_PROJECT_VERSION = 1;
442 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
443 | DEVELOPMENT_TEAM = WLCQDVKTS9;
444 | ENABLE_HARDENED_RUNTIME = YES;
445 | ENABLE_PREVIEWS = YES;
446 | GENERATE_INFOPLIST_FILE = YES;
447 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
448 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
449 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
450 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
451 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
452 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
453 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
454 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
455 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
456 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
457 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
458 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
459 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
460 | MACOSX_DEPLOYMENT_TARGET = 12.6;
461 | MARKETING_VERSION = 1.0;
462 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.Example";
463 | PRODUCT_NAME = "$(TARGET_NAME)";
464 | SDKROOT = auto;
465 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
466 | SWIFT_EMIT_LOC_STRINGS = YES;
467 | SWIFT_VERSION = 5.0;
468 | TARGETED_DEVICE_FAMILY = "1,2";
469 | };
470 | name = Debug;
471 | };
472 | 429EDC9029D742F300D75C2E /* Release */ = {
473 | isa = XCBuildConfiguration;
474 | buildSettings = {
475 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
476 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
477 | CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements;
478 | CODE_SIGN_STYLE = Automatic;
479 | CURRENT_PROJECT_VERSION = 1;
480 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
481 | DEVELOPMENT_TEAM = WLCQDVKTS9;
482 | ENABLE_HARDENED_RUNTIME = YES;
483 | ENABLE_PREVIEWS = YES;
484 | GENERATE_INFOPLIST_FILE = YES;
485 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
486 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
487 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
488 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
489 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
490 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
491 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
492 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
493 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
494 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
495 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
496 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
497 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
498 | MACOSX_DEPLOYMENT_TARGET = 12.6;
499 | MARKETING_VERSION = 1.0;
500 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.Example";
501 | PRODUCT_NAME = "$(TARGET_NAME)";
502 | SDKROOT = auto;
503 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
504 | SWIFT_EMIT_LOC_STRINGS = YES;
505 | SWIFT_VERSION = 5.0;
506 | TARGETED_DEVICE_FAMILY = "1,2";
507 | };
508 | name = Release;
509 | };
510 | 429EDC9229D742F300D75C2E /* Debug */ = {
511 | isa = XCBuildConfiguration;
512 | buildSettings = {
513 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
514 | BUNDLE_LOADER = "$(TEST_HOST)";
515 | CODE_SIGN_STYLE = Automatic;
516 | CURRENT_PROJECT_VERSION = 1;
517 | DEVELOPMENT_TEAM = WLCQDVKTS9;
518 | GENERATE_INFOPLIST_FILE = YES;
519 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
520 | MACOSX_DEPLOYMENT_TARGET = 12.6;
521 | MARKETING_VERSION = 1.0;
522 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleTests";
523 | PRODUCT_NAME = "$(TARGET_NAME)";
524 | SDKROOT = auto;
525 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
526 | SWIFT_EMIT_LOC_STRINGS = NO;
527 | SWIFT_VERSION = 5.0;
528 | TARGETED_DEVICE_FAMILY = "1,2";
529 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example";
530 | };
531 | name = Debug;
532 | };
533 | 429EDC9329D742F300D75C2E /* Release */ = {
534 | isa = XCBuildConfiguration;
535 | buildSettings = {
536 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
537 | BUNDLE_LOADER = "$(TEST_HOST)";
538 | CODE_SIGN_STYLE = Automatic;
539 | CURRENT_PROJECT_VERSION = 1;
540 | DEVELOPMENT_TEAM = WLCQDVKTS9;
541 | GENERATE_INFOPLIST_FILE = YES;
542 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
543 | MACOSX_DEPLOYMENT_TARGET = 12.6;
544 | MARKETING_VERSION = 1.0;
545 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleTests";
546 | PRODUCT_NAME = "$(TARGET_NAME)";
547 | SDKROOT = auto;
548 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
549 | SWIFT_EMIT_LOC_STRINGS = NO;
550 | SWIFT_VERSION = 5.0;
551 | TARGETED_DEVICE_FAMILY = "1,2";
552 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example";
553 | };
554 | name = Release;
555 | };
556 | 429EDC9529D742F300D75C2E /* Debug */ = {
557 | isa = XCBuildConfiguration;
558 | buildSettings = {
559 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
560 | CODE_SIGN_STYLE = Automatic;
561 | CURRENT_PROJECT_VERSION = 1;
562 | DEVELOPMENT_TEAM = WLCQDVKTS9;
563 | GENERATE_INFOPLIST_FILE = YES;
564 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
565 | MACOSX_DEPLOYMENT_TARGET = 12.6;
566 | MARKETING_VERSION = 1.0;
567 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleUITests";
568 | PRODUCT_NAME = "$(TARGET_NAME)";
569 | SDKROOT = auto;
570 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
571 | SWIFT_EMIT_LOC_STRINGS = NO;
572 | SWIFT_VERSION = 5.0;
573 | TARGETED_DEVICE_FAMILY = "1,2";
574 | TEST_TARGET_NAME = Example;
575 | };
576 | name = Debug;
577 | };
578 | 429EDC9629D742F300D75C2E /* Release */ = {
579 | isa = XCBuildConfiguration;
580 | buildSettings = {
581 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
582 | CODE_SIGN_STYLE = Automatic;
583 | CURRENT_PROJECT_VERSION = 1;
584 | DEVELOPMENT_TEAM = WLCQDVKTS9;
585 | GENERATE_INFOPLIST_FILE = YES;
586 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
587 | MACOSX_DEPLOYMENT_TARGET = 12.6;
588 | MARKETING_VERSION = 1.0;
589 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleUITests";
590 | PRODUCT_NAME = "$(TARGET_NAME)";
591 | SDKROOT = auto;
592 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
593 | SWIFT_EMIT_LOC_STRINGS = NO;
594 | SWIFT_VERSION = 5.0;
595 | TARGETED_DEVICE_FAMILY = "1,2";
596 | TEST_TARGET_NAME = Example;
597 | };
598 | name = Release;
599 | };
600 | /* End XCBuildConfiguration section */
601 |
602 | /* Begin XCConfigurationList section */
603 | 429EDC6429D742F000D75C2E /* Build configuration list for PBXProject "Example" */ = {
604 | isa = XCConfigurationList;
605 | buildConfigurations = (
606 | 429EDC8C29D742F300D75C2E /* Debug */,
607 | 429EDC8D29D742F300D75C2E /* Release */,
608 | );
609 | defaultConfigurationIsVisible = 0;
610 | defaultConfigurationName = Release;
611 | };
612 | 429EDC8E29D742F300D75C2E /* Build configuration list for PBXNativeTarget "Example" */ = {
613 | isa = XCConfigurationList;
614 | buildConfigurations = (
615 | 429EDC8F29D742F300D75C2E /* Debug */,
616 | 429EDC9029D742F300D75C2E /* Release */,
617 | );
618 | defaultConfigurationIsVisible = 0;
619 | defaultConfigurationName = Release;
620 | };
621 | 429EDC9129D742F300D75C2E /* Build configuration list for PBXNativeTarget "ExampleTests" */ = {
622 | isa = XCConfigurationList;
623 | buildConfigurations = (
624 | 429EDC9229D742F300D75C2E /* Debug */,
625 | 429EDC9329D742F300D75C2E /* Release */,
626 | );
627 | defaultConfigurationIsVisible = 0;
628 | defaultConfigurationName = Release;
629 | };
630 | 429EDC9429D742F300D75C2E /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = {
631 | isa = XCConfigurationList;
632 | buildConfigurations = (
633 | 429EDC9529D742F300D75C2E /* Debug */,
634 | 429EDC9629D742F300D75C2E /* Release */,
635 | );
636 | defaultConfigurationIsVisible = 0;
637 | defaultConfigurationName = Release;
638 | };
639 | /* End XCConfigurationList section */
640 |
641 | /* Begin XCSwiftPackageProductDependency section */
642 | 429EDC9A29D7431800D75C2E /* CocoaUI */ = {
643 | isa = XCSwiftPackageProductDependency;
644 | productName = CocoaUI;
645 | };
646 | /* End XCSwiftPackageProductDependency section */
647 | };
648 | rootObject = 429EDC6129D742F000D75C2E /* Project object */;
649 | }
650 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
34 |
40 |
41 |
42 |
45 |
51 |
52 |
53 |
54 |
55 |
65 |
67 |
73 |
74 |
75 |
76 |
82 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import SwiftUI
10 | import CocoaUI
11 |
12 | struct ContentView: View {
13 | @State var value: Float = 0
14 | @State var toggle: Bool = true
15 | @State var isScrolled = false
16 |
17 | @State var text = "Hello"
18 | @State var stepperValue = 8
19 |
20 | var body: some View {
21 | TabView {
22 | NavigationView {
23 | VStack(spacing: 4) {
24 | Slider(value: $value)
25 | .cocoa { slider in
26 | #if canImport(UIKit)
27 | slider.setThumbImage(.init(systemName: "swift"), for: .normal)
28 | #elseif canImport(Cocoa)
29 | slider.wantsLayer = true
30 | slider.layer?.borderWidth = 1
31 | #endif
32 | }
33 |
34 | List {
35 | ForEach(0..<100) { i in
36 | NavigationLink {
37 | Text("data \(i)")
38 | .cocoa(for: CocoaViewController.self) { vc in
39 | print(vc)
40 | }
41 | .onViewWillAppear { vc in
42 | vc?.tabBarController?.tabBar.isHidden = true
43 | }
44 | .onViewWillDisappear { vc in
45 | vc?.tabBarController?.tabBar.isHidden = false
46 | }
47 | .background(Color.cyan)
48 | } label: {
49 | Text("data \(i)")
50 | }
51 | }
52 | }.cocoa { collectionView in
53 | #if canImport(UIKit)
54 | collectionView.layer.borderWidth = 1
55 | if !isScrolled {
56 | collectionView.scrollToItem(at: [0, 80], at: .bottom, animated: true)
57 | isScrolled = true
58 | }
59 | #elseif canImport(Cocoa)
60 | collectionView.wantsLayer = true
61 | collectionView.layer?.borderWidth = 1
62 | #endif
63 | }
64 |
65 | TextField("textField", text: $text)
66 | .cocoa { textField in
67 | #if canImport(UIKit)
68 | textField.borderStyle = .roundedRect
69 | textField.clearButtonMode = .always
70 | textField.layer.borderWidth = 1
71 | #elseif canImport(Cocoa)
72 | textField.wantsLayer = true
73 | textField.layer?.borderWidth = 1
74 | textField.bezelStyle = .squareBezel
75 | textField.isBezeled = true
76 | #endif
77 | }
78 | .padding(.vertical)
79 |
80 | TextField("textField", text: .constant("Hello6"))
81 | .cocoa { textField in
82 | #if canImport(UIKit)
83 | textField.borderStyle = .line
84 | textField.clearButtonMode = .always
85 | textField.layer.borderWidth = 5
86 | textField.layer.borderColor = UIColor.cyan.cgColor
87 | #endif
88 | }
89 | .padding(.vertical)
90 |
91 | Toggle(toggle ? "on" : "off", isOn: $toggle)
92 | .cocoa(
93 | customize: { `switch` in
94 | #if canImport(UIKit)
95 | `switch`.onTintColor = .red
96 | `switch`.tintColor = .red
97 | `switch`.layer.borderWidth = 1
98 | #elseif canImport(Cocoa)
99 | `switch`.wantsLayer = true
100 | `switch`.layer?.borderWidth = 5
101 | #endif
102 | },
103 | onViewWillAppear: {
104 | print("onViewWillAppear", $0)
105 | },
106 | onViewDidAppear: {
107 | print("onViewDidAppear", $0)
108 | },
109 | onViewWillDisappear: {
110 | print("onViewWillDisappear", $0)
111 | },
112 | onViewDidDisappear: {
113 | print("onViewDidDisappear", $0)
114 | }
115 | )
116 |
117 | Stepper("stepper", value: $stepperValue)
118 | .cocoa { stepper in
119 | #if canImport(UIKit)
120 | stepper.stepValue = 2
121 | stepper.maximumValue = 10
122 | #elseif canImport(Cocoa)
123 | stepper.maxValue = 5
124 | #endif
125 | }
126 |
127 | ColorPicker("colorPicker", selection: .constant(.red))
128 | .cocoa { colorWell in
129 | #if canImport(UIKit)
130 | colorWell.supportsAlpha = false
131 | #elseif canImport(Cocoa)
132 | colorWell.alphaValue = 0.5
133 | #endif
134 | }
135 |
136 | DatePicker("date picker", selection: .constant(Date()))
137 | .cocoa { datePicker in
138 | #if canImport(UIKit)
139 | datePicker.tintColor = .red
140 | #elseif canImport(Cocoa)
141 | datePicker.textColor = .red
142 | datePicker.wantsLayer = true
143 | datePicker.layer?.borderWidth = 8
144 | #endif
145 | }
146 |
147 | Picker("Pick", selection: .constant("A")) {
148 | Text("A")
149 | Text("B")
150 | }
151 | .cocoa { button in
152 | #if canImport(UIKit)
153 | button.layer.borderWidth = 1
154 | #elseif canImport(Cocoa)
155 | button.wantsLayer = true
156 | button.layer?.borderWidth = 8
157 | #endif
158 | }
159 |
160 | }
161 | .padding()
162 | }
163 | .cocoa { split in
164 | print(split)
165 | }
166 | .tabItem {
167 | VStack {
168 | Image(systemName: "swift")
169 | Text("Swift")
170 | }
171 | }
172 | }
173 | }
174 | }
175 |
176 | struct ContentView_Previews: PreviewProvider {
177 | static var previews: some View {
178 | ContentView()
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/Example/Example/Example.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/Example/ExampleApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleApp.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | @main
12 | struct ExampleApp: App {
13 | var body: some Scene {
14 | WindowGroup {
15 | ContentView()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/ExampleTests/ExampleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleTests.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExampleTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
27 | }
28 |
29 | func testPerformanceExample() throws {
30 | // This is an example of a performance test case.
31 | measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Example/ExampleUITests/ExampleUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleUITests.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExampleUITests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
20 | }
21 |
22 | override func tearDownWithError() throws {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | func testLaunchPerformance() throws {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Example/ExampleUITests/ExampleUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleUITestsLaunchTests.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExampleUITestsLaunchTests: XCTestCase {
12 |
13 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
14 | true
15 | }
16 |
17 | override func setUpWithError() throws {
18 | continueAfterFailure = false
19 | }
20 |
21 | func testLaunch() throws {
22 | let app = XCUIApplication()
23 | app.launch()
24 |
25 | // Insert steps here to perform after app launch but before taking a screenshot,
26 | // such as logging into a test account or navigating somewhere in the app
27 |
28 | let attachment = XCTAttachment(screenshot: app.screenshot())
29 | attachment.name = "Launch Screen"
30 | attachment.lifetime = .keepAlways
31 | add(attachment)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Example/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: ""
7 | products: [ ],
8 | targets: []
9 | )
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 p-x9
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "CocoaUI",
7 | platforms: [
8 | .macOS(.v10_15),
9 | .iOS(.v13),
10 | .tvOS(.v13)
11 | ],
12 | products: [
13 | .library(
14 | name: "CocoaUI",
15 | targets: ["CocoaUI"]
16 | ),
17 | ],
18 | dependencies: [],
19 | targets: [
20 | .target(
21 | name: "CocoaUI",
22 | dependencies: []
23 | ),
24 | .testTarget(
25 | name: "CocoaUITests",
26 | dependencies: ["CocoaUI"]
27 | ),
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CocoaUI
2 |
3 | Obtain and customize UIKit/Cocoa objects from SwiftUI components.
4 |
5 |
6 |
7 | [](https://github.com/p-x9/CocoaUI/issues)
8 | [](https://github.com/p-x9/CocoaUI/network/members)
9 | [](https://github.com/p-x9/CocoaUI/stargazers)
10 | [](https://github.com/p-x9/CocoaUI/)
11 |
12 | ## Demo
13 | For example, Slider uses UISlider internally.
14 | Therefore, it can be customized by directly referencing the UISlider object as follows.
15 |
16 | ```swift
17 | Slider(value: $value)
18 | .cocoa { slider in // UISider
19 | slider.setThumbImage(.init(systemName: "swift"), for: .normal)
20 | }
21 | ```
22 | 
23 |
24 | ## Document
25 | For components conforming to the protocol named `DefaultCocoaViewBridging`, you can get UIKit/Cocoa objects as follows.
26 | `DefaultCocoaViewBridging` gets the UIView object from SwiftUI.View.
27 | In contrast, `DefaultCocoaControllerBridging` gets the UIViewController object.
28 |
29 | The CocoaBriding protocol defines a `DefaultCocoaType`.
30 | For example, for Toggle, the DefaultCocoaType is UISwitch(iOS).
31 | It can be handled as follows
32 |
33 | ```swift
34 | Toggle("Hello", isOn: .constant(true))
35 | .cocoa { `switch` in
36 | `switch`.onTintColor = .red
37 | }
38 | ```
39 | ### Specify type
40 | However, if the ToggleStyle is set to `Button`, `UIButton` is used internally instead of `UISwitch`.
41 | For such cases, it is also possible to retrieve the data by specifying the type as follows.
42 |
43 | ```swift
44 | Toggle("Hello", isOn: .constant(true))
45 | .cocoa(for: UIButton.self) { button in
46 | button.layer.borderWidth = 1
47 | }
48 | ```
49 | The method of specifying the type is defined in SwiftUI.View's and does not need to conform to the `DefaultCocoaViewBridging` or `DefaultCocoaControllerBridging ` protocols.
50 | If the specified type is not found, the closure will not be called.
51 |
52 | ### Support additional component
53 | ```swift
54 | extension XXView: DefaultCocoaViewBridging { // confirms `DefaultCocoaViewBridging`
55 | public typealias DefaultCocoaViewType = XXCocoaView // UIKit/Cocoa type
56 | }
57 |
58 | extension YYView: DefaultCocoaViewControllerBridging { // confirms `DefaultCocoaViewControllerBridging`
59 | public typealias DefaultCocoaControllerType = YYCocoaViewController // UIKit/Cocoa type
60 | }
61 | ```
62 |
63 | ### LifeCycle Event Modifiers
64 | In some View lifecycle events, a modifier is provided to retrieve the obtained UIKit/Cocoa object.
65 | As an example, the following code hides the tabBar on push and redisplays it on pop.
66 | ```swift
67 | TabView {
68 | NavigationView {
69 | List(0..<100) { i in
70 | NavigationLink {
71 | Text("Detail: \(i)")
72 | .cocoa(for: CocoaViewController.self) { vc in
73 | print(vc)
74 | }
75 | .onViewWillAppear { vc in
76 | // Hide TabBar
77 | vc?.tabBarController?.tabBar.isHidden = true
78 | }
79 | .onViewWillDisappear { vc in
80 | // Show TabBar
81 | vc?.tabBarController?.tabBar.isHidden = false
82 | }
83 | } label: {
84 | Text("Row: \(i)")
85 | }
86 | }
87 | }
88 | }
89 | ```
90 |
91 | The following modifiers are available.
92 | - onViewWillAppear
93 | - onViewDidLoad
94 | - onViewWillDisappear
95 | - onViewDidDisAppear
96 |
97 | ## SwiftUI and Cocoa correspondence table
98 | This may vary depending on the operating system and usage conditions.
99 |
100 | |SwiftUI|style|UIKit(iOS)|Cocoa(macOS)|UIKit(tvOS)|
101 | |:----:|:----:|:----:|:----:|:----:|
102 | |ScrollView|-| UIScrollView|NSScrollView|UIScrollView|
103 | |List|-| UICollectionView(>=iOS16) UITableView(=iOS16) UITableView(: View {
12 | let content: Content
13 | var customize: ((CocoaType) -> Void)?
14 |
15 | var onViewWillAppear: ((CocoaType?) -> Void)?
16 | var onViewDidAppear: ((CocoaType?) -> Void)?
17 | var onViewWillDisappear: ((CocoaType?) -> Void)?
18 | var onViewDidDisappear: ((CocoaType?) -> Void)?
19 |
20 | init(_ content: Content,
21 | customize: ((CocoaType) -> Void)? = nil,
22 | onViewWillAppear: ((CocoaType?) -> Void)? = nil,
23 | onViewDidAppear: ((CocoaType?) -> Void)? = nil,
24 | onViewWillDisappear: ((CocoaType?) -> Void)? = nil,
25 | onViewDidDisappear: ((CocoaType?) -> Void)? = nil
26 | ) {
27 | self.content = content
28 | self.customize = customize
29 | self.onViewWillAppear = onViewWillAppear
30 | self.onViewDidAppear = onViewDidAppear
31 | self.onViewWillDisappear = onViewWillDisappear
32 | self.onViewDidDisappear = onViewDidDisappear
33 | }
34 |
35 | public var body: some View {
36 | content
37 | .hidden()
38 | .allowsHitTesting(true)
39 | .overlay(
40 | OverlayView(
41 | for: CocoaType.self,
42 | content: {
43 | content
44 | },
45 | customize: customize,
46 | onViewWillAppear: onViewWillAppear,
47 | onViewDidAppear: onViewDidAppear,
48 | onViewWillDisappear: onViewWillDisappear,
49 | onViewDidDisappear: onViewDidDisappear
50 | )
51 | )
52 | }
53 | }
54 |
55 | extension CocoaBridgeView {
56 | public func customize(_ handler: @escaping (CocoaType) -> Void) -> Self {
57 | set(handler, for: \.customize)
58 | }
59 |
60 | public func onViewWillAppear(_ handler: @escaping (CocoaType?) -> Void) -> Self {
61 | set(handler, for: \.onViewWillAppear)
62 | }
63 |
64 | public func onViewDidAppear(_ handler: @escaping (CocoaType?) -> Void) -> Self {
65 | set(handler, for: \.onViewDidAppear)
66 | }
67 |
68 | public func onViewWillDisappear(_ handler: @escaping (CocoaType?) -> Void) -> Self {
69 | set(handler, for: \.onViewWillDisappear)
70 | }
71 |
72 | public func onViewDidDisappear(_ handler: @escaping (CocoaType?) -> Void) -> Self {
73 | set(handler, for: \.onViewDidDisappear)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Sources/CocoaUI/CocoaBridging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CocoaViewBridging.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | // MARK: View ⇄ CocoaView
12 | public protocol DefaultCocoaViewBridging: SwiftUI.View {
13 | associatedtype DefaultCocoaViewType: CocoaView
14 | }
15 |
16 | extension DefaultCocoaViewBridging {
17 | public func cocoa(
18 | customize: ((DefaultCocoaViewType) -> Void)? = nil
19 | ) -> CocoaBridgeView {
20 | CocoaBridgeView(self, customize: customize)
21 | }
22 |
23 | public func cocoa(
24 | customize: ((DefaultCocoaViewType) -> Void)? = nil,
25 | onViewWillAppear: ((DefaultCocoaViewType?) -> Void)? = nil,
26 | onViewDidAppear: ((DefaultCocoaViewType?) -> Void)? = nil,
27 | onViewWillDisappear: ((DefaultCocoaViewType?) -> Void)? = nil,
28 | onViewDidDisappear: ((DefaultCocoaViewType?) -> Void)? = nil
29 | ) -> CocoaBridgeView {
30 | CocoaBridgeView(
31 | self,
32 | customize: customize,
33 | onViewWillAppear: onViewWillAppear,
34 | onViewDidAppear: onViewDidAppear,
35 | onViewWillDisappear: onViewWillDisappear,
36 | onViewDidDisappear: onViewDidDisappear
37 | )
38 | }
39 | }
40 |
41 | // MARK: View ⇄ CocoaViewController
42 | public protocol DefaultCocoaViewControllerBridging: SwiftUI.View {
43 | associatedtype DefaultCocoaControllerType: CocoaViewController
44 | }
45 |
46 | extension DefaultCocoaViewControllerBridging {
47 | public func cocoa(
48 | customize: ((DefaultCocoaControllerType) -> Void)? = nil
49 | ) -> CocoaBridgeView {
50 | CocoaBridgeView(self, customize: customize)
51 | }
52 |
53 | public func cocoa(
54 | customize: ((DefaultCocoaControllerType) -> Void)? = nil,
55 | onViewWillAppear: ((DefaultCocoaControllerType?) -> Void)? = nil,
56 | onViewDidAppear: ((DefaultCocoaControllerType?) -> Void)? = nil,
57 | onViewWillDisappear: ((DefaultCocoaControllerType?) -> Void)? = nil,
58 | onViewDidDisappear: ((DefaultCocoaControllerType?) -> Void)? = nil
59 | ) -> CocoaBridgeView {
60 | CocoaBridgeView(
61 | self,
62 | customize: customize,
63 | onViewWillAppear: onViewWillAppear,
64 | onViewDidAppear: onViewDidAppear,
65 | onViewWillDisappear: onViewWillDisappear,
66 | onViewDidDisappear: onViewDidDisappear
67 | )
68 | }
69 | }
70 |
71 | // MARK: any View
72 | extension View {
73 | public func cocoa(
74 | for type: T.Type,
75 | customize: ((T) -> Void)? = nil,
76 | onViewWillAppear: ((T?) -> Void)? = nil,
77 | onViewDidAppear: ((T?) -> Void)? = nil,
78 | onViewWillDisappear: ((T?) -> Void)? = nil,
79 | onViewDidDisappear: ((T?) -> Void)? = nil
80 | ) -> CocoaBridgeView {
81 | CocoaBridgeView(
82 | self,
83 | customize: customize,
84 | onViewWillAppear: onViewWillAppear,
85 | onViewDidAppear: onViewDidAppear,
86 | onViewWillDisappear: onViewWillDisappear,
87 | onViewDidDisappear: onViewDidDisappear
88 | )
89 | }
90 |
91 | public func cocoa(
92 | for type: T.Type,
93 | customize: @escaping ((T) -> Void)
94 | ) -> CocoaBridgeView {
95 | CocoaBridgeView(self, customize: customize)
96 | }
97 |
98 | public func cocoa(
99 | for type: T.Type,
100 | customize: ((T) -> Void)? = nil,
101 | onViewWillAppear: ((T?) -> Void)? = nil,
102 | onViewDidAppear: ((T?) -> Void)? = nil,
103 | onViewWillDisappear: ((T?) -> Void)? = nil,
104 | onViewDidDisappear: ((T?) -> Void)? = nil
105 | ) -> CocoaBridgeView {
106 | CocoaBridgeView(
107 | self,
108 | customize: customize,
109 | onViewWillAppear: onViewWillAppear,
110 | onViewDidAppear: onViewDidAppear,
111 | onViewWillDisappear: onViewWillDisappear,
112 | onViewDidDisappear: onViewDidDisappear
113 | )
114 | }
115 |
116 | public func cocoa(
117 | for type: T.Type,
118 | customize: @escaping ((T) -> Void)
119 | ) -> CocoaBridgeView {
120 | CocoaBridgeView(self, customize: customize)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Sources/CocoaUI/CocoaUI.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension ScrollView: DefaultCocoaViewBridging {
4 | public typealias DefaultCocoaViewType = CocoaScrollView
5 | }
6 |
7 | extension List: DefaultCocoaViewBridging {
8 | #if canImport(UIKit)
9 | /// NOTE: Definition for iOS 16 or later
10 | /// < iOS16 `UITableView`
11 | public typealias DefaultCocoaViewType = UICollectionView
12 | #elseif canImport(Cocoa)
13 | public typealias DefaultCocoaViewType = NSTableView
14 | #endif
15 | }
16 |
17 | @available(iOS 14.0, *)
18 | @available(macOS 11.0, *)
19 | @available(tvOS 14.0, *)
20 | extension ProgressView: DefaultCocoaViewBridging {
21 | #if canImport(UIKit)
22 | public typealias DefaultCocoaViewType = UIProgressView
23 | #elseif canImport(Cocoa)
24 | public typealias DefaultCocoaViewType = NSProgressIndicator
25 | #endif
26 | }
27 |
28 |
29 | extension TextField: DefaultCocoaViewBridging {
30 | public typealias DefaultCocoaViewType = CocoaTextField
31 | }
32 |
33 | extension SecureField: DefaultCocoaViewBridging {
34 | public typealias DefaultCocoaViewType = CocoaTextField
35 | }
36 |
37 | extension Picker: DefaultCocoaViewBridging {
38 | #if canImport(UIKit) && os(iOS)
39 | public typealias DefaultCocoaViewType = UIPickerView
40 | #elseif canImport(Cocoa)
41 | public typealias DefaultCocoaViewType = NSButton
42 | #elseif canImport(UIKit) && os(tvOS)
43 | public typealias DefaultCocoaViewType = UISegmentedControl
44 | #endif
45 | }
46 |
47 | // MARK: - iOS and tvOS only
48 | #if os(iOS) || os(tvOS)
49 | extension Form: DefaultCocoaViewBridging {
50 | #if os(iOS)
51 | /// NOTE: Definition for iOS 16 or later
52 | /// < iOS16 `UITableView`
53 | public typealias DefaultCocoaViewType = UICollectionView
54 | #elseif os(tvOS)
55 | public typealias DefaultCocoaViewType = UITableView
56 | #endif
57 | }
58 | #endif
59 |
60 | // MARK: - macOS and iOS only
61 | #if os(iOS) || os(macOS)
62 | extension Slider: DefaultCocoaViewBridging {
63 | public typealias DefaultCocoaViewType = CocoaSlider
64 | }
65 |
66 | extension Stepper: DefaultCocoaViewBridging {
67 | public typealias DefaultCocoaViewType = CocoaStepper
68 | }
69 |
70 | extension DatePicker: DefaultCocoaViewBridging {
71 | public typealias DefaultCocoaViewType = CocoaDatePicker
72 | }
73 |
74 | @available(iOS 14.0, *)
75 | @available(macOS 11.0, *)
76 | extension ColorPicker: DefaultCocoaViewBridging {
77 | public typealias DefaultCocoaViewType = CocoaColorWell
78 | }
79 |
80 | extension Toggle: DefaultCocoaViewBridging {
81 | /// NOTE: Depending on the `ToggleStyle`, Cocoa types are different.
82 | #if canImport(UIKit)
83 | public typealias DefaultCocoaViewType = UISwitch
84 | #elseif canImport(Cocoa)
85 | public typealias DefaultCocoaViewType = NSButton
86 | #endif
87 | }
88 |
89 | @available(iOS 14.0, *)
90 | @available(macOS 11.0, *)
91 | extension TextEditor: DefaultCocoaViewBridging {
92 | public typealias DefaultCocoaViewType = CocoaTextView
93 | }
94 | #endif
95 |
96 | // MARK: - iOS only
97 | #if os(iOS)
98 | @available(iOS 16.0, *)
99 | extension MultiDatePicker: DefaultCocoaViewBridging {
100 | public typealias DefaultCocoaViewType = UICalendarView
101 | }
102 | #endif
103 |
104 | // MARK: - macOS only
105 | #if os(macOS) && canImport(Cocoa)
106 | extension Button: DefaultCocoaViewBridging {
107 | public typealias DefaultCocoaViewType = NSButton
108 | }
109 | #endif
110 |
111 | // MARK: - Tab and Navigation
112 | #if canImport(UIKit)
113 | extension TabView: DefaultCocoaViewControllerBridging {
114 | public typealias DefaultCocoaControllerType = UITabBarController
115 | }
116 | #elseif canImport(Cocoa)
117 | extension TabView: DefaultCocoaViewBridging {
118 | public typealias DefaultCocoaViewType = NSTabView
119 | }
120 | #endif
121 |
122 | #if canImport(UIKit) && os(iOS)
123 | extension NavigationView: DefaultCocoaViewControllerBridging {
124 | public typealias DefaultCocoaControllerType = UISplitViewController
125 | }
126 | #elseif canImport(Cocoa)
127 | extension NavigationView: DefaultCocoaViewBridging {
128 | public typealias DefaultCocoaViewType = NSSplitView
129 | }
130 | #elseif canImport(UIKit) && os(tvOS)
131 | extension NavigationView: DefaultCocoaViewControllerBridging {
132 | public typealias DefaultCocoaControllerType = UINavigationController
133 | }
134 | #endif
135 |
136 | #if canImport(UIKit)
137 | @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
138 | extension NavigationStack: DefaultCocoaViewControllerBridging {
139 | public typealias DefaultCocoaControllerType = UINavigationController
140 | }
141 | #elseif canImport(Cocoa)
142 | //@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
143 | //extension NavigationStack: DefaultCocoaViewBridging {
144 | // public typealias DefaultCocoaType = CocoaView
145 | //}
146 | #endif
147 |
148 | #if os(iOS) || os(macOS)
149 | #if canImport(UIKit)
150 | @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
151 | extension NavigationSplitView: DefaultCocoaViewControllerBridging {
152 | public typealias DefaultCocoaControllerType = UISplitViewController
153 | }
154 | #elseif canImport(Cocoa)
155 | @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
156 | extension NavigationSplitView: DefaultCocoaViewBridging {
157 | public typealias DefaultCocoaViewType = NSSplitView
158 | }
159 | #endif
160 | #endif
161 |
--------------------------------------------------------------------------------
/Sources/CocoaUI/Extension/View+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 | import SwiftUI
9 |
10 | extension CocoaView {
11 | func find(for type: T.Type) -> T? {
12 | for subview in subviews {
13 | if let typed = subview as? T {
14 | return typed
15 | } else if let typed = subview.find(for: type) {
16 | return typed
17 | }
18 | }
19 | return self as? T
20 | }
21 | }
22 |
23 | extension CocoaViewController {
24 | func find(for type: T.Type) -> T? {
25 | for child in children {
26 | if let typed = child as? T {
27 | return typed
28 | } else if let typed = child.find(for: type) {
29 | return typed
30 | }
31 | }
32 | return self as? T
33 | }
34 | }
35 |
36 | extension View {
37 | func set(_ value: T, for keyPath: WritableKeyPath) -> Self {
38 | var new = self
39 | new[keyPath: keyPath] = value
40 | return new
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/CocoaUI/OverlayHostingController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OverlayHostingController.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/04/02.
6 | //
7 | //
8 |
9 | import SwiftUI
10 | #if canImport(UIKit)
11 | import UIKit
12 | #elseif canImport(Cocoa)
13 | import Cocoa
14 | #endif
15 |
16 | final public class OverlayHostingController: CocoaHostingController {
17 |
18 | var updateHandler: ((OverlayHostingController) -> Void)?
19 | var shouldUpdate = true
20 |
21 | var onViewWillAppear: ((OverlayHostingController) -> Void)?
22 | var onViewDidAppear: ((OverlayHostingController) -> Void)?
23 | var onViewWillDisappear: ((OverlayHostingController) -> Void)?
24 | var onViewDidDisappear: ((OverlayHostingController) -> Void)?
25 |
26 | #if canImport(UIKit)
27 | public override func didMove(toParent parent: UIViewController?) {
28 | super.didMove(toParent: parent)
29 |
30 | updateHandler?(self)
31 | shouldUpdate = true
32 | }
33 |
34 | public override func viewDidLayoutSubviews() {
35 | super.viewDidLayoutSubviews()
36 |
37 | if shouldUpdate {
38 | updateHandler?(self)
39 | shouldUpdate = false
40 | } else {
41 | shouldUpdate = true
42 | }
43 | }
44 |
45 | public override func viewWillAppear(_ animated: Bool) {
46 | super.viewWillAppear(animated)
47 |
48 | onViewWillAppear?(self)
49 | }
50 |
51 | public override func viewDidAppear(_ animated: Bool) {
52 | super.viewDidAppear(animated)
53 |
54 | updateHandler?(self)
55 | shouldUpdate = true
56 |
57 | onViewDidAppear?(self)
58 | }
59 |
60 | public override func viewWillDisappear(_ animated: Bool) {
61 | super.viewWillDisappear(animated)
62 |
63 | onViewWillDisappear?(self)
64 | }
65 |
66 | public override func viewDidDisappear(_ animated: Bool) {
67 | super.viewDidDisappear(animated)
68 |
69 | onViewDidDisappear?(self)
70 | }
71 | #elseif canImport(Cocoa)
72 | public override func viewDidLayout() {
73 | super.viewDidLayout()
74 |
75 | if shouldUpdate {
76 | updateHandler?(self)
77 | shouldUpdate = false
78 | } else {
79 | shouldUpdate = true
80 | }
81 | }
82 |
83 | public override func viewWillAppear() {
84 | super.viewWillAppear()
85 |
86 | onViewWillAppear?(self)
87 | }
88 |
89 | public override func viewDidAppear() {
90 | super.viewDidAppear()
91 |
92 | updateHandler?(self)
93 | shouldUpdate = true
94 | onViewDidAppear?(self)
95 | }
96 |
97 | public override func viewWillDisappear() {
98 | super.viewWillDisappear()
99 |
100 | onViewWillDisappear?(self)
101 | }
102 |
103 | public override func viewDidDisappear() {
104 | super.viewDidDisappear()
105 |
106 | onViewDidDisappear?(self)
107 | }
108 | #endif
109 | }
110 |
--------------------------------------------------------------------------------
/Sources/CocoaUI/OverlayView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OverlayView.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct OverlayView: CocoaViewControllerRepresentable {
12 | let content: () -> Content
13 | var customize: ((CocoaType) -> Void)?
14 |
15 | var onViewWillAppear: ((CocoaType?) -> Void)?
16 | var onViewDidAppear: ((CocoaType?) -> Void)?
17 | var onViewWillDisappear: ((CocoaType?) -> Void)?
18 | var onViewDidDisappear: ((CocoaType?) -> Void)?
19 |
20 | init(
21 | for type: CocoaType.Type,
22 | content: @escaping () -> Content,
23 | customize: ((CocoaType) -> Void)?,
24 | onViewWillAppear: ((CocoaType?) -> Void)?,
25 | onViewDidAppear: ((CocoaType?) -> Void)?,
26 | onViewWillDisappear: ((CocoaType?) -> Void)?,
27 | onViewDidDisappear: ((CocoaType?) -> Void)?
28 | ) {
29 | self.content = content
30 | self.customize = customize
31 | self.onViewWillAppear = onViewWillAppear
32 | self.onViewDidAppear = onViewDidAppear
33 | self.onViewWillDisappear = onViewWillDisappear
34 | self.onViewDidDisappear = onViewDidDisappear
35 | }
36 |
37 | func findTarget(from controller: CocoaViewController) -> CocoaType? {
38 | if CocoaType.isSubclass(of: CocoaView.self) {
39 | return controller.view.find(for: CocoaType.self)
40 | }
41 |
42 | if CocoaType.isSubclass(of: CocoaViewController.self) {
43 | return controller.find(for: CocoaType.self)
44 | }
45 | return nil
46 | }
47 |
48 | func customize(_ controller: CocoaViewController) {
49 | guard let target = findTarget(from: controller) else {
50 | return
51 | }
52 | customize?(target)
53 | }
54 |
55 | func updateHandlers(for controller: OverlayHostingController) {
56 | controller.updateHandler = { controller in
57 | customize(controller)
58 | }
59 |
60 | controller.onViewWillAppear = { controller in
61 | let target = findTarget(from: controller)
62 | onViewWillAppear?(target)
63 | }
64 |
65 | controller.onViewDidAppear = { controller in
66 | let target = findTarget(from: controller)
67 | onViewDidAppear?(target)
68 | }
69 |
70 | controller.onViewWillDisappear = { controller in
71 | let target = findTarget(from: controller)
72 | onViewWillDisappear?(target)
73 | }
74 |
75 | controller.onViewDidDisappear = { controller in
76 | let target = findTarget(from: controller)
77 | onViewDidDisappear?(target)
78 | }
79 | }
80 |
81 | #if canImport(UIKit)
82 | typealias UIViewControllerType = OverlayHostingController
83 |
84 | func makeUIViewController(context: Context) -> UIViewControllerType {
85 | let controller = OverlayHostingController(rootView: content())
86 | controller.view.backgroundColor = .clear
87 | updateHandlers(for: controller)
88 | return controller
89 | }
90 |
91 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
92 | updateHandlers(for: uiViewController)
93 |
94 | DispatchQueue.main.async {
95 | uiViewController.rootView = content()
96 | uiViewController.view.backgroundColor = .clear
97 | customize(uiViewController)
98 | uiViewController.shouldUpdate = true
99 | }
100 | }
101 | #elseif canImport(Cocoa)
102 | typealias NSViewControllerType = OverlayHostingController
103 |
104 | func makeNSViewController(context: Context) -> NSViewControllerType {
105 | let controller = OverlayHostingController(rootView: content())
106 | updateHandlers(for: controller)
107 | return controller
108 | }
109 |
110 | func updateNSViewController(_ nsViewController: NSViewControllerType, context: Context) {
111 | updateHandlers(for: nsViewController)
112 |
113 | DispatchQueue.main.async {
114 | nsViewController.rootView = content()
115 | customize(nsViewController)
116 | nsViewController.shouldUpdate = true
117 | }
118 | }
119 | #endif
120 | }
121 |
122 | extension OverlayView {
123 | func customize(_ handler: ((CocoaType) -> Void)?) -> Self {
124 | set(handler, for: \.customize)
125 | }
126 |
127 | func onViewWillAppear(_ handler: ((CocoaType?) -> Void)?) -> Self {
128 | set(handler, for: \.onViewWillAppear)
129 | }
130 |
131 | func onViewDidAppear(_ handler: ((CocoaType?) -> Void)?) -> Self {
132 | set(handler, for: \.onViewDidAppear)
133 | }
134 |
135 | func onViewWillDisappear(_ handler: ((CocoaType?) -> Void)?) -> Self {
136 | set(handler, for: \.onViewWillDisappear)
137 | }
138 |
139 | func onViewDidDisappear(_ handler: ((CocoaType?) -> Void)?) -> Self {
140 | set(handler, for: \.onViewDidDisappear)
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Sources/CocoaUI/Util/typealias.swift:
--------------------------------------------------------------------------------
1 | //
2 | // typealias.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/04/01.
6 | //
7 | //
8 | import SwiftUI
9 |
10 | #if canImport(UIKit)
11 | import UIKit
12 | #elseif canImport(Cocoa)
13 | import Cocoa
14 | #endif
15 |
16 | #if canImport(UIKit)
17 | public typealias CocoaView = UIView
18 | #elseif canImport(Cocoa)
19 | public typealias CocoaView = NSView
20 | #endif
21 |
22 | #if canImport(UIKit)
23 | public typealias CocoaViewRepresentable = UIViewRepresentable
24 | #elseif canImport(Cocoa)
25 | public typealias CocoaViewRepresentable = NSViewRepresentable
26 | #endif
27 |
28 | #if canImport(UIKit)
29 | public typealias CocoaViewControllerRepresentable = UIViewControllerRepresentable
30 | #elseif canImport(Cocoa)
31 | public typealias CocoaViewControllerRepresentable = NSViewControllerRepresentable
32 | #endif
33 |
34 | #if canImport(UIKit)
35 | public typealias CocoaScrollView = UIScrollView
36 | #elseif canImport(Cocoa)
37 | public typealias CocoaScrollView = NSScrollView
38 | #endif
39 |
40 | #if canImport(UIKit)
41 | public typealias CocoaCollectionView = UICollectionView
42 | #elseif canImport(Cocoa)
43 | public typealias CocoaCollectionView = NSCollectionView
44 | #endif
45 |
46 | #if canImport(UIKit)
47 | public typealias CocoaTextField = UITextField
48 | #elseif canImport(Cocoa)
49 | public typealias CocoaTextField = NSTextField
50 | #endif
51 |
52 | #if canImport(UIKit)
53 | public typealias CocoaTextView = UITextView
54 | #elseif canImport(Cocoa)
55 | public typealias CocoaTextView = NSTextView
56 | #endif
57 |
58 | // MARK: - iOS and macOS only
59 | #if os(iOS) || os(macOS)
60 |
61 | #if canImport(UIKit)
62 | public typealias CocoaSwitch = UISwitch
63 | #elseif canImport(Cocoa)
64 | public typealias CocoaSwitch = NSSwitch
65 | #endif
66 |
67 | #if canImport(UIKit)
68 | public typealias CocoaSlider = UISlider
69 | #elseif canImport(Cocoa)
70 | public typealias CocoaSlider = NSSlider
71 | #endif
72 |
73 | #if canImport(UIKit)
74 | public typealias CocoaStepper = UIStepper
75 | #elseif canImport(Cocoa)
76 | public typealias CocoaStepper = NSStepper
77 | #endif
78 |
79 | #if canImport(UIKit)
80 | public typealias CocoaDatePicker = UIDatePicker
81 | #elseif canImport(Cocoa)
82 | public typealias CocoaDatePicker = NSDatePicker
83 | #endif
84 |
85 | #if canImport(UIKit)
86 | @available(iOS 14.0, *)
87 | public typealias CocoaColorWell = UIColorWell
88 | #elseif canImport(Cocoa)
89 | public typealias CocoaColorWell = NSColorWell
90 | #endif
91 |
92 | #endif
93 |
94 | // MARK: - Controller
95 | #if canImport(UIKit)
96 | public typealias CocoaHostingController = UIHostingController
97 | #elseif canImport(Cocoa)
98 | public typealias CocoaHostingController = NSHostingController
99 | #endif
100 |
101 | #if canImport(UIKit)
102 | public typealias CocoaViewController = UIViewController
103 | #elseif canImport(Cocoa)
104 | public typealias CocoaViewController = NSViewController
105 | #endif
106 |
107 |
--------------------------------------------------------------------------------
/Tests/CocoaUITests/CocoaUITests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import CocoaUI
3 |
4 | final class CocoaUITests: XCTestCase {}
5 |
--------------------------------------------------------------------------------