├── README.md ├── VisionMessagesDualPane.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── VisionMessagesDualPane ├── Assets.xcassets ├── AppIcon.appiconset │ ├── Contents.json │ ├── icon-1024.png │ ├── icon-120.png │ ├── icon-152.png │ ├── icon-167.png │ ├── icon-180.png │ ├── icon-29.png │ ├── icon-40.png │ ├── icon-58.png │ ├── icon-60.png │ ├── icon-76.png │ ├── icon-80.png │ ├── icon-87.png │ └── icon20.png └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── Info.plist └── Source ├── Application ├── MDPAppDelegate.swift ├── MDPSceneDelegate+NSToolbar.swift └── MDPSceneDelegate.swift └── Main ├── MDPAuxiliaryPaneViewController.swift ├── MDPDualPaneViewController.swift └── MDPMainViewController.swift /README.md: -------------------------------------------------------------------------------- 1 | # VisionMessagesDualPane 2 | 3 | Simple example to demonstrate how one might implement a Messages-style animated auxiliary pane view presentation on visionOS. 4 | 5 | ### Screenshots 6 | 7 | https://github.com/steventroughtonsmith/VisionMessagesDualPane/assets/45212/846ea290-2406-4645-ba5c-e549a2fcde92 8 | 9 | -------------------------------------------------------------------------------- /VisionMessagesDualPane.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B0080ADA2B65EC790049D78A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B0080AD92B65EC790049D78A /* Assets.xcassets */; }; 11 | B0080ADE2B65EC790049D78A /* MDPAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0080ADD2B65EC790049D78A /* MDPAppDelegate.swift */; }; 12 | B0080AE02B65EC790049D78A /* MDPSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0080ADF2B65EC790049D78A /* MDPSceneDelegate.swift */; }; 13 | B0080AE22B65EC790049D78A /* MDPSceneDelegate+NSToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0080AE12B65EC790049D78A /* MDPSceneDelegate+NSToolbar.swift */; }; 14 | B0080AE52B65EC790049D78A /* MDPMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0080AE42B65EC790049D78A /* MDPMainViewController.swift */; }; 15 | B0080AE82B65EC790049D78A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B0080AE62B65EC790049D78A /* LaunchScreen.storyboard */; }; 16 | B0080AF02B65ECA30049D78A /* MDPAuxiliaryPaneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0080AEF2B65ECA30049D78A /* MDPAuxiliaryPaneViewController.swift */; }; 17 | B0080AF22B65ED030049D78A /* MDPDualPaneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0080AF12B65ED030049D78A /* MDPDualPaneViewController.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | B0080AD62B65EC790049D78A /* VisionMessagesDualPane.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VisionMessagesDualPane.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | B0080AD92B65EC790049D78A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | B0080ADD2B65EC790049D78A /* MDPAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDPAppDelegate.swift; sourceTree = ""; }; 24 | B0080ADF2B65EC790049D78A /* MDPSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDPSceneDelegate.swift; sourceTree = ""; }; 25 | B0080AE12B65EC790049D78A /* MDPSceneDelegate+NSToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MDPSceneDelegate+NSToolbar.swift"; sourceTree = ""; }; 26 | B0080AE42B65EC790049D78A /* MDPMainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDPMainViewController.swift; sourceTree = ""; }; 27 | B0080AE72B65EC790049D78A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | B0080AE92B65EC790049D78A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | B0080AEF2B65ECA30049D78A /* MDPAuxiliaryPaneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDPAuxiliaryPaneViewController.swift; sourceTree = ""; }; 30 | B0080AF12B65ED030049D78A /* MDPDualPaneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDPDualPaneViewController.swift; sourceTree = ""; }; 31 | B0080AF32B65FBDA0049D78A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | B0080AD32B65EC790049D78A /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | B0080ACD2B65EC790049D78A = { 46 | isa = PBXGroup; 47 | children = ( 48 | B0080AF32B65FBDA0049D78A /* README.md */, 49 | B0080AD82B65EC790049D78A /* VisionMessagesDualPane */, 50 | B0080AD72B65EC790049D78A /* Products */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | B0080AD72B65EC790049D78A /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | B0080AD62B65EC790049D78A /* VisionMessagesDualPane.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | B0080AD82B65EC790049D78A /* VisionMessagesDualPane */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | B0080AD92B65EC790049D78A /* Assets.xcassets */, 66 | B0080AE62B65EC790049D78A /* LaunchScreen.storyboard */, 67 | B0080AE92B65EC790049D78A /* Info.plist */, 68 | B0080ADB2B65EC790049D78A /* Source */, 69 | ); 70 | path = VisionMessagesDualPane; 71 | sourceTree = ""; 72 | }; 73 | B0080ADB2B65EC790049D78A /* Source */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | B0080ADC2B65EC790049D78A /* Application */, 77 | B0080AE32B65EC790049D78A /* Main */, 78 | ); 79 | path = Source; 80 | sourceTree = ""; 81 | }; 82 | B0080ADC2B65EC790049D78A /* Application */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | B0080ADD2B65EC790049D78A /* MDPAppDelegate.swift */, 86 | B0080ADF2B65EC790049D78A /* MDPSceneDelegate.swift */, 87 | B0080AE12B65EC790049D78A /* MDPSceneDelegate+NSToolbar.swift */, 88 | ); 89 | path = Application; 90 | sourceTree = ""; 91 | }; 92 | B0080AE32B65EC790049D78A /* Main */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | B0080AF12B65ED030049D78A /* MDPDualPaneViewController.swift */, 96 | B0080AE42B65EC790049D78A /* MDPMainViewController.swift */, 97 | B0080AEF2B65ECA30049D78A /* MDPAuxiliaryPaneViewController.swift */, 98 | ); 99 | path = Main; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | B0080AD52B65EC790049D78A /* VisionMessagesDualPane */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = B0080AEC2B65EC790049D78A /* Build configuration list for PBXNativeTarget "VisionMessagesDualPane" */; 108 | buildPhases = ( 109 | B0080AD22B65EC790049D78A /* Sources */, 110 | B0080AD32B65EC790049D78A /* Frameworks */, 111 | B0080AD42B65EC790049D78A /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = VisionMessagesDualPane; 118 | productName = VisionMessagesDualPane; 119 | productReference = B0080AD62B65EC790049D78A /* VisionMessagesDualPane.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | B0080ACE2B65EC790049D78A /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | BuildIndependentTargetsInParallel = 1; 129 | CLASSPREFIX = MDP; 130 | LastSwiftUpdateCheck = 1520; 131 | LastUpgradeCheck = 1520; 132 | TargetAttributes = { 133 | B0080AD52B65EC790049D78A = { 134 | CreatedOnToolsVersion = 15.2; 135 | }; 136 | }; 137 | }; 138 | buildConfigurationList = B0080AD12B65EC790049D78A /* Build configuration list for PBXProject "VisionMessagesDualPane" */; 139 | compatibilityVersion = "Xcode 14.0"; 140 | developmentRegion = en; 141 | hasScannedForEncodings = 0; 142 | knownRegions = ( 143 | en, 144 | Base, 145 | ); 146 | mainGroup = B0080ACD2B65EC790049D78A; 147 | productRefGroup = B0080AD72B65EC790049D78A /* Products */; 148 | projectDirPath = ""; 149 | projectRoot = ""; 150 | targets = ( 151 | B0080AD52B65EC790049D78A /* VisionMessagesDualPane */, 152 | ); 153 | }; 154 | /* End PBXProject section */ 155 | 156 | /* Begin PBXResourcesBuildPhase section */ 157 | B0080AD42B65EC790049D78A /* Resources */ = { 158 | isa = PBXResourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | B0080ADA2B65EC790049D78A /* Assets.xcassets in Resources */, 162 | B0080AE82B65EC790049D78A /* LaunchScreen.storyboard in Resources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXResourcesBuildPhase section */ 167 | 168 | /* Begin PBXSourcesBuildPhase section */ 169 | B0080AD22B65EC790049D78A /* Sources */ = { 170 | isa = PBXSourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | B0080AE22B65EC790049D78A /* MDPSceneDelegate+NSToolbar.swift in Sources */, 174 | B0080ADE2B65EC790049D78A /* MDPAppDelegate.swift in Sources */, 175 | B0080AF22B65ED030049D78A /* MDPDualPaneViewController.swift in Sources */, 176 | B0080AE02B65EC790049D78A /* MDPSceneDelegate.swift in Sources */, 177 | B0080AE52B65EC790049D78A /* MDPMainViewController.swift in Sources */, 178 | B0080AF02B65ECA30049D78A /* MDPAuxiliaryPaneViewController.swift in Sources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXSourcesBuildPhase section */ 183 | 184 | /* Begin PBXVariantGroup section */ 185 | B0080AE62B65EC790049D78A /* LaunchScreen.storyboard */ = { 186 | isa = PBXVariantGroup; 187 | children = ( 188 | B0080AE72B65EC790049D78A /* Base */, 189 | ); 190 | name = LaunchScreen.storyboard; 191 | sourceTree = ""; 192 | }; 193 | /* End PBXVariantGroup section */ 194 | 195 | /* Begin XCBuildConfiguration section */ 196 | B0080AEA2B65EC790049D78A /* Debug */ = { 197 | isa = XCBuildConfiguration; 198 | buildSettings = { 199 | ALWAYS_SEARCH_USER_PATHS = NO; 200 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 204 | CLANG_ENABLE_MODULES = YES; 205 | CLANG_ENABLE_OBJC_ARC = YES; 206 | CLANG_ENABLE_OBJC_WEAK = YES; 207 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 208 | CLANG_WARN_BOOL_CONVERSION = YES; 209 | CLANG_WARN_COMMA = YES; 210 | CLANG_WARN_CONSTANT_CONVERSION = YES; 211 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 213 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INFINITE_RECURSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 220 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 222 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 223 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 224 | CLANG_WARN_STRICT_PROTOTYPES = YES; 225 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 226 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 227 | CLANG_WARN_UNREACHABLE_CODE = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 230 | COPY_PHASE_STRIP = NO; 231 | DEBUG_INFORMATION_FORMAT = dwarf; 232 | ENABLE_STRICT_OBJC_MSGSEND = YES; 233 | ENABLE_TESTABILITY = YES; 234 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 235 | GCC_C_LANGUAGE_STANDARD = gnu17; 236 | GCC_DYNAMIC_NO_PIC = NO; 237 | GCC_NO_COMMON_BLOCKS = YES; 238 | GCC_OPTIMIZATION_LEVEL = 0; 239 | GCC_PREPROCESSOR_DEFINITIONS = ( 240 | "DEBUG=1", 241 | "$(inherited)", 242 | ); 243 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 245 | GCC_WARN_UNDECLARED_SELECTOR = YES; 246 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 247 | GCC_WARN_UNUSED_FUNCTION = YES; 248 | GCC_WARN_UNUSED_VARIABLE = YES; 249 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 250 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 251 | MTL_FAST_MATH = YES; 252 | ONLY_ACTIVE_ARCH = YES; 253 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 254 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 255 | SWIFT_VERSION = 5.0; 256 | }; 257 | name = Debug; 258 | }; 259 | B0080AEB2B65EC790049D78A /* Release */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | ALWAYS_SEARCH_USER_PATHS = NO; 263 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 264 | CLANG_ANALYZER_NONNULL = YES; 265 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_ENABLE_OBJC_WEAK = YES; 270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_COMMA = YES; 273 | CLANG_WARN_CONSTANT_CONVERSION = YES; 274 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 276 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 277 | CLANG_WARN_EMPTY_BODY = YES; 278 | CLANG_WARN_ENUM_CONVERSION = YES; 279 | CLANG_WARN_INFINITE_RECURSION = YES; 280 | CLANG_WARN_INT_CONVERSION = YES; 281 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 283 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 285 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 286 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 287 | CLANG_WARN_STRICT_PROTOTYPES = YES; 288 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 289 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 290 | CLANG_WARN_UNREACHABLE_CODE = YES; 291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 292 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_NS_ASSERTIONS = NO; 296 | ENABLE_STRICT_OBJC_MSGSEND = YES; 297 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 298 | GCC_C_LANGUAGE_STANDARD = gnu17; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 307 | MTL_ENABLE_DEBUG_INFO = NO; 308 | MTL_FAST_MATH = YES; 309 | SWIFT_COMPILATION_MODE = wholemodule; 310 | SWIFT_VERSION = 5.0; 311 | }; 312 | name = Release; 313 | }; 314 | B0080AED2B65EC790049D78A /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | CLANG_STATIC_ANALYZER_MODE = deep; 319 | CODE_SIGN_IDENTITY = "iPhone Developer"; 320 | CODE_SIGN_STYLE = Automatic; 321 | CURRENT_PROJECT_VERSION = 1; 322 | DEVELOPMENT_TEAM = 2ZDN69KUUV; 323 | ENABLE_HARDENED_RUNTIME = YES; 324 | GCC_TREAT_WARNINGS_AS_ERRORS = deep; 325 | GCC_WARN_PEDANTIC = deep; 326 | GENERATE_INFOPLIST_FILE = YES; 327 | INFOPLIST_FILE = "$(PRODUCT_NAME)/Info.plist"; 328 | IPHONEOS_DEPLOYMENT_TARGET = 17.2; 329 | LD_RUNPATH_SEARCH_PATHS = ( 330 | "$(inherited)", 331 | "@executable_path/Frameworks", 332 | ); 333 | MARKETING_VERSION = 1.0; 334 | PRODUCT_BUNDLE_IDENTIFIER = com.highcaffeinecontent.VisionMessagesDualPane; 335 | PRODUCT_NAME = VisionMessagesDualPane; 336 | RUN_CLANG_STATIC_ANALYZER = YES; 337 | SDKROOT = auto; 338 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; 339 | SUPPORTS_MACCATALYST = YES; 340 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; 341 | SWIFT_EMIT_LOC_STRINGS = YES; 342 | SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; 343 | SWIFT_VERSION = 5.0; 344 | TARGETED_DEVICE_FAMILY = "1,2,6,7"; 345 | }; 346 | name = Debug; 347 | }; 348 | B0080AEE2B65EC790049D78A /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | CLANG_STATIC_ANALYZER_MODE = deep; 353 | CODE_SIGN_IDENTITY = "iPhone Developer"; 354 | CODE_SIGN_STYLE = Automatic; 355 | CURRENT_PROJECT_VERSION = 1; 356 | DEVELOPMENT_TEAM = 2ZDN69KUUV; 357 | ENABLE_HARDENED_RUNTIME = YES; 358 | GCC_TREAT_WARNINGS_AS_ERRORS = deep; 359 | GCC_WARN_PEDANTIC = deep; 360 | GENERATE_INFOPLIST_FILE = YES; 361 | INFOPLIST_FILE = "$(PRODUCT_NAME)/Info.plist"; 362 | IPHONEOS_DEPLOYMENT_TARGET = 17.2; 363 | LD_RUNPATH_SEARCH_PATHS = ( 364 | "$(inherited)", 365 | "@executable_path/Frameworks", 366 | ); 367 | MARKETING_VERSION = 1.0; 368 | PRODUCT_BUNDLE_IDENTIFIER = com.highcaffeinecontent.VisionMessagesDualPane; 369 | PRODUCT_NAME = VisionMessagesDualPane; 370 | RUN_CLANG_STATIC_ANALYZER = YES; 371 | SDKROOT = auto; 372 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; 373 | SUPPORTS_MACCATALYST = YES; 374 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; 375 | SWIFT_EMIT_LOC_STRINGS = YES; 376 | SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; 377 | SWIFT_VERSION = 5.0; 378 | TARGETED_DEVICE_FAMILY = "1,2,6,7"; 379 | VALIDATE_PRODUCT = YES; 380 | }; 381 | name = Release; 382 | }; 383 | /* End XCBuildConfiguration section */ 384 | 385 | /* Begin XCConfigurationList section */ 386 | B0080AD12B65EC790049D78A /* Build configuration list for PBXProject "VisionMessagesDualPane" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | B0080AEA2B65EC790049D78A /* Debug */, 390 | B0080AEB2B65EC790049D78A /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Release; 394 | }; 395 | B0080AEC2B65EC790049D78A /* Build configuration list for PBXNativeTarget "VisionMessagesDualPane" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | B0080AED2B65EC790049D78A /* Debug */, 399 | B0080AEE2B65EC790049D78A /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | defaultConfigurationName = Release; 403 | }; 404 | /* End XCConfigurationList section */ 405 | }; 406 | rootObject = B0080ACE2B65EC790049D78A /* Project object */; 407 | } 408 | -------------------------------------------------------------------------------- /VisionMessagesDualPane.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon-40.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon-60.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon-58.png", 19 | "scale": "2x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon-87.png", 25 | "scale": "3x" 26 | }, 27 | { 28 | "size": "40x40", 29 | "idiom": "iphone", 30 | "filename": "icon-80.png", 31 | "scale": "2x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon-120.png", 37 | "scale": "3x" 38 | }, 39 | { 40 | "size": "60x60", 41 | "idiom": "iphone", 42 | "filename": "icon-120.png", 43 | "scale": "2x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "icon-180.png", 49 | "scale": "3x" 50 | }, 51 | { 52 | "size": "20x20", 53 | "idiom": "ipad", 54 | "filename": "icon20.png", 55 | "scale": "1x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "icon-40.png", 61 | "scale": "2x" 62 | }, 63 | { 64 | "size": "29x29", 65 | "idiom": "ipad", 66 | "filename": "icon-29.png", 67 | "scale": "1x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "icon-58.png", 73 | "scale": "2x" 74 | }, 75 | { 76 | "size": "40x40", 77 | "idiom": "ipad", 78 | "filename": "icon-40.png", 79 | "scale": "1x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "icon-80.png", 85 | "scale": "2x" 86 | }, 87 | { 88 | "size": "76x76", 89 | "idiom": "ipad", 90 | "filename": "icon-76.png", 91 | "scale": "1x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "icon-152.png", 97 | "scale": "2x" 98 | }, 99 | { 100 | "size": "83.5x83.5", 101 | "idiom": "ipad", 102 | "filename": "icon-167.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "1024x1024", 107 | "idiom": "ios-marketing", 108 | "filename": "icon-1024.png", 109 | "scale": "1x" 110 | } 111 | ], 112 | "info": { 113 | "version": 1, 114 | "author": "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-120.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-152.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-167.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-180.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-58.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-60.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-80.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon-87.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/VisionMessagesDualPane/c450440d5a779467ffdd44543f050ddba1fce78d/VisionMessagesDualPane/Assets.xcassets/AppIcon.appiconset/icon20.png -------------------------------------------------------------------------------- /VisionMessagesDualPane/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VisionMessagesDualPane/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | LSRequiresIPhoneOS 8 | 9 | UIApplicationSceneManifest 10 | 11 | UIApplicationSupportsMultipleScenes 12 | 13 | UISceneConfigurations 14 | 15 | UIWindowSceneSessionRoleApplication 16 | 17 | 18 | UISceneConfigurationName 19 | Default Configuration 20 | UISceneDelegateClassName 21 | $(PRODUCT_MODULE_NAME).MDPSceneDelegate 22 | 23 | 24 | 25 | 26 | UIApplicationSupportsIndirectInputEvents 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIRequiredDeviceCapabilities 31 | 32 | armv7 33 | 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Source/Application/MDPAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDPAppDelegate.swift 3 | // VisionMessagesDualPane 4 | // 5 | // Created by Steven Troughton-Smith on 28/01/2024. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class MDPAppDelegate: UIResponder, UIApplicationDelegate { 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Source/Application/MDPSceneDelegate+NSToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDPSceneDelegate+NSToolbar.swift 3 | // VisionMessagesDualPane 4 | // 5 | // Created by Steven Troughton-Smith on 28/01/2024. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | #if targetEnvironment(macCatalyst) 12 | import AppKit 13 | 14 | extension MDPSceneDelegate: NSToolbarDelegate { 15 | 16 | func toolbarItems() -> [NSToolbarItem.Identifier] { 17 | return [.toggleSidebar] 18 | } 19 | 20 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 21 | return toolbarItems() 22 | } 23 | 24 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 25 | return toolbarItems() 26 | } 27 | 28 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { 29 | return NSToolbarItem(itemIdentifier: itemIdentifier) 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Source/Application/MDPSceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDPSceneDelegate.swift 3 | // VisionMessagesDualPane 4 | // 5 | // Created by Steven Troughton-Smith on 28/01/2024. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class MDPSceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | guard let windowScene = scene as? UIWindowScene else { 16 | fatalError("Expected scene of type UIWindowScene but got an unexpected type") 17 | } 18 | window = UIWindow(windowScene: windowScene) 19 | 20 | if let window = window { 21 | window.rootViewController = MDPDualPaneViewController() 22 | 23 | #if targetEnvironment(macCatalyst) 24 | 25 | let toolbar = NSToolbar(identifier: NSToolbar.Identifier("MDPSceneDelegate.Toolbar")) 26 | toolbar.delegate = self 27 | toolbar.displayMode = .iconOnly 28 | toolbar.allowsUserCustomization = false 29 | 30 | windowScene.titlebar?.toolbar = toolbar 31 | windowScene.titlebar?.toolbarStyle = .unified 32 | 33 | #endif 34 | 35 | window.makeKeyAndVisible() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Source/Main/MDPAuxiliaryPaneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDPAuxiliaryPaneViewController.swift 3 | // VisionMessagesDualPane 4 | // 5 | // Created by Steven Troughton-Smith on 28/01/2024. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | // MARK: - SwiftUI that does the z animation in 3D space 12 | 13 | struct AuxiliaryPane: View { 14 | 15 | @ObservedObject var parentViewController:MDPDualPaneViewController 16 | 17 | var body: some View { 18 | AuxiliaryPaneWrapper(parentViewController: parentViewController) 19 | #if os(visionOS) 20 | .offset(z: parentViewController.isShowingBothPanes ? 0 : -64) 21 | #endif 22 | .opacity(parentViewController.isShowingBothPanes ? 1.0 : 0.0) 23 | .animation(.spring, value: parentViewController.isShowingBothPanes) 24 | } 25 | } 26 | 27 | // MARK: - SwiftUI wrapper for UIKit content view controller 28 | 29 | struct AuxiliaryPaneWrapper: UIViewControllerRepresentable { 30 | var parentViewController:MDPDualPaneViewController 31 | 32 | func makeUIViewController(context: Context) -> some UIViewController { 33 | let vc = MDPAuxiliaryPaneViewController() 34 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: parentViewController, action: #selector(MDPDualPaneViewController.togglePane(_:))) 35 | let nc = UINavigationController(rootViewController: vc) 36 | 37 | return nc 38 | } 39 | 40 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { 41 | 42 | } 43 | } 44 | 45 | // MARK: - UIKit content view controller 46 | 47 | class MDPAuxiliaryPaneViewController: UICollectionViewController { 48 | 49 | static let auxiliaryPaneWidth = CGFloat(320) 50 | 51 | // MARK: - Everything below this point is not important for this example 52 | 53 | enum Section { 54 | case main 55 | } 56 | 57 | struct Item: Hashable { 58 | var identifier = UUID().uuidString 59 | 60 | func hash(into hasher: inout Hasher) { 61 | hasher.combine(identifier) 62 | } 63 | 64 | static func == (lhs: Item, rhs: Item) -> Bool { 65 | return lhs.identifier == rhs.identifier 66 | } 67 | } 68 | 69 | typealias ItemType = Item 70 | 71 | let reuseIdentifier = "Cell" 72 | var dataSource: UICollectionViewDiffableDataSource! = nil 73 | var currentItems:[ItemType] = [] 74 | 75 | let padding = CGFloat(20) 76 | 77 | init() { 78 | let layout = UICollectionViewFlowLayout() 79 | 80 | super.init(collectionViewLayout: layout) 81 | guard let collectionView = collectionView else { return } 82 | 83 | #if !os(visionOS) 84 | view.backgroundColor = .systemBackground 85 | #endif 86 | 87 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier) 88 | 89 | if #available(iOS 15.0, *) { 90 | collectionView.allowsFocus = true 91 | } 92 | 93 | collectionView.contentInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) 94 | 95 | configureDataSource() 96 | } 97 | 98 | required init?(coder: NSCoder) { 99 | fatalError("init(coder:) has not been implemented") 100 | } 101 | 102 | // MARK: - Layout 103 | 104 | override func viewDidLayoutSubviews() { 105 | var columns = 2 106 | let maxWidth = CGFloat(200) 107 | 108 | guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } 109 | 110 | while view.bounds.size.width/CGFloat(columns) > maxWidth 111 | { 112 | columns += 1 113 | } 114 | 115 | let usableWidth = collectionView.bounds.width-collectionView.contentInset.left-collectionView.contentInset.right 116 | let itemSize = (usableWidth - padding*((CGFloat(columns-1)))) / CGFloat(columns) 117 | 118 | layout.itemSize = CGSize(width: itemSize, height: itemSize) 119 | layout.minimumLineSpacing = padding 120 | } 121 | 122 | // MARK: - Data Source 123 | 124 | func configureDataSource() { 125 | 126 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { 127 | (collectionView: UICollectionView, indexPath: IndexPath, item: ItemType) -> UICollectionViewCell? in 128 | 129 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifier, for: indexPath) 130 | 131 | var config = UIListContentConfiguration.cell() 132 | 133 | config.text = "Cell" 134 | config.textProperties.alignment = .center 135 | 136 | cell.contentConfiguration = config 137 | 138 | var bg = UIBackgroundConfiguration.listPlainCell() 139 | bg.cornerRadius = CGFloat(8) 140 | bg.backgroundColor = .systemFill 141 | cell.backgroundConfiguration = bg 142 | 143 | return cell 144 | } 145 | 146 | collectionView.dataSource = dataSource 147 | 148 | refresh() 149 | } 150 | 151 | func snapshot() -> NSDiffableDataSourceSectionSnapshot { 152 | var snapshot = NSDiffableDataSourceSectionSnapshot() 153 | 154 | for _ in 0 ..< 9 { 155 | currentItems.append(ItemType()) 156 | } 157 | 158 | snapshot.append(currentItems) 159 | 160 | return snapshot 161 | } 162 | 163 | func refresh() { 164 | guard let dataSource = collectionView.dataSource as? UICollectionViewDiffableDataSource else { return } 165 | 166 | dataSource.apply(snapshot(), to: .main, animatingDifferences: false) 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Source/Main/MDPDualPaneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDPDualPaneViewController.swift 3 | // VisionMessagesDualPane 4 | // 5 | // Created by Steven Troughton-Smith on 28/01/2024. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class MDPDualPaneViewController: UIViewController, ObservableObject { 12 | 13 | let padding = CGFloat(20) 14 | 15 | var auxiliaryController:UIHostingController? = nil 16 | let mainController = MDPMainViewController() 17 | let mainNavigationController:UINavigationController 18 | 19 | // MARK: - 20 | 21 | init() { 22 | mainNavigationController = UINavigationController(rootViewController: mainController) 23 | 24 | super.init(nibName: nil, bundle: nil) 25 | 26 | auxiliaryController = UIHostingController(rootView: AuxiliaryPane(parentViewController: self)) 27 | 28 | mainController.button.addTarget(self, action: #selector(togglePane(_:)), for: .touchUpInside) 29 | 30 | 31 | addChild(mainNavigationController) 32 | view.addSubview(mainNavigationController.view) 33 | 34 | addChild(auxiliaryController!) 35 | view.addSubview(auxiliaryController!.view) 36 | 37 | } 38 | 39 | required init?(coder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | // MARK: - 44 | 45 | @Published var isShowingBothPanes = false { 46 | didSet { 47 | 48 | view.setNeedsLayout() 49 | 50 | UIView.animate(withDuration: 0.3) { [weak self] in 51 | self?.view.layoutIfNeeded() 52 | } 53 | 54 | _updateWindowGeometry() 55 | } 56 | } 57 | 58 | @objc func togglePane(_ sender:Any?) { 59 | isShowingBothPanes.toggle() 60 | } 61 | 62 | // MARK: - Window Geometry 63 | 64 | func _updateWindowGeometry() { 65 | #if os(visionOS) 66 | /* 67 | This is quick and dirty, and it would make more sense to locate this code at your window scene level 68 | */ 69 | guard let window = view.window else { return } 70 | guard let scene = window.windowScene else { return } 71 | 72 | let geo = UIWindowScene.GeometryPreferences.Vision() 73 | 74 | geo.size = window.bounds.size 75 | 76 | if isShowingBothPanes { 77 | geo.size?.width += MDPAuxiliaryPaneViewController.auxiliaryPaneWidth + padding 78 | } 79 | else { 80 | geo.size?.width -= MDPAuxiliaryPaneViewController.auxiliaryPaneWidth + padding 81 | } 82 | 83 | scene.requestGeometryUpdate(geo) 84 | #endif 85 | } 86 | 87 | // MARK: - Layout 88 | 89 | override func viewDidLayoutSubviews() { 90 | let division = view.bounds.divided(atDistance: MDPAuxiliaryPaneViewController.auxiliaryPaneWidth+padding, from: .maxXEdge) 91 | 92 | mainNavigationController.view.frame = isShowingBothPanes ? division.remainder : view.bounds 93 | auxiliaryController?.view.frame = division.slice.divided(atDistance: padding, from: .minXEdge).remainder 94 | } 95 | 96 | #if os(visionOS) 97 | /* This view has no background glass, just empty space */ 98 | override var preferredContainerBackgroundStyle: UIContainerBackgroundStyle { 99 | return .hidden 100 | } 101 | #endif 102 | } 103 | -------------------------------------------------------------------------------- /VisionMessagesDualPane/Source/Main/MDPMainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDPMainViewController.swift 3 | // VisionMessagesDualPane 4 | // 5 | // Created by Steven Troughton-Smith on 28/01/2024. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | final class MDPMainViewController: UIViewController { 12 | 13 | let button = UIButton(type: .system) 14 | 15 | init() { 16 | super.init(nibName: nil, bundle: nil) 17 | title = "VisionMessagesDualPane" 18 | 19 | #if !os(visionOS) 20 | view.backgroundColor = .systemBackground 21 | #endif 22 | 23 | button.configuration = .borderedProminent() 24 | button.setTitle("Show Second Pane", for: .normal) 25 | 26 | view.addSubview(button) 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | 33 | override func viewDidLayoutSubviews() { 34 | button.sizeToFit() 35 | button.frame = CGRect(x: (view.bounds.width-button.bounds.width)/2, y: (view.bounds.height-button.bounds.height)/2, width: button.bounds.width, height: button.bounds.height) 36 | } 37 | } 38 | --------------------------------------------------------------------------------