├── .gitignore ├── README.md ├── SwiftUI View Inside a MapKit Callout.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── SwiftUI View Inside a MapKit Callout ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── kitten.imageset │ ├── Contents.json │ └── Image.png └── marvin.imageset │ ├── Contents.json │ └── Image.png ├── Base.lproj └── LaunchScreen.storyboard ├── ContentView.swift ├── Info.plist ├── MapViews ├── MapCalloutView.swift └── MapView.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── SceneDelegate.swift └── Test Views ├── ButtonTestView.swift ├── TestButtonView.swift ├── TestImage.swift └── TestScrollView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | SwiftUI View Inside a MapKit Callout.xcodeproj/xcuserdata/khuffie.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist 3 | xcuserdata/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftui-mapkit-callout 2 | This is a simple class to add a SwiftUI View inside an MKAnnotationView.detailCalloutAccessoryView property. 3 | 4 | Yes, you can technically set .canShowCallout to false and create your own completely custom callout, but you'd have to draw your own bubble, position it manually and who knows what can happen in the future. 5 | 6 | I wanted to avoid doing that, and wanted to avoid using UIKit. 7 | 8 | ## Usage 9 | 10 | * Copy the `MapCalloutView` file located inside the `MapViews` folder into your project. 11 | * Inside the `mapView:viewFor:annotation` method, create or reuse an `MKAnnotationView` as normal 12 | * Set `.canShowCallout` to `true` on your `MKAnnotationView` 13 | * Create your SwiftUI View as normal 14 | * Pass your created view in the initializer for `MapCalloutView`, making sure it's type casted to `AnyView` 15 | * Pass your instance of `MapCalloutView` to `detailCalloutAccessoryView` 16 | 17 | ## Example 18 | 19 | ```swift 20 | view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier) 21 | view?.canShowCallout = true 22 | 23 | //create any SwiftUI View you like 24 | let customView = Text("This is a SwiftUI Text View") 25 | 26 | //create an instance of MapCalloutView 27 | let callout = MapCalloutView(rootView: AnyView(customView)) 28 | 29 | //pass it to detailCalloutAccessoryView 30 | view?.detailCalloutAccessoryView = callout 31 | ``` 32 | 33 | The sample project includes initializations of a few different kind of views. 34 | 35 | ## Tips & Tricks 36 | * I'd recommend setting a `maxHeight` and `maxWidth` to the `.frame` of your SwiftUI View 37 | * The left and right callout accessory views will work as expected 38 | * If `title` is set on the `annotation`, it *will* be displayed above your SwiftUI View. I haven't figured out a way to hide this. If you have any thoughts, would love to hear them! 39 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 794E8CBE2484424100E18AF7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CBD2484424100E18AF7 /* AppDelegate.swift */; }; 11 | 794E8CC02484424100E18AF7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CBF2484424100E18AF7 /* SceneDelegate.swift */; }; 12 | 794E8CC22484424100E18AF7 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CC12484424100E18AF7 /* ContentView.swift */; }; 13 | 794E8CC42484424400E18AF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 794E8CC32484424400E18AF7 /* Assets.xcassets */; }; 14 | 794E8CC72484424400E18AF7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 794E8CC62484424400E18AF7 /* Preview Assets.xcassets */; }; 15 | 794E8CCA2484424400E18AF7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 794E8CC82484424400E18AF7 /* LaunchScreen.storyboard */; }; 16 | 794E8CD32484427400E18AF7 /* MapCalloutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CD22484427400E18AF7 /* MapCalloutView.swift */; }; 17 | 794E8CD5248442AD00E18AF7 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CD4248442AD00E18AF7 /* MapView.swift */; }; 18 | 794E8CD8248449F500E18AF7 /* TestImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CD7248449F500E18AF7 /* TestImage.swift */; }; 19 | 794E8CDA24844C5C00E18AF7 /* TestScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CD924844C5C00E18AF7 /* TestScrollView.swift */; }; 20 | 794E8CDE24844D5F00E18AF7 /* TestButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794E8CDD24844D5F00E18AF7 /* TestButtonView.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 794E8CBA2484424100E18AF7 /* SwiftUI View Inside a MapKit Callout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftUI View Inside a MapKit Callout.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 794E8CBD2484424100E18AF7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | 794E8CBF2484424100E18AF7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 27 | 794E8CC12484424100E18AF7 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 28 | 794E8CC32484424400E18AF7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 794E8CC62484424400E18AF7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 30 | 794E8CC92484424400E18AF7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | 794E8CCB2484424400E18AF7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 794E8CD22484427400E18AF7 /* MapCalloutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCalloutView.swift; sourceTree = ""; }; 33 | 794E8CD4248442AD00E18AF7 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; 34 | 794E8CD7248449F500E18AF7 /* TestImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestImage.swift; sourceTree = ""; }; 35 | 794E8CD924844C5C00E18AF7 /* TestScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestScrollView.swift; sourceTree = ""; }; 36 | 794E8CDD24844D5F00E18AF7 /* TestButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestButtonView.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 794E8CB72484424100E18AF7 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 794E8CB12484424100E18AF7 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 794E8CBC2484424100E18AF7 /* SwiftUI View Inside a MapKit Callout */, 54 | 794E8CBB2484424100E18AF7 /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 794E8CBB2484424100E18AF7 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 794E8CBA2484424100E18AF7 /* SwiftUI View Inside a MapKit Callout.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | 794E8CBC2484424100E18AF7 /* SwiftUI View Inside a MapKit Callout */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 794E8CD6248449E400E18AF7 /* Test Views */, 70 | 794E8CD12484426200E18AF7 /* MapViews */, 71 | 794E8CBD2484424100E18AF7 /* AppDelegate.swift */, 72 | 794E8CBF2484424100E18AF7 /* SceneDelegate.swift */, 73 | 794E8CC12484424100E18AF7 /* ContentView.swift */, 74 | 794E8CC32484424400E18AF7 /* Assets.xcassets */, 75 | 794E8CC82484424400E18AF7 /* LaunchScreen.storyboard */, 76 | 794E8CCB2484424400E18AF7 /* Info.plist */, 77 | 794E8CC52484424400E18AF7 /* Preview Content */, 78 | ); 79 | path = "SwiftUI View Inside a MapKit Callout"; 80 | sourceTree = ""; 81 | }; 82 | 794E8CC52484424400E18AF7 /* Preview Content */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 794E8CC62484424400E18AF7 /* Preview Assets.xcassets */, 86 | ); 87 | path = "Preview Content"; 88 | sourceTree = ""; 89 | }; 90 | 794E8CD12484426200E18AF7 /* MapViews */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 794E8CD22484427400E18AF7 /* MapCalloutView.swift */, 94 | 794E8CD4248442AD00E18AF7 /* MapView.swift */, 95 | ); 96 | path = MapViews; 97 | sourceTree = ""; 98 | }; 99 | 794E8CD6248449E400E18AF7 /* Test Views */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 794E8CD7248449F500E18AF7 /* TestImage.swift */, 103 | 794E8CD924844C5C00E18AF7 /* TestScrollView.swift */, 104 | 794E8CDD24844D5F00E18AF7 /* TestButtonView.swift */, 105 | ); 106 | path = "Test Views"; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 794E8CB92484424100E18AF7 /* SwiftUI View Inside a MapKit Callout */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 794E8CCE2484424400E18AF7 /* Build configuration list for PBXNativeTarget "SwiftUI View Inside a MapKit Callout" */; 115 | buildPhases = ( 116 | 794E8CB62484424100E18AF7 /* Sources */, 117 | 794E8CB72484424100E18AF7 /* Frameworks */, 118 | 794E8CB82484424100E18AF7 /* Resources */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = "SwiftUI View Inside a MapKit Callout"; 125 | productName = "SwiftUI View Inside a MapKit Callout"; 126 | productReference = 794E8CBA2484424100E18AF7 /* SwiftUI View Inside a MapKit Callout.app */; 127 | productType = "com.apple.product-type.application"; 128 | }; 129 | /* End PBXNativeTarget section */ 130 | 131 | /* Begin PBXProject section */ 132 | 794E8CB22484424100E18AF7 /* Project object */ = { 133 | isa = PBXProject; 134 | attributes = { 135 | LastSwiftUpdateCheck = 1150; 136 | LastUpgradeCheck = 1150; 137 | ORGANIZATIONNAME = "Ahmed El-Khuffash"; 138 | TargetAttributes = { 139 | 794E8CB92484424100E18AF7 = { 140 | CreatedOnToolsVersion = 11.5; 141 | }; 142 | }; 143 | }; 144 | buildConfigurationList = 794E8CB52484424100E18AF7 /* Build configuration list for PBXProject "SwiftUI View Inside a MapKit Callout" */; 145 | compatibilityVersion = "Xcode 9.3"; 146 | developmentRegion = en; 147 | hasScannedForEncodings = 0; 148 | knownRegions = ( 149 | en, 150 | Base, 151 | ); 152 | mainGroup = 794E8CB12484424100E18AF7; 153 | productRefGroup = 794E8CBB2484424100E18AF7 /* Products */; 154 | projectDirPath = ""; 155 | projectRoot = ""; 156 | targets = ( 157 | 794E8CB92484424100E18AF7 /* SwiftUI View Inside a MapKit Callout */, 158 | ); 159 | }; 160 | /* End PBXProject section */ 161 | 162 | /* Begin PBXResourcesBuildPhase section */ 163 | 794E8CB82484424100E18AF7 /* Resources */ = { 164 | isa = PBXResourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 794E8CCA2484424400E18AF7 /* LaunchScreen.storyboard in Resources */, 168 | 794E8CC72484424400E18AF7 /* Preview Assets.xcassets in Resources */, 169 | 794E8CC42484424400E18AF7 /* Assets.xcassets in Resources */, 170 | ); 171 | runOnlyForDeploymentPostprocessing = 0; 172 | }; 173 | /* End PBXResourcesBuildPhase section */ 174 | 175 | /* Begin PBXSourcesBuildPhase section */ 176 | 794E8CB62484424100E18AF7 /* Sources */ = { 177 | isa = PBXSourcesBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | 794E8CD32484427400E18AF7 /* MapCalloutView.swift in Sources */, 181 | 794E8CDA24844C5C00E18AF7 /* TestScrollView.swift in Sources */, 182 | 794E8CD8248449F500E18AF7 /* TestImage.swift in Sources */, 183 | 794E8CBE2484424100E18AF7 /* AppDelegate.swift in Sources */, 184 | 794E8CD5248442AD00E18AF7 /* MapView.swift in Sources */, 185 | 794E8CC02484424100E18AF7 /* SceneDelegate.swift in Sources */, 186 | 794E8CDE24844D5F00E18AF7 /* TestButtonView.swift in Sources */, 187 | 794E8CC22484424100E18AF7 /* ContentView.swift in Sources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXSourcesBuildPhase section */ 192 | 193 | /* Begin PBXVariantGroup section */ 194 | 794E8CC82484424400E18AF7 /* LaunchScreen.storyboard */ = { 195 | isa = PBXVariantGroup; 196 | children = ( 197 | 794E8CC92484424400E18AF7 /* Base */, 198 | ); 199 | name = LaunchScreen.storyboard; 200 | sourceTree = ""; 201 | }; 202 | /* End PBXVariantGroup section */ 203 | 204 | /* Begin XCBuildConfiguration section */ 205 | 794E8CCC2484424400E18AF7 /* Debug */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 212 | CLANG_CXX_LIBRARY = "libc++"; 213 | CLANG_ENABLE_MODULES = YES; 214 | CLANG_ENABLE_OBJC_ARC = YES; 215 | CLANG_ENABLE_OBJC_WEAK = YES; 216 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 217 | CLANG_WARN_BOOL_CONVERSION = YES; 218 | CLANG_WARN_COMMA = YES; 219 | CLANG_WARN_CONSTANT_CONVERSION = YES; 220 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 222 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 223 | CLANG_WARN_EMPTY_BODY = YES; 224 | CLANG_WARN_ENUM_CONVERSION = YES; 225 | CLANG_WARN_INFINITE_RECURSION = YES; 226 | CLANG_WARN_INT_CONVERSION = YES; 227 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | COPY_PHASE_STRIP = NO; 238 | DEBUG_INFORMATION_FORMAT = dwarf; 239 | ENABLE_STRICT_OBJC_MSGSEND = YES; 240 | ENABLE_TESTABILITY = YES; 241 | GCC_C_LANGUAGE_STANDARD = gnu11; 242 | GCC_DYNAMIC_NO_PIC = NO; 243 | GCC_NO_COMMON_BLOCKS = YES; 244 | GCC_OPTIMIZATION_LEVEL = 0; 245 | GCC_PREPROCESSOR_DEFINITIONS = ( 246 | "DEBUG=1", 247 | "$(inherited)", 248 | ); 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | IPHONEOS_DEPLOYMENT_TARGET = 13.5; 256 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 257 | MTL_FAST_MATH = YES; 258 | ONLY_ACTIVE_ARCH = YES; 259 | SDKROOT = iphoneos; 260 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 261 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 262 | }; 263 | name = Debug; 264 | }; 265 | 794E8CCD2484424400E18AF7 /* Release */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_ANALYZER_NONNULL = YES; 270 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 272 | CLANG_CXX_LIBRARY = "libc++"; 273 | CLANG_ENABLE_MODULES = YES; 274 | CLANG_ENABLE_OBJC_ARC = YES; 275 | CLANG_ENABLE_OBJC_WEAK = YES; 276 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 277 | CLANG_WARN_BOOL_CONVERSION = YES; 278 | CLANG_WARN_COMMA = YES; 279 | CLANG_WARN_CONSTANT_CONVERSION = YES; 280 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 283 | CLANG_WARN_EMPTY_BODY = YES; 284 | CLANG_WARN_ENUM_CONVERSION = YES; 285 | CLANG_WARN_INFINITE_RECURSION = YES; 286 | CLANG_WARN_INT_CONVERSION = YES; 287 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 289 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu11; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 13.5; 310 | MTL_ENABLE_DEBUG_INFO = NO; 311 | MTL_FAST_MATH = YES; 312 | SDKROOT = iphoneos; 313 | SWIFT_COMPILATION_MODE = wholemodule; 314 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 315 | VALIDATE_PRODUCT = YES; 316 | }; 317 | name = Release; 318 | }; 319 | 794E8CCF2484424400E18AF7 /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 323 | CODE_SIGN_STYLE = Automatic; 324 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUI View Inside a MapKit Callout/Preview Content\""; 325 | ENABLE_PREVIEWS = YES; 326 | INFOPLIST_FILE = "SwiftUI View Inside a MapKit Callout/Info.plist"; 327 | LD_RUNPATH_SEARCH_PATHS = ( 328 | "$(inherited)", 329 | "@executable_path/Frameworks", 330 | ); 331 | PRODUCT_BUNDLE_IDENTIFIER = "com.khuffie.SwiftUI-View-Inside-a-MapKit-Callout"; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SWIFT_VERSION = 5.0; 334 | TARGETED_DEVICE_FAMILY = "1,2"; 335 | }; 336 | name = Debug; 337 | }; 338 | 794E8CD02484424400E18AF7 /* Release */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 342 | CODE_SIGN_STYLE = Automatic; 343 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUI View Inside a MapKit Callout/Preview Content\""; 344 | ENABLE_PREVIEWS = YES; 345 | INFOPLIST_FILE = "SwiftUI View Inside a MapKit Callout/Info.plist"; 346 | LD_RUNPATH_SEARCH_PATHS = ( 347 | "$(inherited)", 348 | "@executable_path/Frameworks", 349 | ); 350 | PRODUCT_BUNDLE_IDENTIFIER = "com.khuffie.SwiftUI-View-Inside-a-MapKit-Callout"; 351 | PRODUCT_NAME = "$(TARGET_NAME)"; 352 | SWIFT_VERSION = 5.0; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Release; 356 | }; 357 | /* End XCBuildConfiguration section */ 358 | 359 | /* Begin XCConfigurationList section */ 360 | 794E8CB52484424100E18AF7 /* Build configuration list for PBXProject "SwiftUI View Inside a MapKit Callout" */ = { 361 | isa = XCConfigurationList; 362 | buildConfigurations = ( 363 | 794E8CCC2484424400E18AF7 /* Debug */, 364 | 794E8CCD2484424400E18AF7 /* Release */, 365 | ); 366 | defaultConfigurationIsVisible = 0; 367 | defaultConfigurationName = Release; 368 | }; 369 | 794E8CCE2484424400E18AF7 /* Build configuration list for PBXNativeTarget "SwiftUI View Inside a MapKit Callout" */ = { 370 | isa = XCConfigurationList; 371 | buildConfigurations = ( 372 | 794E8CCF2484424400E18AF7 /* Debug */, 373 | 794E8CD02484424400E18AF7 /* Release */, 374 | ); 375 | defaultConfigurationIsVisible = 0; 376 | defaultConfigurationName = Release; 377 | }; 378 | /* End XCConfigurationList section */ 379 | }; 380 | rootObject = 794E8CB22484424100E18AF7 /* Project object */; 381 | } 382 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Assets.xcassets/kitten.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Assets.xcassets/kitten.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khuffie/swiftui-mapkit-callout/5fb07c28fd3f5de3d8ff7bb79aa018dd01cd6abd/SwiftUI View Inside a MapKit Callout/Assets.xcassets/kitten.imageset/Image.png -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Assets.xcassets/marvin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Assets.xcassets/marvin.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khuffie/swiftui-mapkit-callout/5fb07c28fd3f5de3d8ff7bb79aa018dd01cd6abd/SwiftUI View Inside a MapKit Callout/Assets.xcassets/marvin.imageset/Image.png -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/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 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | var body: some View { 13 | NavigationView { 14 | MapView() 15 | //.edgesIgnoringSafeArea(.all) 16 | 17 | .navigationBarTitle("SwiftUI Callouts") 18 | } 19 | } 20 | } 21 | 22 | struct ContentView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | ContentView() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/MapViews/MapCalloutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapCalloutView.swift 3 | // tdotwiz 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftUI 12 | 13 | /** 14 | A custom callout view to be be passed as an MKMarkerAnnotationView, where you can use a SwiftUI View as it's base. 15 | */ 16 | class MapCalloutView: UIView { 17 | 18 | //create the UIHostingController we need. For now just adding a generic UI 19 | let body:UIHostingController = UIHostingController(rootView: AnyView(Text("Hello")) ) 20 | 21 | 22 | /** 23 | An initializer for the callout. You must pass it in your SwiftUI view as the rootView property, wrapped with AnyView. e.g. 24 | MapCalloutView(rootView: AnyView(YourCustomView)) 25 | 26 | Obviously you can pass in any properties to your custom view. 27 | */ 28 | init(rootView: AnyView) { 29 | super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) 30 | body.rootView = AnyView(rootView) 31 | setupView() 32 | } 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | super.init(coder: aDecoder) 36 | setupView() 37 | } 38 | 39 | /** 40 | Ensures the callout bubble resizes according to the size of the SwiftUI view that's passed in. 41 | */ 42 | private func setupView() { 43 | 44 | translatesAutoresizingMaskIntoConstraints = false 45 | 46 | //pass in your SwiftUI View as the rootView to the body UIHostingController 47 | //body.rootView = Text("Hello World * 2") 48 | body.view.translatesAutoresizingMaskIntoConstraints = false 49 | body.view.frame = bounds 50 | body.view.backgroundColor = nil 51 | //add the subview to the map callout 52 | addSubview(body.view) 53 | 54 | NSLayoutConstraint.activate([ 55 | body.view.topAnchor.constraint(equalTo: topAnchor), 56 | body.view.bottomAnchor.constraint(equalTo: bottomAnchor), 57 | body.view.leftAnchor.constraint(equalTo: leftAnchor), 58 | body.view.rightAnchor.constraint(equalTo: rightAnchor) 59 | ]) 60 | 61 | sizeToFit() 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/MapViews/MapView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapView.swift 3 | // tdotwiz 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-26. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import MapKit 12 | 13 | struct MapView: UIViewRepresentable { 14 | 15 | /** 16 | Sample data for map annotations. 17 | */ 18 | let sampleData = [ 19 | MKPointAnnotation(__coordinate: CLLocationCoordinate2D(latitude: 43.585563, longitude: -79.540732), title: "Marie Curtis Park East Beach", subtitle: "Image Test"), 20 | MKPointAnnotation(__coordinate: CLLocationCoordinate2D(latitude: 43.637432, longitude: -79.455954), title: "Sunnyside Beach", subtitle: "Scroll Test"), 21 | MKPointAnnotation(__coordinate: CLLocationCoordinate2D(latitude: 43.619325, longitude: -79.393254), title: "Hanlan\'s Point Beach", subtitle: "Button Test"), 22 | MKPointAnnotation(__coordinate: CLLocationCoordinate2D(latitude: 43.66381, longitude: -79.305057), title: "Woodbine Beaches", subtitle: "Right Callout Test"), 23 | MKPointAnnotation(__coordinate: CLLocationCoordinate2D(latitude: 43.71363, longitude: -79.225948), title: "", subtitle: "") 24 | 25 | ] 26 | 27 | func makeUIView(context: UIViewRepresentableContext) -> MKMapView { 28 | let mapView = MKMapView() 29 | //center the map in toronto 30 | // set coordinates (lat lon) 31 | let coords = CLLocationCoordinate2D(latitude: 43.7288, longitude: -79.3562) 32 | // set span (radius of points) 33 | let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5) 34 | // set region 35 | let region = MKCoordinateRegion(center: coords, span: span) 36 | mapView.setRegion(region, animated: false) 37 | 38 | mapView.delegate = context.coordinator 39 | return mapView 40 | } 41 | 42 | func updateUIView(_ view: MKMapView, context: UIViewRepresentableContext) { 43 | 44 | //add anottations here 45 | for sample in sampleData { 46 | view.addAnnotation(sample) 47 | } 48 | } 49 | 50 | func makeCoordinator() -> Coordinator { 51 | Coordinator(self) 52 | } 53 | 54 | 55 | 56 | class Coordinator: NSObject, MKMapViewDelegate { 57 | var parent: MapView 58 | 59 | init(_ parent: MapView) { 60 | self.parent = parent 61 | } 62 | 63 | func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { 64 | //print(mapView.centerCoordinate) 65 | } 66 | 67 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 68 | 69 | let identifier = "beach" 70 | 71 | // attempt to find a cell we can recycle 72 | var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView 73 | 74 | 75 | if(view == nil) { 76 | // we didn't find one; make a new one 77 | view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier) 78 | view?.subtitleVisibility = .visible 79 | view?.displayPriority = .required 80 | view?.canShowCallout = true 81 | view?.titleVisibility = .hidden 82 | 83 | let annotation = view?.annotation 84 | let callout: MapCalloutView 85 | 86 | switch annotation?.subtitle { 87 | case "Image Test": 88 | let customView = TestImage(imageName: "marvin") 89 | callout = MapCalloutView(rootView: AnyView(customView)) 90 | case "Scroll Test": 91 | let customView = TestScrollView() 92 | callout = MapCalloutView(rootView: AnyView(customView)) 93 | case "Button Test": 94 | let customView = TestButtonView() 95 | callout = MapCalloutView(rootView: AnyView(customView)) 96 | case "Right Callout Test": 97 | let customView = TestImage(imageName: "kitten") 98 | view?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) 99 | callout = MapCalloutView(rootView: AnyView(customView)) 100 | default: 101 | let customView = Text("The view when passing no title") 102 | callout = MapCalloutView(rootView: AnyView(customView)) 103 | } 104 | 105 | view?.detailCalloutAccessoryView = callout 106 | 107 | } else { 108 | // we have a view to reuse, so give it the new annotation 109 | view?.annotation = annotation 110 | } 111 | 112 | return view 113 | } 114 | 115 | func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { 116 | 117 | print("Call out control tapped") 118 | 119 | } 120 | } 121 | 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Test Views/ButtonTestView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonTestView.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ButtonTestView: View { 12 | var body: some View { 13 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) 14 | } 15 | } 16 | 17 | struct ButtonTestView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | ButtonTestView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Test Views/TestButtonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestButtonView.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TestButtonView: View { 12 | var body: some View { 13 | 14 | VStack { 15 | NavigationLink(destination: TestScrollView() ) { 16 | Text("NavigationLinks within the SwiftUI View don't work") 17 | } 18 | Text("Not surprising, since technically this view isn't inside a Navigation View?") 19 | 20 | 21 | Button(action: { 22 | UIApplication.shared.open(URL(string: "https://twitter.com/khuffie")!) 23 | }) { 24 | Text("Regular Button's seem to work") 25 | .padding() 26 | } 27 | 28 | } 29 | } 30 | } 31 | 32 | struct TestButtonView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | TestButtonView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Test Views/TestImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestImage.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TestImage: View { 12 | 13 | var imageName: String 14 | 15 | var body: some View { 16 | Image(imageName) 17 | .resizable() 18 | .scaledToFit() 19 | } 20 | } 21 | 22 | struct TestImage_Previews: PreviewProvider { 23 | static var previews: some View { 24 | TestImage(imageName: "marvin") 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SwiftUI View Inside a MapKit Callout/Test Views/TestScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestScrollView.swift 3 | // SwiftUI View Inside a MapKit Callout 4 | // 5 | // Created by Ahmed El-Khuffash on 2020-05-31. 6 | // Copyright © 2020 Ahmed El-Khuffash. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TestScrollView: View { 12 | var body: some View { 13 | ScrollView { 14 | Text(" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin imperdiet tempor pellentesque. Nam tempus nunc vitae odio rhoncus euismod. Aenean sit amet nisi in nunc ornare consectetur. Quisque et fringilla dolor. Aenean quis diam commodo, varius massa id, suscipit ante. Duis sed eros a lorem mollis pretium. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. ") 15 | 16 | Text(" Curabitur non urna ex. Aliquam porta, nisi eget accumsan aliquam, elit dui suscipit neque, rhoncus varius urna quam eu nisl. Vestibulum lobortis molestie arcu, id scelerisque magna viverra et. Sed et euismod velit, nec ornare felis. Phasellus libero sem, aliquam sed dolor eu, placerat ornare sem. Etiam turpis erat, blandit ut pulvinar vitae, varius fringilla purus. Cras ut gravida neque. Etiam sit amet condimentum elit. Morbi varius eros in luctus facilisis. Vivamus et nunc non orci gravida bibendum. Proin sit amet congue augue. Vivamus iaculis consequat dignissim. Phasellus non pharetra leo. Proin vel dui eu elit gravida rhoncus quis eu ante. Suspendisse vestibulum, ipsum et mollis vestibulum, velit velit placerat tortor, et posuere augue lorem ut magna. ") 17 | 18 | Text(" Praesent ultrices ex vel eros venenatis, dapibus euismod diam pellentesque. Sed accumsan eros tellus, vel efficitur nulla sodales nec. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin imperdiet augue tellus, sed ornare diam auctor ac. Suspendisse sit amet ullamcorper risus. Nullam et lorem arcu. Phasellus mi mi, tempus ac tempor vitae, volutpat quis mi. Proin scelerisque neque dolor, sit amet vulputate nisi bibendum porttitor. Etiam aliquam dignissim feugiat. Maecenas nec nisi in eros malesuada pretium eu tempor quam. Etiam est diam, scelerisque vitae libero nec, volutpat interdum justo. Nunc placerat, metus non tristique tristique, ante erat semper enim, quis vulputate est velit malesuada sem. Nullam nec mauris vitae dui pharetra semper vitae nec quam. ") 19 | } 20 | .frame(maxHeight: 100) 21 | } 22 | } 23 | 24 | struct TestScrollView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | TestScrollView() 27 | } 28 | } 29 | --------------------------------------------------------------------------------