├── .gitignore ├── README.md └── SwiftUI.playground ├── Contents.swift ├── Resources ├── chilkoottrail.jpg ├── chincoteague.jpg ├── hiddenlake.jpg ├── icybay.jpg ├── lakemcdonald.jpg ├── landmarkData.json ├── rainbowlake.jpg ├── silversalmoncreek.jpg ├── stmarylake.jpg ├── turtlerock.jpg ├── twinlake.jpg ├── umbagog.jpg └── yukon_charleyrivers.jpg ├── Sources ├── LICENSE │ └── LICENSE.txt ├── LandmarkDetail.swift ├── LandmarkList.swift ├── Models │ ├── Data.swift │ └── Landmark.swift ├── PlaygroundRootView.swift └── Supporting Views │ ├── CircleImage.swift │ ├── LandmarkRow.swift │ └── MapView.swift └── contents.xcplayground /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift,xcode 3 | # Edit at https://www.gitignore.io/?templates=swift,xcode 4 | 5 | ### Swift ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | # Package.pins 44 | # Package.resolved 45 | .build/ 46 | 47 | # CocoaPods 48 | # We recommend against adding the Pods directory to your .gitignore. However 49 | # you should judge for yourself, the pros and cons are mentioned at: 50 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 51 | # Pods/ 52 | # Add this line if you want to avoid checking in source code from the Xcode workspace 53 | # *.xcworkspace 54 | 55 | # Carthage 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 63 | # screenshots whenever they are needed. 64 | # For more information about the recommended setup visit: 65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 66 | 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots/**/*.png 70 | fastlane/test_output 71 | 72 | # Code Injection 73 | # After new code Injection tools there's a generated folder /iOSInjectionProject 74 | # https://github.com/johnno1962/injectionforxcode 75 | 76 | iOSInjectionProject/ 77 | 78 | ### Xcode ### 79 | # Xcode 80 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 81 | 82 | ## User settings 83 | 84 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 85 | 86 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 87 | 88 | ### Xcode Patch ### 89 | *.xcodeproj/* 90 | !*.xcodeproj/project.pbxproj 91 | !*.xcodeproj/xcshareddata/ 92 | !*.xcworkspace/contents.xcworkspacedata 93 | /*.gcno 94 | **/xcshareddata/WorkspaceSettings.xcsettings 95 | 96 | # End of https://www.gitignore.io/api/swift,xcode 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Playground for macOS Mojave 2 | 3 | Apple announced their new **SwiftUI** Framework at WWDC 2019. 4 | 5 | While it requires **macOS Catalina beta** to be run natively, it can already be used in Swift Playgrounds on the current stable version, **macOS Mojave**. 6 | 7 | This project provides a Playground version of Apple’s [**Building Lists and Navigation** tutorial](https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation). [Xcode 11 beta](https://developer.apple.com/download/) is required. 8 | 9 | ## Implementation 10 | 11 | A `UIHostingController` can be used in a Playground Live View like this: 12 | 13 | ```swift 14 | PlaygroundPage.current.liveView = UIHostingController(rootView: PlaygroundRootView()) 15 | ``` 16 | 17 | The given root view must be `public`, along with its `init()` and `body`: 18 | 19 | ```swift 20 | public struct PlaygroundRootView: View { 21 | public init() {} 22 | 23 | public var body: some View { 24 | LandmarkList() 25 | } 26 | } 27 | ```` 28 | 29 | From here on, standard SwiftUI Views can be used to build the interface. 30 | 31 | ## Execution 32 | 33 | Press the blue Play button next to the `PlaygroundPage.current.liveView` line or type **⇧⌘↵** to run the Playground. If, after the initial build, the preview is not responsive, just press the Play button again. 34 | 35 | ## Navigation 36 | 37 | Use **View > Navigator > Show Project Navigator** or **⌘1** to show the Playground’s source files and recources. 38 | -------------------------------------------------------------------------------- /SwiftUI.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PlaygroundSupport 3 | 4 | // Present the view controller in the Live View window 5 | PlaygroundPage.current.liveView = UIHostingController(rootView: PlaygroundRootView()) 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/chilkoottrail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/chilkoottrail.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/chincoteague.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/chincoteague.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/hiddenlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/hiddenlake.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/icybay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/icybay.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/lakemcdonald.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/lakemcdonald.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/landmarkData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Turtle Rock", 4 | "category": "Featured", 5 | "city": "Twentynine Palms", 6 | "state": "California", 7 | "id": 1001, 8 | "park": "Joshua Tree National Park", 9 | "coordinates": { 10 | "longitude": -116.166868, 11 | "latitude": 34.011286 12 | }, 13 | "imageName": "turtlerock" 14 | }, 15 | { 16 | "name": "Silver Salmon Creek", 17 | "category": "Lakes", 18 | "city": "Port Alsworth", 19 | "state": "Alaska", 20 | "id": 4, 21 | "park": "Lake Clark National Park and Preserve", 22 | "coordinates": { 23 | "longitude": -152.665167, 24 | "latitude": 59.980167 25 | }, 26 | "imageName": "silversalmoncreek" 27 | }, 28 | { 29 | "name": "Chilkoot Trail", 30 | "category": "Rivers", 31 | "city": "Skagway", 32 | "state": "Alaska", 33 | "id": 5, 34 | "park": "Klondike Gold Rush National Historical Park", 35 | "coordinates": { 36 | "longitude": -135.334571, 37 | "latitude": 59.560551 38 | }, 39 | "imageName": "chilkoottrail" 40 | }, 41 | { 42 | "name": "St. Mary Lake", 43 | "category": "Lakes", 44 | "city": "Browning", 45 | "state": "Montana", 46 | "id": 8, 47 | "park": "Glacier National Park", 48 | "coordinates": { 49 | "longitude": -113.536248, 50 | "latitude": 48.69423 51 | }, 52 | "imageName": "stmarylake" 53 | }, 54 | { 55 | "name": "Twin Lake", 56 | "category": "Lakes", 57 | "city": "Twin Lakes", 58 | "state": "Alaska", 59 | "id": 11, 60 | "park": "Lake Clark National Park and Preserve", 61 | "coordinates": { 62 | "longitude": -153.849883, 63 | "latitude": 60.641684 64 | }, 65 | "imageName": "twinlake" 66 | }, 67 | { 68 | "name": "Lake McDonald", 69 | "category": "Lakes", 70 | "city": "West Glacier", 71 | "state": "Montana", 72 | "id": 6, 73 | "park": "Glacier National Park", 74 | "coordinates": { 75 | "longitude": -113.934831, 76 | "latitude": 48.56002 77 | }, 78 | "imageName": "lakemcdonald" 79 | }, 80 | { 81 | "name": "Charley Rivers", 82 | "category": "Rivers", 83 | "city": "Eaking", 84 | "state": "Alaska", 85 | "id": 1, 86 | "park": "Charley Rivers National Preserve", 87 | "coordinates": { 88 | "longitude": -143.122586, 89 | "latitude": 65.350021 90 | }, 91 | "imageName": "yukon_charleyrivers" 92 | }, 93 | { 94 | "name": "Icy Bay", 95 | "category": "Lakes", 96 | "city": "Icy Bay", 97 | "state": "Alaska", 98 | "id": 2, 99 | "park": "Wrangell-St. Elias National Park and Preserve", 100 | "coordinates": { 101 | "longitude": -141.518167, 102 | "latitude": 60.089917 103 | }, 104 | "imageName": "icybay" 105 | }, 106 | { 107 | "name": "Rainbow Lake", 108 | "category": "Lakes", 109 | "city": "Willow", 110 | "state": "Alaska", 111 | "id": 3, 112 | "park": "State Recreation Area", 113 | "coordinates": { 114 | "longitude": -150.086103, 115 | "latitude": 61.694334 116 | }, 117 | "imageName": "rainbowlake" 118 | }, 119 | { 120 | "name": "Hidden Lake", 121 | "category": "Lakes", 122 | "city": "Newhalem", 123 | "state": "Washington", 124 | "id": 7, 125 | "park": "North Cascades National Park", 126 | "coordinates": { 127 | "longitude": -121.17799, 128 | "latitude": 48.495442 129 | }, 130 | "imageName": "hiddenlake" 131 | }, 132 | { 133 | "name": "Chincoteague", 134 | "category": "Rivers", 135 | "city": "Chincoteague", 136 | "state": "Virginia", 137 | "id": 9, 138 | "park": "Chincoteague National Wildlife Refuge", 139 | "coordinates": { 140 | "longitude": -75.383212, 141 | "latitude": 37.91531 142 | }, 143 | "imageName": "chincoteague" 144 | }, 145 | { 146 | "name": "Lake Umbagog", 147 | "category": "Lakes", 148 | "city": "Errol", 149 | "state": "New Hampshire", 150 | "id": 10, 151 | "park": "Umbagog National Wildlife Refuge", 152 | "coordinates": { 153 | "longitude": -71.056816, 154 | "latitude": 44.747408 155 | }, 156 | "imageName": "umbagog" 157 | } 158 | ] 159 | -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/rainbowlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/rainbowlake.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/silversalmoncreek.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/silversalmoncreek.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/stmarylake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/stmarylake.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/turtlerock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/turtlerock.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/twinlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/twinlake.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/umbagog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/umbagog.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Resources/yukon_charleyrivers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attoPascal/SwiftUI-Tutorial-Playground/eab2e8fda4d666e4e412e9a0d12e8ed949083a91/SwiftUI.playground/Resources/yukon_charleyrivers.jpg -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/LICENSE/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2019 Apple Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/LandmarkDetail.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view showing the details for a landmark. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct LandmarkDetail: View { 11 | var landmark: Landmark 12 | 13 | var body: some View { 14 | VStack { 15 | MapView(coordinate: landmark.locationCoordinate) 16 | .frame(height: 300) 17 | 18 | CircleImage(image: landmark.image(forSize: 250)) 19 | .offset(x: 0, y: -130) 20 | .padding(.bottom, -130) 21 | 22 | VStack(alignment: .leading) { 23 | Text(landmark.name) 24 | .font(.title) 25 | 26 | HStack(alignment: .top) { 27 | Text(landmark.park) 28 | .font(.subheadline) 29 | Spacer() 30 | Text(landmark.state) 31 | .font(.subheadline) 32 | } 33 | } 34 | .padding() 35 | 36 | Spacer() 37 | } 38 | .navigationBarTitle(Text(verbatim: landmark.name), displayMode: .inline) 39 | } 40 | } 41 | 42 | #if DEBUG 43 | struct LandmarkDetail_Previews: PreviewProvider { 44 | static var previews: some View { 45 | LandmarkDetail(landmark: landmarkData[0]) 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/LandmarkList.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view showing a list of landmarks. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct LandmarkList: View { 11 | var body: some View { 12 | NavigationView { 13 | List(landmarkData) { landmark in 14 | NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 15 | LandmarkRow(landmark: landmark) 16 | } 17 | } 18 | .navigationBarTitle(Text("Landmarks"), displayMode: .large) 19 | } 20 | } 21 | } 22 | 23 | #if DEBUG 24 | struct LandmarkList_Previews: PreviewProvider { 25 | static var previews: some View { 26 | ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName in 27 | LandmarkList() 28 | .previewDevice(PreviewDevice(rawValue: deviceName)) 29 | .previewDisplayName(deviceName) 30 | } 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/Models/Data.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Helpers for loading images and data. 6 | */ 7 | 8 | import UIKit 9 | import SwiftUI 10 | import CoreLocation 11 | 12 | let landmarkData: [Landmark] = load("landmarkData.json") 13 | 14 | func load(_ filename: String, as type: T.Type = T.self) -> T { 15 | let data: Data 16 | 17 | guard let file = Bundle.main.url(forResource: filename, withExtension: nil) 18 | else { 19 | fatalError("Couldn't find \(filename) in main bundle.") 20 | } 21 | 22 | do { 23 | data = try Data(contentsOf: file) 24 | } catch { 25 | fatalError("Couldn't load \(filename) from main bundle:\n\(error)") 26 | } 27 | 28 | do { 29 | let decoder = JSONDecoder() 30 | return try decoder.decode(T.self, from: data) 31 | } catch { 32 | fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") 33 | } 34 | } 35 | 36 | final class ImageStore { 37 | fileprivate typealias _ImageDictionary = [String: [Int: CGImage]] 38 | fileprivate var images: _ImageDictionary = [:] 39 | 40 | fileprivate static var originalSize = 250 41 | fileprivate static var scale = 2 42 | 43 | static var shared = ImageStore() 44 | 45 | func image(name: String, size: Int) -> Image { 46 | let index = _guaranteeInitialImage(name: name) 47 | 48 | let sizedImage = images.values[index][size] 49 | ?? _sizeImage(images.values[index][ImageStore.originalSize]!, to: size * ImageStore.scale) 50 | images.values[index][size] = sizedImage 51 | 52 | return Image(sizedImage, scale: Length(ImageStore.scale), label: Text(verbatim: name)) 53 | } 54 | 55 | fileprivate func _guaranteeInitialImage(name: String) -> _ImageDictionary.Index { 56 | if let index = images.index(forKey: name) { return index } 57 | 58 | guard 59 | let url = Bundle.main.url(forResource: name, withExtension: "jpg"), 60 | let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil), 61 | let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) 62 | else { 63 | fatalError("Couldn't load image \(name).jpg from main bundle.") 64 | } 65 | 66 | images[name] = [ImageStore.originalSize: image] 67 | return images.index(forKey: name)! 68 | } 69 | 70 | fileprivate func _sizeImage(_ image: CGImage, to size: Int) -> CGImage { 71 | guard 72 | let colorSpace = image.colorSpace, 73 | let context = CGContext( 74 | data: nil, 75 | width: size, height: size, 76 | bitsPerComponent: image.bitsPerComponent, 77 | bytesPerRow: image.bytesPerRow, 78 | space: colorSpace, 79 | bitmapInfo: image.bitmapInfo.rawValue) 80 | else { 81 | fatalError("Couldn't create graphics context.") 82 | } 83 | context.interpolationQuality = .high 84 | context.draw(image, in: CGRect(x: 0, y: 0, width: size, height: size)) 85 | 86 | if let sizedImage = context.makeImage() { 87 | return sizedImage 88 | } else { 89 | fatalError("Couldn't resize image.") 90 | } 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/Models/Landmark.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | The model for an individual landmark. 6 | */ 7 | 8 | import SwiftUI 9 | import CoreLocation 10 | 11 | struct Landmark: Hashable, Codable, Identifiable { 12 | var id: Int 13 | var name: String 14 | fileprivate var imageName: String 15 | fileprivate var coordinates: Coordinates 16 | var state: String 17 | var park: String 18 | var category: Category 19 | 20 | var locationCoordinate: CLLocationCoordinate2D { 21 | CLLocationCoordinate2D( 22 | latitude: coordinates.latitude, 23 | longitude: coordinates.longitude) 24 | } 25 | 26 | func image(forSize size: Int) -> Image { 27 | ImageStore.shared.image(name: imageName, size: size) 28 | } 29 | 30 | enum Category: String, CaseIterable, Codable, Hashable { 31 | case featured = "Featured" 32 | case lakes = "Lakes" 33 | case rivers = "Rivers" 34 | } 35 | } 36 | 37 | struct Coordinates: Hashable, Codable { 38 | var latitude: Double 39 | var longitude: Double 40 | } 41 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/PlaygroundRootView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Abstract: 3 | A public view with public initializer and content to be used as the root view in a Playground 4 | */ 5 | 6 | import SwiftUI 7 | 8 | public struct PlaygroundRootView: View { 9 | public init() {} 10 | 11 | public var body: some View { 12 | // set the SwiftUI root view here: 13 | LandmarkList() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/Supporting Views/CircleImage.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that clips an image to a circle and adds a stroke and shadow. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct CircleImage: View { 11 | var image: Image 12 | 13 | var body: some View { 14 | image 15 | .clipShape(Circle()) 16 | .overlay(Circle().stroke(Color.white, lineWidth: 4)) 17 | .shadow(radius: 10) 18 | } 19 | } 20 | 21 | #if DEBUG 22 | struct CircleImage_Previews: PreviewProvider { 23 | static var previews: some View { 24 | CircleImage(image: Image("turtlerock")) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/Supporting Views/LandmarkRow.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A single row to be displayed in a list of landmarks. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct LandmarkRow: View { 11 | var landmark: Landmark 12 | 13 | var body: some View { 14 | HStack { 15 | landmark.image(forSize: 50) 16 | Text(verbatim: landmark.name) 17 | Spacer() 18 | } 19 | } 20 | } 21 | 22 | #if DEBUG 23 | struct LandmarkRow_Previews: PreviewProvider { 24 | static var previews: some View { 25 | Group { 26 | LandmarkRow(landmark: landmarkData[0]) 27 | LandmarkRow(landmark: landmarkData[1]) 28 | } 29 | .previewLayout(.fixed(width: 300, height: 70)) 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /SwiftUI.playground/Sources/Supporting Views/MapView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that hosts an `MKMapView`. 6 | */ 7 | 8 | import SwiftUI 9 | import MapKit 10 | 11 | struct MapView: UIViewRepresentable { 12 | var coordinate: CLLocationCoordinate2D 13 | 14 | func makeUIView(context: Context) -> MKMapView { 15 | MKMapView(frame: .zero) 16 | } 17 | 18 | func updateUIView(_ view: MKMapView, context: Context) { 19 | let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02) 20 | let region = MKCoordinateRegion(center: coordinate, span: span) 21 | view.setRegion(region, animated: true) 22 | } 23 | } 24 | 25 | #if DEBUG 26 | struct MapView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | MapView(coordinate: landmarkData[0].locationCoordinate) 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /SwiftUI.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --------------------------------------------------------------------------------