├── .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 | [![Github issues](https://img.shields.io/github/issues/p-x9/CocoaUI)](https://github.com/p-x9/CocoaUI/issues) 8 | [![Github forks](https://img.shields.io/github/forks/p-x9/CocoaUI)](https://github.com/p-x9/CocoaUI/network/members) 9 | [![Github stars](https://img.shields.io/github/stars/p-x9/CocoaUI)](https://github.com/p-x9/CocoaUI/stargazers) 10 | [![Github top language](https://img.shields.io/github/languages/top/p-x9/CocoaUI)](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 | ![Slider](https://user-images.githubusercontent.com/50244599/229353608-86eb9a3c-815e-4919-9f44-1cc35d244d7e.png) 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 | --------------------------------------------------------------------------------