├── .gitignore ├── Makefile ├── README.md ├── SwiftUIPlayground.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── SwiftUIPlayground ├── AppleSwiftUITutorials │ └── Landmarks │ │ ├── Hike │ │ ├── GraphCapsule.swift │ │ ├── HexagonParameters.swift │ │ ├── HikeBadge.swift │ │ ├── HikeDetail.swift │ │ ├── HikeGraph.swift │ │ └── HikeView.swift │ │ ├── Home │ │ ├── CategoryRow.swift │ │ ├── Home.swift │ │ ├── PageControl.swift │ │ ├── PageView.swift │ │ └── PageViewController.swift │ │ ├── Landmark Views │ │ ├── LandmarkDetail.swift │ │ ├── LandmarkList.swift │ │ └── LandmarkRow.swift │ │ ├── Models │ │ ├── Data.swift │ │ ├── Hike.swift │ │ ├── Landmark.swift │ │ ├── Profile.swift │ │ └── UserData.swift │ │ ├── Profile │ │ ├── ProfileEditor.swift │ │ ├── ProfileHost.swift │ │ └── ProfileSummary.swift │ │ ├── Resources │ │ ├── charleyrivers.jpg │ │ ├── charleyrivers_feature.jpg │ │ ├── chilkoottrail.jpg │ │ ├── chincoteague.jpg │ │ ├── hiddenlake.jpg │ │ ├── hikeData.json │ │ ├── icybay.jpg │ │ ├── lakemcdonald.jpg │ │ ├── landmarkData.json │ │ ├── rainbowlake.jpg │ │ ├── silversalmoncreek.jpg │ │ ├── stmarylake.jpg │ │ ├── stmarylake_feature.jpg │ │ ├── turtlerock.jpg │ │ ├── turtlerock_feature.jpg │ │ ├── twinlake.jpg │ │ └── umbagog.jpg │ │ └── Supporting Views │ │ ├── Badge.swift │ │ ├── BadgeBackground.swift │ │ ├── BadgeSymbol.swift │ │ ├── CircleImage.swift │ │ ├── FeatureCard.swift │ │ ├── MapView.swift │ │ └── RotatedBadgeSymbol.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── ButtonColor.colorset │ │ └── Contents.json │ ├── ButtonColorGradient.colorset │ │ └── Contents.json │ ├── Contents.json │ ├── cookies.imageset │ │ ├── 1599px-Chocolate_chip_cookies.jpg │ │ └── Contents.json │ └── turtlerock.imageset │ │ ├── Contents.json │ │ └── turtlerock.jpg ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Convenience │ ├── Array+Indexed.swift │ └── Date+Extensions.swift ├── Info.plist ├── Models │ ├── CoreDataStack.swift │ ├── PlaygroundModel.xcdatamodeld │ │ └── PlaygroundModel.xcdatamodel │ │ │ └── contents │ └── Todo+CoreDataClass.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SwiftUIPlayground.swift └── Views │ ├── AccountView.swift │ ├── ActivityIndicatorView.swift │ ├── AlertExampleView.swift │ ├── AllWidgetsView.swift │ ├── ButtonExampleView.swift │ ├── CameraButtonView.swift │ ├── ChartExample.swift │ ├── ChartExampleView.swift │ ├── ClipImageSquareView.swift │ ├── CloseButtonExampleView.swift │ ├── CollectionViewExample.swift │ ├── ColorCycleView.swift │ ├── ConditionalDatePickerView.swift │ ├── ContactFormView.swift │ ├── CustomAlertView.swift │ ├── DarkModeExampleView.swift │ ├── DashboardView.swift │ ├── DatePickerExampleView.swift │ ├── DragGestureView.swift │ ├── EditButtonExampleView.swift │ ├── EditableListExample.swift │ ├── EnumeratedListView.swift │ ├── EnvironmentView.swift │ ├── Frameworks │ ├── Core Data │ │ ├── Binding+OptionalFallback.swift │ │ ├── TodoView.swift │ │ └── TodosView.swift │ ├── Core Image │ │ └── ImageEffectView.swift │ ├── Foundation │ │ └── TodosURLSessionExampleView.swift │ ├── MessageUI │ │ └── MailView.swift │ └── PencilKit │ │ └── PencilKitView.swift │ ├── ImageExampleView.swift │ ├── ImagePickerView.swift │ ├── KeyboardAwareView.swift │ ├── Layout │ ├── LayoutSameHeightView.swift │ └── RelativePositionExampleView.swift │ ├── LinkedStateChangeView.swift │ ├── ListExampleView.swift │ ├── MainView.swift │ ├── MenuButtonExampleView.swift │ ├── ModalPickerView.swift │ ├── NavigationViewExample.swift │ ├── OptionalsExampleView.swift │ ├── PickerExampleView.swift │ ├── PlayersProgressView.swift │ ├── PlaygroundView.swift │ ├── ProgrammaticNavigationExampleView.swift │ ├── QRCodeScannerExampleView.swift │ ├── SceneViewExample.swift │ ├── ScrollViewExample.swift │ ├── SelectionEffectView.swift │ ├── ShadowsView.swift │ ├── ShareSheetExampleView.swift │ ├── SimpleGridViewExample.swift │ ├── SizePreferenceExampleView.swift │ ├── SizedUIViewRepresentableView.swift │ ├── SliderExampleView.swift │ ├── StateExampleView.swift │ ├── StepperExampleView.swift │ ├── StrangeStateGlitchExampleView copy.swift │ ├── SwiftUISwitchView.swift │ ├── TextExampleView.swift │ ├── TextFieldExampleView.swift │ ├── ToggleExampleView.swift │ ├── UserDefaultsExampleView.swift │ └── art.scnassets │ ├── ship.scn │ └── texture.png └── docs ├── Button.png ├── DatePicker.png ├── EditButton.png ├── Form.png ├── Image.png ├── List.png ├── NavigationView.png ├── Picker.png ├── ScrollView.png ├── Section.png ├── SecureField.png ├── Slider.png ├── Stepper.png ├── TabView.png ├── Text.png ├── TextField.png ├── Toggle.png ├── overview.png ├── views.csv └── views.rb /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FORMATTER_OPTIONS = --swiftversion 5.2 --stripunusedargs unnamed-only --self insert --disable blankLinesAtStartOfScope,blankLinesAtEndOfScope --ifdef no-indent 2 | 3 | format: 4 | swiftformat --header "// SwiftUIPlayground\n// https://github.com/ralfebert/SwiftUIPlayground/" $(FORMATTER_OPTIONS) --exclude SwiftUIPlayground/AppleSwiftUITutorials SwiftUIPlayground* 5 | swiftformat $(FORMATTER_OPTIONS) SwiftUIPlayground/AppleSwiftUITutorials 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUIPlayground 2 | 3 | SwiftUIPlayground is a collection of SwiftUI example snippets: 4 | 5 | SwiftUI view | UIKit View | Example 6 | ---------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------- 7 | [Button][swiftui-button] | [UIButton][uikit-uibutton] | [][example-button] 8 | [DatePicker][swiftui-datepicker] | [UIDatePicker][uikit-uidatepicker] | [][example-datepicker] 9 | [EditButton][swiftui-editbutton] | | [][example-editbutton] 10 | [Form][swiftui-form] | | [][example-form] 11 | [Image][swiftui-image] | [UIImageView][uikit-uiimageview] | [][example-image] 12 | [List][swiftui-list] | [UITableView][uikit-uitableview] | [][example-list] 13 | [NavigationView][swiftui-navigationview] | [UINavigationController][uikit-uinavigationcontroller] | [][example-navigationview] 14 | [Picker][swiftui-picker] | [UIPickerView][uikit-uipickerview] | [][example-picker] 15 | [ScrollView][swiftui-scrollview] | [UIScrollView][uikit-uiscrollview] | [][example-scrollview] 16 | [Section][swiftui-section] | | [][example-section] 17 | [SecureField][swiftui-securefield] | [UITextField][uikit-uitextfield] | [][example-securefield] 18 | [Slider][swiftui-slider] | [UISlider][uikit-uislider] | [][example-slider] 19 | [Stepper][swiftui-stepper] | [UIStepper][uikit-uistepper] | [][example-stepper] 20 | [TabView][swiftui-tabview] | [UITabBarController][uikit-uitabbarcontroller] | [][example-tabview] 21 | [Text][swiftui-text] | [UILabel][uikit-uilabel] | [][example-text] 22 | [TextField][swiftui-textfield] | [UITextField][uikit-uitextfield] | [][example-textfield] 23 | [Toggle][swiftui-toggle] | [UISwitch][uikit-uiswitch] | [][example-toggle] 24 | 25 | [swiftui-button]: https://developer.apple.com/documentation/swiftui/button 26 | [example-button]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ButtonExampleView.swift 27 | [uikit-uibutton]: https://developer.apple.com/documentation/uikit/uibutton 28 | [swiftui-datepicker]: https://developer.apple.com/documentation/swiftui/datepicker 29 | [example-datepicker]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/DatePickerExampleView.swift 30 | [uikit-uidatepicker]: https://developer.apple.com/documentation/uikit/uidatepicker 31 | [swiftui-editbutton]: https://developer.apple.com/documentation/swiftui/editbutton 32 | [example-editbutton]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/EditButtonExampleView.swift 33 | [swiftui-form]: https://developer.apple.com/documentation/swiftui/form 34 | [example-form]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ContactFormView.swift 35 | [swiftui-image]: https://developer.apple.com/documentation/swiftui/image 36 | [example-image]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ImageExampleView.swift 37 | [uikit-uiimageview]: https://developer.apple.com/documentation/uikit/uiimageview 38 | [swiftui-list]: https://developer.apple.com/documentation/swiftui/list 39 | [example-list]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ListExampleView.swift 40 | [uikit-uitableview]: https://developer.apple.com/documentation/uikit/uitableview 41 | [swiftui-navigationview]: https://developer.apple.com/documentation/swiftui/navigationview 42 | [example-navigationview]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/NavigationViewExample.swift 43 | [uikit-uinavigationcontroller]: https://developer.apple.com/documentation/uikit/uinavigationcontroller 44 | [swiftui-picker]: https://developer.apple.com/documentation/swiftui/picker 45 | [example-picker]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/PickerExampleView.swift 46 | [uikit-uipickerview]: https://developer.apple.com/documentation/uikit/uipickerview 47 | [swiftui-scrollview]: https://developer.apple.com/documentation/swiftui/scrollview 48 | [example-scrollview]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ScrollViewExample.swift 49 | [uikit-uiscrollview]: https://developer.apple.com/documentation/uikit/uiscrollview 50 | [swiftui-section]: https://developer.apple.com/documentation/swiftui/section 51 | [example-section]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ContactFormView.swift 52 | [swiftui-securefield]: https://developer.apple.com/documentation/swiftui/securefield 53 | [example-securefield]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/TextFieldExampleView.swift 54 | [uikit-uitextfield]: https://developer.apple.com/documentation/uikit/uitextfield 55 | [swiftui-slider]: https://developer.apple.com/documentation/swiftui/slider 56 | [example-slider]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/SliderExampleView.swift 57 | [uikit-uislider]: https://developer.apple.com/documentation/uikit/uislider 58 | [swiftui-stepper]: https://developer.apple.com/documentation/swiftui/stepper 59 | [example-stepper]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/StepperExampleView.swift 60 | [uikit-uistepper]: https://developer.apple.com/documentation/uikit/uistepper 61 | [swiftui-tabview]: https://developer.apple.com/documentation/swiftui/tabview 62 | [example-tabview]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/MainView.swift 63 | [uikit-uitabbarcontroller]: https://developer.apple.com/documentation/uikit/uitabbarcontroller 64 | [swiftui-text]: https://developer.apple.com/documentation/swiftui/text 65 | [example-text]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/TextExampleView.swift 66 | [uikit-uilabel]: https://developer.apple.com/documentation/uikit/uilabel 67 | [swiftui-textfield]: https://developer.apple.com/documentation/swiftui/textfield 68 | [example-textfield]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/TextFieldExampleView.swift 69 | [uikit-uitextfield]: https://developer.apple.com/documentation/uikit/uitextfield 70 | [swiftui-toggle]: https://developer.apple.com/documentation/swiftui/toggle 71 | [example-toggle]: https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/ToggleExampleView.swift 72 | [uikit-uiswitch]: https://developer.apple.com/documentation/uikit/uiswitch 73 | 74 | ## Image copyrights 75 | 76 | * Cookies: 77 | https://de.wikipedia.org/wiki/Datei:Chocolate_chip_cookies.jpg 78 | -------------------------------------------------------------------------------- /SwiftUIPlayground.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftUIPlayground.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUIPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CodeScanner", 6 | "repositoryURL": "https://github.com/twostraws/CodeScanner.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9d902dec616bd70b1a2a1b1e6d821c10c392ba9f", 10 | "version": "1.0.6" 11 | } 12 | }, 13 | { 14 | "package": "ImagePickerView", 15 | "repositoryURL": "https://github.com/ralfebert/ImagePickerView.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "b84dd1c85a65cddba40c89699cd63ba0cb248dbd", 19 | "version": "0.5.0" 20 | } 21 | }, 22 | { 23 | "package": "KeyboardAware", 24 | "repositoryURL": "https://github.com/ralfebert/KeyboardAwareSwiftUI.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "c76a2e1ee69b413885c604e62f39b796de97dcda", 28 | "version": "0.5.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Hike/GraphCapsule.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A single line in the graph. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct GraphCapsule: View { 11 | var index: Int 12 | var height: CGFloat 13 | var range: Range 14 | var overallRange: Range 15 | 16 | var heightRatio: CGFloat { 17 | max(CGFloat(magnitude(of: self.range) / magnitude(of: self.overallRange)), 0.15) 18 | } 19 | 20 | var offsetRatio: CGFloat { 21 | CGFloat((self.range.lowerBound - self.overallRange.lowerBound) / magnitude(of: self.overallRange)) 22 | } 23 | 24 | var body: some View { 25 | Capsule() 26 | .fill(Color.white) 27 | .frame(height: height * heightRatio) 28 | .offset(x: 0, y: height * -offsetRatio) 29 | } 30 | } 31 | 32 | struct GraphCapsule_Previews: PreviewProvider { 33 | static var previews: some View { 34 | GraphCapsule(index: 0, height: 150, range: 10 ..< 50, overallRange: 0 ..< 100) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Hike/HexagonParameters.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Size, position, and other information used to draw a badge. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct HexagonParameters { 11 | struct Segment { 12 | let useWidth: (CGFloat, CGFloat, CGFloat) 13 | let xFactors: (CGFloat, CGFloat, CGFloat) 14 | let useHeight: (CGFloat, CGFloat, CGFloat) 15 | let yFactors: (CGFloat, CGFloat, CGFloat) 16 | } 17 | 18 | static let adjustment: CGFloat = 0.085 19 | static let points = [ 20 | Segment( 21 | useWidth: (1.00, 1.00, 1.00), 22 | xFactors: (0.60, 0.40, 0.50), 23 | useHeight: (1.00, 1.00, 0.00), 24 | yFactors: (0.05, 0.05, 0.00) 25 | ), 26 | Segment( 27 | useWidth: (1.00, 1.00, 0.00), 28 | xFactors: (0.05, 0.00, 0.00), 29 | useHeight: (1.00, 1.00, 1.00), 30 | yFactors: (0.20 + adjustment, 0.30 + adjustment, 0.25 + adjustment) 31 | ), 32 | Segment( 33 | useWidth: (1.00, 1.00, 0.00), 34 | xFactors: (0.00, 0.05, 0.00), 35 | useHeight: (1.00, 1.00, 1.00), 36 | yFactors: (0.70 - adjustment, 0.80 - adjustment, 0.75 - adjustment) 37 | ), 38 | Segment( 39 | useWidth: (1.00, 1.00, 1.00), 40 | xFactors: (0.40, 0.60, 0.50), 41 | useHeight: (1.00, 1.00, 1.00), 42 | yFactors: (0.95, 0.95, 1.00) 43 | ), 44 | Segment( 45 | useWidth: (1.00, 1.00, 1.00), 46 | xFactors: (0.95, 1.00, 1.00), 47 | useHeight: (1.00, 1.00, 1.00), 48 | yFactors: (0.80 - adjustment, 0.70 - adjustment, 0.75 - adjustment) 49 | ), 50 | Segment( 51 | useWidth: (1.00, 1.00, 1.00), 52 | xFactors: (1.00, 0.95, 1.00), 53 | useHeight: (1.00, 1.00, 1.00), 54 | yFactors: (0.30 + adjustment, 0.20 + adjustment, 0.25 + adjustment) 55 | ), 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Hike/HikeBadge.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that shows a badge for hiking. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct HikeBadge: View { 11 | var name: String 12 | var body: some View { 13 | VStack(alignment: .center) { 14 | Badge() 15 | .frame(width: 300, height: 300) 16 | .scaleEffect(1.0 / 3.0) 17 | .frame(width: 100, height: 100) 18 | Text(name) 19 | .font(.caption) 20 | .accessibility(label: Text("Badge for \(name).")) 21 | } 22 | } 23 | } 24 | 25 | struct HikeBadge_Previews: PreviewProvider { 26 | static var previews: some View { 27 | HikeBadge(name: "Preview Testing") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Hike/HikeDetail.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view showing the details for a hike. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct HikeDetail: View { 11 | let hike: Hike 12 | @State var dataToShow = \Hike.Observation.elevation 13 | 14 | var buttons = [ 15 | ("Elevation", \Hike.Observation.elevation), 16 | ("Heart Rate", \Hike.Observation.heartRate), 17 | ("Pace", \Hike.Observation.pace), 18 | ] 19 | 20 | var body: some View { 21 | VStack { 22 | HikeGraph(hike: hike, path: dataToShow) 23 | .frame(height: 200) 24 | 25 | HStack(spacing: 25) { 26 | ForEach(buttons, id: \.0) { value in 27 | Button(action: { 28 | self.dataToShow = value.1 29 | }) { 30 | Text(value.0) 31 | .font(.system(size: 15)) 32 | .foregroundColor(value.1 == self.dataToShow 33 | ? Color.gray 34 | : Color.accentColor) 35 | .animation(nil) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | struct HikeDetail_Previews: PreviewProvider { 44 | static var previews: some View { 45 | HikeDetail(hike: hikeData[0]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Hike/HikeGraph.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | The elevation, heart rate, and pace of a hike plotted on a graph. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | func rangeOfRanges(_ ranges: C) -> Range 11 | where C.Element == Range 12 | { 13 | guard !ranges.isEmpty else { return 0 ..< 0 } 14 | let low = ranges.lazy.map(\.lowerBound).min()! 15 | let high = ranges.lazy.map(\.upperBound).max()! 16 | return low ..< high 17 | } 18 | 19 | func magnitude(of range: Range) -> Double { 20 | range.upperBound - range.lowerBound 21 | } 22 | 23 | extension Animation { 24 | static func ripple(index: Int) -> Animation { 25 | Animation.spring(dampingFraction: 0.5) 26 | .speed(2) 27 | .delay(0.03 * Double(index)) 28 | } 29 | } 30 | 31 | struct HikeGraph: View { 32 | var hike: Hike 33 | var path: KeyPath> 34 | 35 | var color: Color { 36 | switch self.path { 37 | case \.elevation: 38 | return .gray 39 | case \.heartRate: 40 | return Color(hue: 0, saturation: 0.5, brightness: 0.7) 41 | case \.pace: 42 | return Color(hue: 0.7, saturation: 0.4, brightness: 0.7) 43 | default: 44 | return .black 45 | } 46 | } 47 | 48 | var body: some View { 49 | let data = hike.observations 50 | let overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: self.path] }) 51 | let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()! 52 | let heightRatio = (1 - CGFloat(maxMagnitude / magnitude(of: overallRange))) / 2 53 | 54 | return GeometryReader { proxy in 55 | HStack(alignment: .bottom, spacing: proxy.size.width / 120) { 56 | ForEach(data.indices) { index in 57 | GraphCapsule( 58 | index: index, 59 | height: proxy.size.height, 60 | range: data[index][keyPath: self.path], 61 | overallRange: overallRange 62 | ) 63 | .colorMultiply(self.color) 64 | .transition(.slide) 65 | .animation(.ripple(index: index)) 66 | } 67 | .offset(x: 0, y: proxy.size.height * heightRatio) 68 | } 69 | } 70 | } 71 | } 72 | 73 | struct HikeGraph_Previews: PreviewProvider { 74 | static var previews: some View { 75 | Group { 76 | HikeGraph(hike: hikeData[0], path: \.elevation) 77 | .frame(height: 200) 78 | HikeGraph(hike: hikeData[0], path: \.heartRate) 79 | .frame(height: 200) 80 | HikeGraph(hike: hikeData[0], path: \.pace) 81 | .frame(height: 200) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Hike/HikeView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view displaying information about a hike, including an elevation graph. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct HikeView: View { 11 | var hike: Hike 12 | @State private var showDetail = false 13 | 14 | var transition: AnyTransition { 15 | let insertion = AnyTransition.move(edge: .trailing) 16 | .combined(with: .opacity) 17 | let removal = AnyTransition.scale 18 | .combined(with: .opacity) 19 | return .asymmetric(insertion: insertion, removal: removal) 20 | } 21 | 22 | var body: some View { 23 | VStack { 24 | HStack { 25 | HikeGraph(hike: hike, path: \.elevation) 26 | .frame(width: 50, height: 30) 27 | .animation(nil) 28 | 29 | VStack(alignment: .leading) { 30 | Text(hike.name) 31 | .font(.headline) 32 | Text(hike.distanceText) 33 | } 34 | 35 | Spacer() 36 | 37 | Button(action: { 38 | withAnimation { 39 | self.showDetail.toggle() 40 | } 41 | }) { 42 | Image(systemName: "chevron.right.circle") 43 | .imageScale(.large) 44 | .rotationEffect(.degrees(showDetail ? 90 : 0)) 45 | .scaleEffect(showDetail ? 1.5 : 1) 46 | .padding() 47 | } 48 | } 49 | 50 | if showDetail { 51 | HikeDetail(hike: hike) 52 | .transition(transition) 53 | } 54 | } 55 | } 56 | } 57 | 58 | struct HikeView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | VStack { 61 | HikeView(hike: hikeData[0]) 62 | .padding() 63 | Spacer() 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Home/CategoryRow.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view showing a scrollable list of landmarks. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct CategoryRow: View { 11 | var categoryName: String 12 | var items: [Landmark] 13 | 14 | var body: some View { 15 | VStack(alignment: .leading) { 16 | Text(self.categoryName) 17 | .font(.headline) 18 | .padding(.leading, 15) 19 | .padding(.top, 5) 20 | 21 | ScrollView(.horizontal, showsIndicators: false) { 22 | HStack(alignment: .top, spacing: 0) { 23 | ForEach(self.items) { landmark in 24 | NavigationLink( 25 | destination: LandmarkDetail( 26 | landmark: landmark 27 | ) 28 | ) { 29 | CategoryItem(landmark: landmark) 30 | } 31 | } 32 | } 33 | } 34 | .frame(height: 185) 35 | } 36 | } 37 | } 38 | 39 | struct CategoryItem: View { 40 | var landmark: Landmark 41 | var body: some View { 42 | VStack(alignment: .leading) { 43 | landmark.image 44 | .renderingMode(.original) 45 | .resizable() 46 | .frame(width: 155, height: 155) 47 | .cornerRadius(5) 48 | Text(landmark.name) 49 | .foregroundColor(.primary) 50 | .font(.caption) 51 | } 52 | .padding(.leading, 15) 53 | } 54 | } 55 | 56 | struct CategoryRow_Previews: PreviewProvider { 57 | static var previews: some View { 58 | CategoryRow( 59 | categoryName: landmarkData[0].category.rawValue, 60 | items: Array(landmarkData.prefix(4)) 61 | ) 62 | .environmentObject(UserData()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Home/Home.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view showing featured landmarks above a list of all of the landmarks. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct CategoryHome: View { 11 | var categories: [String: [Landmark]] { 12 | Dictionary( 13 | grouping: landmarkData, 14 | by: { $0.category.rawValue } 15 | ) 16 | } 17 | 18 | var featured: [Landmark] { 19 | landmarkData.filter { $0.isFeatured } 20 | } 21 | 22 | @State var showingProfile = false 23 | @EnvironmentObject var userData: UserData 24 | 25 | var profileButton: some View { 26 | Button(action: { self.showingProfile.toggle() }) { 27 | Image(systemName: "person.crop.circle") 28 | .imageScale(.large) 29 | .accessibility(label: Text("User Profile")) 30 | .padding() 31 | } 32 | } 33 | 34 | var body: some View { 35 | NavigationView { 36 | List { 37 | FeaturedLandmarks(landmarks: featured) 38 | .scaledToFill() 39 | .frame(height: 200) 40 | .clipped() 41 | .listRowInsets(EdgeInsets()) 42 | 43 | ForEach(categories.keys.sorted(), id: \.self) { key in 44 | CategoryRow(categoryName: key, items: self.categories[key]!) 45 | } 46 | .listRowInsets(EdgeInsets()) 47 | 48 | NavigationLink(destination: LandmarkList()) { 49 | Text("See All") 50 | } 51 | } 52 | .navigationBarTitle(Text("Featured")) 53 | .navigationBarItems(trailing: profileButton) 54 | .sheet(isPresented: $showingProfile) { 55 | ProfileHost() 56 | .environmentObject(self.userData) 57 | } 58 | } 59 | } 60 | } 61 | 62 | struct FeaturedLandmarks: View { 63 | var landmarks: [Landmark] 64 | var body: some View { 65 | landmarks[0].image.resizable() 66 | } 67 | } 68 | 69 | struct CategoryHome_Previews: PreviewProvider { 70 | static var previews: some View { 71 | CategoryHome() 72 | .environmentObject(UserData()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Home/PageControl.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view wrapping a UIPageControl. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | import UIKit 11 | 12 | struct PageControl: UIViewRepresentable { 13 | var numberOfPages: Int 14 | @Binding var currentPage: Int 15 | 16 | func makeCoordinator() -> Coordinator { 17 | Coordinator(self) 18 | } 19 | 20 | func makeUIView(context: Context) -> UIPageControl { 21 | let control = UIPageControl() 22 | control.numberOfPages = self.numberOfPages 23 | control.addTarget( 24 | context.coordinator, 25 | action: #selector(Coordinator.updateCurrentPage(sender:)), 26 | for: .valueChanged 27 | ) 28 | 29 | return control 30 | } 31 | 32 | func updateUIView(_ uiView: UIPageControl, context: Context) { 33 | uiView.currentPage = self.currentPage 34 | } 35 | 36 | class Coordinator: NSObject { 37 | var control: PageControl 38 | 39 | init(_ control: PageControl) { 40 | self.control = control 41 | } 42 | 43 | @objc 44 | func updateCurrentPage(sender: UIPageControl) { 45 | self.control.currentPage = sender.currentPage 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Home/PageView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view for bridging a UIPageViewController. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct PageView: View { 11 | var viewControllers: [UIHostingController] 12 | @State var currentPage = 0 13 | 14 | init(_ views: [Page]) { 15 | self.viewControllers = views.map { UIHostingController(rootView: $0) } 16 | } 17 | 18 | var body: some View { 19 | ZStack(alignment: .bottomTrailing) { 20 | PageViewController(controllers: viewControllers, currentPage: $currentPage) 21 | PageControl(numberOfPages: viewControllers.count, currentPage: $currentPage) 22 | .padding(.trailing) 23 | } 24 | } 25 | } 26 | 27 | struct PageView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | PageView(features.map { FeatureCard(landmark: $0) }) 30 | .aspectRatio(3 / 2, contentMode: .fit) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Home/PageViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that wraps a UIPageViewController. 6 | */ 7 | 8 | import SwiftUI 9 | import UIKit 10 | 11 | struct PageViewController: UIViewControllerRepresentable { 12 | var controllers: [UIViewController] 13 | @Binding var currentPage: Int 14 | 15 | func makeCoordinator() -> Coordinator { 16 | Coordinator(self) 17 | } 18 | 19 | func makeUIViewController(context: Context) -> UIPageViewController { 20 | let pageViewController = UIPageViewController( 21 | transitionStyle: .scroll, 22 | navigationOrientation: .horizontal 23 | ) 24 | pageViewController.dataSource = context.coordinator 25 | pageViewController.delegate = context.coordinator 26 | 27 | return pageViewController 28 | } 29 | 30 | func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) { 31 | pageViewController.setViewControllers( 32 | [self.controllers[self.currentPage]], direction: .forward, animated: true 33 | ) 34 | } 35 | 36 | class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { 37 | var parent: PageViewController 38 | 39 | init(_ pageViewController: PageViewController) { 40 | self.parent = pageViewController 41 | } 42 | 43 | func pageViewController( 44 | _ pageViewController: UIPageViewController, 45 | viewControllerBefore viewController: UIViewController 46 | ) -> UIViewController? { 47 | guard let index = parent.controllers.firstIndex(of: viewController) else { 48 | return nil 49 | } 50 | if index == 0 { 51 | return self.parent.controllers.last 52 | } 53 | return self.parent.controllers[index - 1] 54 | } 55 | 56 | func pageViewController( 57 | _ pageViewController: UIPageViewController, 58 | viewControllerAfter viewController: UIViewController 59 | ) -> UIViewController? { 60 | guard let index = parent.controllers.firstIndex(of: viewController) else { 61 | return nil 62 | } 63 | if index + 1 == self.parent.controllers.count { 64 | return self.parent.controllers.first 65 | } 66 | return self.parent.controllers[index + 1] 67 | } 68 | 69 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { 70 | if completed, 71 | let visibleViewController = pageViewController.viewControllers?.first, 72 | let index = parent.controllers.firstIndex(of: visibleViewController) 73 | { 74 | self.parent.currentPage = index 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Landmark Views/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 | @EnvironmentObject var userData: UserData 12 | var landmark: Landmark 13 | 14 | var landmarkIndex: Int { 15 | self.userData.landmarks.firstIndex(where: { $0.id == landmark.id })! 16 | } 17 | 18 | var body: some View { 19 | VStack { 20 | MapView(coordinate: landmark.locationCoordinate) 21 | .edgesIgnoringSafeArea(.top) 22 | .frame(height: 300) 23 | 24 | CircleImage(image: landmark.image) 25 | .offset(x: 0, y: -130) 26 | .padding(.bottom, -130) 27 | 28 | VStack(alignment: .leading) { 29 | HStack { 30 | Text(landmark.name) 31 | .font(.title) 32 | 33 | Button(action: { 34 | self.userData.landmarks[self.landmarkIndex] 35 | .isFavorite.toggle() 36 | }) { 37 | if self.userData.landmarks[self.landmarkIndex].isFavorite { 38 | Image(systemName: "star.fill") 39 | .foregroundColor(Color.yellow) 40 | } else { 41 | Image(systemName: "star") 42 | .foregroundColor(Color.gray) 43 | } 44 | } 45 | } 46 | 47 | HStack(alignment: .top) { 48 | Text(landmark.park) 49 | .font(.subheadline) 50 | Spacer() 51 | Text(landmark.state) 52 | .font(.subheadline) 53 | } 54 | } 55 | .padding() 56 | 57 | Spacer() 58 | } 59 | } 60 | } 61 | 62 | struct LandmarkDetail_Previews: PreviewProvider { 63 | static var previews: some View { 64 | let userData = UserData() 65 | return LandmarkDetail(landmark: userData.landmarks[0]) 66 | .environmentObject(userData) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Landmark Views/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 | @EnvironmentObject private var userData: UserData 12 | 13 | var body: some View { 14 | List { 15 | Toggle(isOn: $userData.showFavoritesOnly) { 16 | Text("Show Favorites Only") 17 | } 18 | 19 | ForEach(userData.landmarks) { landmark in 20 | if !self.userData.showFavoritesOnly || landmark.isFavorite { 21 | NavigationLink( 22 | destination: LandmarkDetail(landmark: landmark) 23 | ) { 24 | LandmarkRow(landmark: landmark) 25 | } 26 | } 27 | } 28 | } 29 | .navigationBarTitle(Text("Landmarks")) 30 | } 31 | } 32 | 33 | struct LandmarksList_Previews: PreviewProvider { 34 | static var previews: some View { 35 | ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in 36 | NavigationView { 37 | LandmarkList() 38 | .previewDevice(PreviewDevice(rawValue: deviceName)) 39 | .previewDisplayName(deviceName) 40 | } 41 | } 42 | .environmentObject(UserData()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Landmark 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 16 | .resizable() 17 | .frame(width: 50, height: 50) 18 | Text(landmark.name) 19 | Spacer() 20 | 21 | if landmark.isFavorite { 22 | Image(systemName: "star.fill") 23 | .imageScale(.medium) 24 | .foregroundColor(.yellow) 25 | } 26 | } 27 | } 28 | } 29 | 30 | struct LandmarkRow_Previews: PreviewProvider { 31 | static var previews: some View { 32 | Group { 33 | LandmarkRow(landmark: landmarkData[0]) 34 | LandmarkRow(landmark: landmarkData[1]) 35 | } 36 | .previewLayout(.fixed(width: 300, height: 70)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/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 CoreLocation 9 | import Foundation 10 | import SwiftUI 11 | 12 | let landmarkData: [Landmark] = load("landmarkData.json") 13 | let features = landmarkData.filter { $0.isFeatured } 14 | let hikeData: [Hike] = load("hikeData.json") 15 | 16 | func load(_ filename: String) -> T { 17 | let data: Data 18 | 19 | guard let file = Bundle.main.url(forResource: filename, withExtension: nil) 20 | else { 21 | fatalError("Couldn't find \(filename) in main bundle.") 22 | } 23 | 24 | do { 25 | data = try Data(contentsOf: file) 26 | } catch { 27 | fatalError("Couldn't load \(filename) from main bundle:\n\(error)") 28 | } 29 | 30 | do { 31 | let decoder = JSONDecoder() 32 | return try decoder.decode(T.self, from: data) 33 | } catch { 34 | fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") 35 | } 36 | } 37 | 38 | final class ImageStore { 39 | typealias _ImageDictionary = [String: CGImage] 40 | private var images: _ImageDictionary = [:] 41 | 42 | fileprivate static var scale = 2 43 | 44 | static var shared = ImageStore() 45 | 46 | func image(name: String) -> Image { 47 | let index = self._guaranteeImage(name: name) 48 | 49 | return Image(self.images.values[index], scale: CGFloat(ImageStore.scale), label: Text(name)) 50 | } 51 | 52 | static func loadImage(name: String) -> CGImage { 53 | guard 54 | let url = Bundle.main.url(forResource: name, withExtension: "jpg"), 55 | let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil), 56 | let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) 57 | else { 58 | fatalError("Couldn't load image \(name).jpg from main bundle.") 59 | } 60 | return image 61 | } 62 | 63 | private func _guaranteeImage(name: String) -> _ImageDictionary.Index { 64 | if let index = images.index(forKey: name) { return index } 65 | 66 | self.images[name] = ImageStore.loadImage(name: name) 67 | return self.images.index(forKey: name)! 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Models/Hike.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | The model for a hike. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct Hike: Codable, Hashable, Identifiable { 11 | var name: String 12 | var id: Int 13 | var distance: Double 14 | var difficulty: Int 15 | var observations: [Observation] 16 | 17 | static var formatter = LengthFormatter() 18 | 19 | var distanceText: String { 20 | Hike.formatter 21 | .string(fromValue: self.distance, unit: .kilometer) 22 | } 23 | 24 | struct Observation: Codable, Hashable { 25 | var distanceFromStart: Double 26 | 27 | var elevation: Range 28 | var pace: Range 29 | var heartRate: Range 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/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 CoreLocation 9 | import SwiftUI 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 | var isFavorite: Bool 20 | var isFeatured: Bool 21 | 22 | var locationCoordinate: CLLocationCoordinate2D { 23 | CLLocationCoordinate2D( 24 | latitude: self.coordinates.latitude, 25 | longitude: self.coordinates.longitude 26 | ) 27 | } 28 | 29 | var featureImage: Image? { 30 | guard self.isFeatured else { return nil } 31 | 32 | return Image( 33 | ImageStore.loadImage(name: "\(self.imageName)_feature"), 34 | scale: 2, 35 | label: Text(self.name) 36 | ) 37 | } 38 | 39 | enum Category: String, CaseIterable, Codable, Hashable { 40 | case featured = "Featured" 41 | case lakes = "Lakes" 42 | case rivers = "Rivers" 43 | case mountains = "Mountains" 44 | } 45 | } 46 | 47 | extension Landmark { 48 | var image: Image { 49 | ImageStore.shared.image(name: self.imageName) 50 | } 51 | } 52 | 53 | struct Coordinates: Hashable, Codable { 54 | var latitude: Double 55 | var longitude: Double 56 | } 57 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Models/Profile.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | An object that models a user profile. 6 | */ 7 | import Foundation 8 | 9 | struct Profile { 10 | var username: String 11 | var prefersNotifications: Bool 12 | var seasonalPhoto: Season 13 | var goalDate: Date 14 | 15 | static let `default` = Self(username: "g_kumar", prefersNotifications: true, seasonalPhoto: .winter) 16 | 17 | init(username: String, prefersNotifications: Bool = true, seasonalPhoto: Season = .winter) { 18 | self.username = username 19 | self.prefersNotifications = prefersNotifications 20 | self.seasonalPhoto = seasonalPhoto 21 | self.goalDate = Date() 22 | } 23 | 24 | enum Season: String, CaseIterable { 25 | case spring = "🌷" 26 | case summer = "🌞" 27 | case autumn = "🍂" 28 | case winter = "☃️" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Models/UserData.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A model object that stores app data. 6 | */ 7 | 8 | import Combine 9 | import SwiftUI 10 | 11 | final class UserData: ObservableObject { 12 | @Published var showFavoritesOnly = false 13 | @Published var landmarks = landmarkData 14 | @Published var profile = Profile.default 15 | } 16 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Profile/ProfileEditor.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | An editable profile view. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct ProfileEditor: View { 11 | @Binding var profile: Profile 12 | 13 | var dateRange: ClosedRange { 14 | let min = Calendar.current.date(byAdding: .year, value: -1, to: self.profile.goalDate)! 15 | let max = Calendar.current.date(byAdding: .year, value: 1, to: self.profile.goalDate)! 16 | return min ... max 17 | } 18 | 19 | var body: some View { 20 | List { 21 | HStack { 22 | Text("Username").bold() 23 | Divider() 24 | TextField("Username", text: $profile.username) 25 | } 26 | 27 | Toggle(isOn: $profile.prefersNotifications) { 28 | Text("Enable Notifications") 29 | } 30 | 31 | VStack(alignment: .leading, spacing: 20) { 32 | Text("Seasonal Photo").bold() 33 | 34 | Picker("Seasonal Photo", selection: $profile.seasonalPhoto) { 35 | ForEach(Profile.Season.allCases, id: \.self) { season in 36 | Text(season.rawValue).tag(season) 37 | } 38 | } 39 | .pickerStyle(SegmentedPickerStyle()) 40 | } 41 | .padding(.top) 42 | 43 | VStack(alignment: .leading, spacing: 20) { 44 | Text("Goal Date").bold() 45 | DatePicker( 46 | "Goal Date", 47 | selection: $profile.goalDate, 48 | in: dateRange, 49 | displayedComponents: .date 50 | ) 51 | } 52 | .padding(.top) 53 | } 54 | } 55 | } 56 | 57 | struct ProfileEditor_Previews: PreviewProvider { 58 | static var previews: some View { 59 | ProfileEditor(profile: .constant(.default)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Profile/ProfileHost.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that hosts the profile viewer and editor. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct ProfileHost: View { 11 | @Environment(\.editMode) var mode 12 | @EnvironmentObject var userData: UserData 13 | @State var draftProfile = Profile.default 14 | 15 | var body: some View { 16 | VStack(alignment: .leading, spacing: 20) { 17 | HStack { 18 | if self.mode?.wrappedValue == .active { 19 | Button("Cancel") { 20 | self.draftProfile = self.userData.profile 21 | self.mode?.animation().wrappedValue = .inactive 22 | } 23 | } 24 | 25 | Spacer() 26 | 27 | EditButton() 28 | } 29 | if self.mode?.wrappedValue == .inactive { 30 | ProfileSummary(profile: userData.profile) 31 | } else { 32 | ProfileEditor(profile: $draftProfile) 33 | .onAppear { 34 | self.draftProfile = self.userData.profile 35 | } 36 | .onDisappear { 37 | self.userData.profile = self.draftProfile 38 | } 39 | } 40 | } 41 | .padding() 42 | } 43 | } 44 | 45 | struct ProfileHost_Previews: PreviewProvider { 46 | static var previews: some View { 47 | ProfileHost().environmentObject(UserData()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Profile/ProfileSummary.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that summarizes a profile. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct ProfileSummary: View { 11 | var profile: Profile 12 | 13 | static let goalFormat: DateFormatter = { 14 | let formatter = DateFormatter() 15 | formatter.dateStyle = .long 16 | formatter.timeStyle = .none 17 | return formatter 18 | }() 19 | 20 | var body: some View { 21 | List { 22 | Text(profile.username) 23 | .bold() 24 | .font(.title) 25 | 26 | Text("Notifications: \(self.profile.prefersNotifications ? "On" : "Off")") 27 | 28 | Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)") 29 | 30 | Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)") 31 | 32 | VStack(alignment: .leading) { 33 | Text("Completed Badges") 34 | .font(.headline) 35 | ScrollView { 36 | HStack { 37 | HikeBadge(name: "First Hike") 38 | 39 | HikeBadge(name: "Earth Day") 40 | .hueRotation(Angle(degrees: 90)) 41 | HikeBadge(name: "Tenth Hike") 42 | .grayscale(0.5) 43 | .hueRotation(Angle(degrees: 45)) 44 | } 45 | } 46 | .frame(height: 140) 47 | } 48 | 49 | VStack(alignment: .leading) { 50 | Text("Recent Hikes") 51 | .font(.headline) 52 | 53 | HikeView(hike: hikeData[0]) 54 | } 55 | } 56 | } 57 | } 58 | 59 | struct ProfileSummary_Previews: PreviewProvider { 60 | static var previews: some View { 61 | ProfileSummary(profile: Profile.default) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/charleyrivers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/charleyrivers.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/charleyrivers_feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/charleyrivers_feature.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/chilkoottrail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/chilkoottrail.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/chincoteague.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/chincoteague.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/hiddenlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/hiddenlake.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/icybay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/icybay.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/lakemcdonald.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/lakemcdonald.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/landmarkData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Turtle Rock", 4 | "category": "Rivers", 5 | "city": "Twentynine Palms", 6 | "state": "California", 7 | "id": 1001, 8 | "isFeatured": true, 9 | "isFavorite": true, 10 | "park": "Joshua Tree National Park", 11 | "coordinates": { 12 | "longitude": -116.166868, 13 | "latitude": 34.011286 14 | }, 15 | "imageName": "turtlerock" 16 | }, 17 | { 18 | "name": "Silver Salmon Creek", 19 | "category": "Lakes", 20 | "city": "Port Alsworth", 21 | "state": "Alaska", 22 | "id": 1002, 23 | "isFeatured": false, 24 | "isFavorite": false, 25 | "park": "Lake Clark National Park and Preserve", 26 | "coordinates": { 27 | "longitude": -152.665167, 28 | "latitude": 59.980167 29 | }, 30 | "imageName": "silversalmoncreek" 31 | }, 32 | { 33 | "name": "Chilkoot Trail", 34 | "category": "Mountains", 35 | "city": "Skagway", 36 | "state": "Alaska", 37 | "id": 1003, 38 | "isFeatured": false, 39 | "isFavorite": true, 40 | "park": "Klondike Gold Rush National Historical Park", 41 | "coordinates": { 42 | "longitude": -135.334571, 43 | "latitude": 59.560551 44 | }, 45 | "imageName": "chilkoottrail" 46 | }, 47 | { 48 | "name": "St. Mary Lake", 49 | "category": "Lakes", 50 | "city": "Browning", 51 | "state": "Montana", 52 | "id": 1004, 53 | "isFeatured": true, 54 | "isFavorite": true, 55 | "park": "Glacier National Park", 56 | "coordinates": { 57 | "longitude": -113.536248, 58 | "latitude": 48.69423 59 | }, 60 | "imageName": "stmarylake" 61 | }, 62 | { 63 | "name": "Twin Lake", 64 | "category": "Lakes", 65 | "city": "Twin Lakes", 66 | "state": "Alaska", 67 | "id": 1005, 68 | "isFeatured": false, 69 | "isFavorite": false, 70 | "park": "Lake Clark National Park and Preserve", 71 | "coordinates": { 72 | "longitude": -153.849883, 73 | "latitude": 60.641684 74 | }, 75 | "imageName": "twinlake" 76 | }, 77 | { 78 | "name": "Lake McDonald", 79 | "category": "Mountains", 80 | "city": "West Glacier", 81 | "state": "Montana", 82 | "id": 1006, 83 | "isFeatured": false, 84 | "isFavorite": false, 85 | "park": "Glacier National Park", 86 | "coordinates": { 87 | "longitude": -113.934831, 88 | "latitude": 48.56002 89 | }, 90 | "imageName": "lakemcdonald" 91 | }, 92 | { 93 | "name": "Charley Rivers", 94 | "category": "Rivers", 95 | "city": "Eaking", 96 | "state": "Alaska", 97 | "id": 1007, 98 | "isFeatured": true, 99 | "isFavorite": false, 100 | "park": "Charley Rivers National Preserve", 101 | "coordinates": { 102 | "longitude": -143.122586, 103 | "latitude": 65.350021 104 | }, 105 | "imageName": "charleyrivers", 106 | }, 107 | { 108 | "name": "Icy Bay", 109 | "category": "Mountains", 110 | "city": "Icy Bay", 111 | "state": "Alaska", 112 | "id": 1008, 113 | "isFeatured": false, 114 | "isFavorite": false, 115 | "park": "Wrangell-St. Elias National Park and Preserve", 116 | "coordinates": { 117 | "longitude": -141.518167, 118 | "latitude": 60.089917 119 | }, 120 | "imageName": "icybay" 121 | }, 122 | { 123 | "name": "Rainbow Lake", 124 | "category": "Lakes", 125 | "city": "Willow", 126 | "state": "Alaska", 127 | "id": 1009, 128 | "isFeatured": false, 129 | "isFavorite": false, 130 | "park": "State Recreation Area", 131 | "coordinates": { 132 | "longitude": -150.086103, 133 | "latitude": 61.694334 134 | }, 135 | "imageName": "rainbowlake" 136 | }, 137 | { 138 | "name": "Hidden Lake", 139 | "category": "Lakes", 140 | "city": "Newhalem", 141 | "state": "Washington", 142 | "id": 1010, 143 | "isFeatured": false, 144 | "isFavorite": false, 145 | "park": "North Cascades National Park", 146 | "coordinates": { 147 | "longitude": -121.17799, 148 | "latitude": 48.495442 149 | }, 150 | "imageName": "hiddenlake" 151 | }, 152 | { 153 | "name": "Chincoteague", 154 | "category": "Rivers", 155 | "city": "Chincoteague", 156 | "state": "Virginia", 157 | "id": 1011, 158 | "isFeatured": false, 159 | "isFavorite": false, 160 | "park": "Chincoteague National Wildlife Refuge", 161 | "coordinates": { 162 | "longitude": -75.383212, 163 | "latitude": 37.91531 164 | }, 165 | "imageName": "chincoteague" 166 | }, 167 | { 168 | "name": "Lake Umbagog", 169 | "category": "Lakes", 170 | "city": "Errol", 171 | "state": "New Hampshire", 172 | "id": 1012, 173 | "isFeatured": false, 174 | "isFavorite": false, 175 | "park": "Umbagog National Wildlife Refuge", 176 | "coordinates": { 177 | "longitude": -71.056816, 178 | "latitude": 44.747408 179 | }, 180 | "imageName": "umbagog" 181 | } 182 | ] 183 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/rainbowlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/rainbowlake.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/silversalmoncreek.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/silversalmoncreek.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/stmarylake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/stmarylake.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/stmarylake_feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/stmarylake_feature.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/turtlerock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/turtlerock.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/turtlerock_feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/turtlerock_feature.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/twinlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/twinlake.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/umbagog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Resources/umbagog.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Supporting Views/Badge.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that displays a badge. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct Badge: View { 11 | static let rotationCount = 8 12 | 13 | var badgeSymbols: some View { 14 | ForEach(0 ..< Badge.rotationCount) { i in 15 | RotatedBadgeSymbol( 16 | angle: .degrees(Double(i) / Double(Badge.rotationCount)) * 360.0) 17 | } 18 | .opacity(0.5) 19 | } 20 | 21 | var body: some View { 22 | ZStack { 23 | BadgeBackground() 24 | 25 | GeometryReader { geometry in 26 | self.badgeSymbols 27 | .scaleEffect(1.0 / 4.0, anchor: .top) 28 | .position(x: geometry.size.width / 2.0, y: (3.0 / 4.0) * geometry.size.height) 29 | } 30 | } 31 | .scaledToFit() 32 | } 33 | } 34 | 35 | struct Badge_Previews: PreviewProvider { 36 | static var previews: some View { 37 | Badge() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Supporting Views/BadgeBackground.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that displays the background of a badge. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct BadgeBackground: View { 11 | var body: some View { 12 | GeometryReader { geometry in 13 | Path { path in 14 | var width: CGFloat = min(geometry.size.width, geometry.size.height) 15 | let height = width 16 | let xScale: CGFloat = 0.832 17 | let xOffset = (width * (1.0 - xScale)) / 2.0 18 | width *= xScale 19 | path.move( 20 | to: CGPoint( 21 | x: xOffset + width * 0.95, 22 | y: height * (0.20 + HexagonParameters.adjustment) 23 | ) 24 | ) 25 | 26 | HexagonParameters.points.forEach { 27 | path.addLine( 28 | to: CGPoint( 29 | x: xOffset + width * $0.useWidth.0 * $0.xFactors.0, 30 | y: height * $0.useHeight.0 * $0.yFactors.0 31 | ) 32 | ) 33 | 34 | path.addQuadCurve( 35 | to: CGPoint( 36 | x: xOffset + width * $0.useWidth.1 * $0.xFactors.1, 37 | y: height * $0.useHeight.1 * $0.yFactors.1 38 | ), 39 | control: CGPoint( 40 | x: xOffset + width * $0.useWidth.2 * $0.xFactors.2, 41 | y: height * $0.useHeight.2 * $0.yFactors.2 42 | ) 43 | ) 44 | } 45 | } 46 | .fill(LinearGradient( 47 | gradient: Gradient( 48 | colors: [Self.gradientStart, Self.gradientEnd]), 49 | startPoint: UnitPoint(x: 0.5, y: 0), 50 | endPoint: UnitPoint(x: 0.5, y: 0.6) 51 | )) 52 | .aspectRatio(1, contentMode: .fit) 53 | } 54 | } 55 | 56 | static let gradientStart = Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255) 57 | static let gradientEnd = Color(red: 239.0 / 255, green: 172.0 / 255, blue: 120.0 / 255) 58 | } 59 | 60 | struct BadgeBackground_Previews: PreviewProvider { 61 | static var previews: some View { 62 | BadgeBackground() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Supporting Views/BadgeSymbol.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that display a symbol in a badge. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct BadgeSymbol: View { 11 | static let symbolColor = Color(red: 79.0 / 255, green: 79.0 / 255, blue: 191.0 / 255) 12 | 13 | var body: some View { 14 | GeometryReader { geometry in 15 | Path { path in 16 | let width = min(geometry.size.width, geometry.size.height) 17 | let height = width * 0.75 18 | let spacing = width * 0.030 19 | let middle = width / 2 20 | let topWidth = 0.226 * width 21 | let topHeight = 0.488 * height 22 | 23 | path.addLines([ 24 | CGPoint(x: middle, y: spacing), 25 | CGPoint(x: middle - topWidth, y: topHeight - spacing), 26 | CGPoint(x: middle, y: topHeight / 2 + spacing), 27 | CGPoint(x: middle + topWidth, y: topHeight - spacing), 28 | CGPoint(x: middle, y: spacing), 29 | ]) 30 | 31 | path.move(to: CGPoint(x: middle, y: topHeight / 2 + spacing * 3)) 32 | path.addLines([ 33 | CGPoint(x: middle - topWidth, y: topHeight + spacing), 34 | CGPoint(x: spacing, y: height - spacing), 35 | CGPoint(x: width - spacing, y: height - spacing), 36 | CGPoint(x: middle + topWidth, y: topHeight + spacing), 37 | CGPoint(x: middle, y: topHeight / 2 + spacing * 3), 38 | ]) 39 | } 40 | .fill(Self.symbolColor) 41 | } 42 | } 43 | } 44 | 45 | struct BadgeSymbol_Previews: PreviewProvider { 46 | static var previews: some View { 47 | BadgeSymbol() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/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 | struct CircleImage_Previews: PreviewProvider { 22 | static var previews: some View { 23 | CircleImage(image: Image("turtlerock")) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Supporting Views/FeatureCard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that shows a featured landmark. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct FeatureCard: View { 11 | var landmark: Landmark 12 | 13 | var body: some View { 14 | landmark.featureImage? 15 | .resizable() 16 | .aspectRatio(3 / 2, contentMode: .fit) 17 | .overlay(TextOverlay(landmark: landmark)) 18 | } 19 | } 20 | 21 | struct TextOverlay: View { 22 | var landmark: Landmark 23 | 24 | var gradient: LinearGradient { 25 | LinearGradient( 26 | gradient: Gradient( 27 | colors: [Color.black.opacity(0.6), Color.black.opacity(0)]), 28 | startPoint: .bottom, 29 | endPoint: .center 30 | ) 31 | } 32 | 33 | var body: some View { 34 | ZStack(alignment: .bottomLeading) { 35 | Rectangle().fill(gradient) 36 | VStack(alignment: .leading) { 37 | Text(landmark.name) 38 | .font(.title) 39 | .bold() 40 | Text(landmark.park) 41 | } 42 | .padding() 43 | } 44 | .foregroundColor(.white) 45 | } 46 | } 47 | 48 | struct FeatureCard_Previews: PreviewProvider { 49 | static var previews: some View { 50 | FeatureCard(landmark: features[0]) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/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 MapKit 9 | import SwiftUI 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(_ uiView: MKMapView, context: Context) { 19 | let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02) 20 | let region = MKCoordinateRegion(center: coordinate, span: span) 21 | uiView.setRegion(region, animated: true) 22 | } 23 | } 24 | 25 | struct MapView_Previews: PreviewProvider { 26 | static var previews: some View { 27 | MapView(coordinate: landmarkData[0].locationCoordinate) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SwiftUIPlayground/AppleSwiftUITutorials/Landmarks/Supporting Views/RotatedBadgeSymbol.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A view that displays a rotated version of a badge symbol. 6 | */ 7 | 8 | import SwiftUI 9 | 10 | struct RotatedBadgeSymbol: View { 11 | let angle: Angle 12 | 13 | var body: some View { 14 | BadgeSymbol() 15 | .padding(-60) 16 | .rotationEffect(angle, anchor: .bottom) 17 | } 18 | } 19 | 20 | struct RotatedBadgeSymbol_Previews: PreviewProvider { 21 | static var previews: some View { 22 | RotatedBadgeSymbol(angle: Angle(degrees: 5)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SwiftUIPlayground/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 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/ButtonColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.578", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x00", 27 | "green" : "0x11", 28 | "red" : "0xBA" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/ButtonColorGradient.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.291", 9 | "green" : "0.434", 10 | "red" : "0.919" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x00", 27 | "green" : "0x11", 28 | "red" : "0x6A" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/cookies.imageset/1599px-Chocolate_chip_cookies.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/Assets.xcassets/cookies.imageset/1599px-Chocolate_chip_cookies.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/cookies.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1599px-Chocolate_chip_cookies.jpg", 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 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/turtlerock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "turtlerock.jpg", 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 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Assets.xcassets/turtlerock.imageset/turtlerock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/Assets.xcassets/turtlerock.imageset/turtlerock.jpg -------------------------------------------------------------------------------- /SwiftUIPlayground/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 | -------------------------------------------------------------------------------- /SwiftUIPlayground/ContentView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// Clip image to square in SwiftUI 7 | /// https://stackoverflow.com/questions/58290963/clip-image-to-square-in-swiftui?rq=1 8 | struct ClipImageView: View { 9 | var body: some View { 10 | HStack { 11 | ForEach(0 ..< 3, id: \.self) { _ in 12 | ZStack { 13 | Image("cookies") 14 | .resizable() 15 | .aspectRatio(contentMode: .fill) 16 | .layoutPriority(-1) 17 | VStack { 18 | Spacer() 19 | Text("Cookie") 20 | .frame(minWidth: 0, maxWidth: .infinity) 21 | .background(Color.white) 22 | } 23 | } 24 | .clipped() 25 | .aspectRatio(1, contentMode: .fit) 26 | .border(Color.red) 27 | } 28 | } 29 | } 30 | } 31 | 32 | struct ContentView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | ClipImageView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Convenience/Array+Indexed.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | struct IndexedCollection: RandomAccessCollection { 5 | typealias Index = Base.Index 6 | typealias Element = (index: Index, element: Base.Element) 7 | let base: Base 8 | var startIndex: Index { self.base.startIndex } 9 | var endIndex: Index { self.base.endIndex } 10 | func index(after i: Index) -> Index { 11 | self.base.index(after: i) 12 | } 13 | 14 | func index(before i: Index) -> Index { 15 | self.base.index(before: i) 16 | } 17 | 18 | func index(_ i: Index, offsetBy distance: Int) -> Index { 19 | self.base.index(i, offsetBy: distance) 20 | } 21 | 22 | subscript(position: Index) -> Element { 23 | (index: position, element: self.base[position]) 24 | } 25 | } 26 | 27 | extension RandomAccessCollection { 28 | func indexed() -> IndexedCollection { 29 | IndexedCollection(base: self) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Convenience/Date+Extensions.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import Foundation 5 | 6 | extension Date { 7 | 8 | init(year: Int, month: Int, day: Int) { 9 | var dateComponents = DateComponents() 10 | dateComponents.year = year 11 | dateComponents.month = month 12 | dateComponents.day = day 13 | self = Calendar.current.date(from: dateComponents)! 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | Scan barcodes 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | UISceneConfigurations 30 | 31 | UIWindowSceneSessionRoleApplication 32 | 33 | 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | UIStatusBarHidden 62 | 63 | UIViewControllerBasedStatusBarAppearance 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Models/CoreDataStack.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import CoreData 5 | import Foundation 6 | 7 | class CoreDataStack { 8 | 9 | public static var shared = CoreDataStack() 10 | 11 | private init() {} 12 | 13 | lazy var persistentContainer: NSPersistentContainer = { 14 | /* 15 | The persistent container for the application. This implementation 16 | creates and returns a container, having loaded the store for the 17 | application to it. This property is optional since there are legitimate 18 | error conditions that could cause the creation of the store to fail. 19 | */ 20 | let container = NSPersistentContainer(name: "PlaygroundModel") 21 | container.loadPersistentStores(completionHandler: { _, error in 22 | if let error = error as NSError? { 23 | // Replace this implementation with code to handle the error appropriately. 24 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 25 | 26 | /* 27 | Typical reasons for an error here include: 28 | * The parent directory does not exist, cannot be created, or disallows writing. 29 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 30 | * The device is out of space. 31 | * The store could not be migrated to the current model version. 32 | Check the error message to determine what the actual problem was. 33 | */ 34 | fatalError("Unresolved error \(error), \(error.userInfo)") 35 | } 36 | }) 37 | return container 38 | }() 39 | 40 | // MARK: - Core Data Saving support 41 | 42 | func saveContext() { 43 | let context = self.persistentContainer.viewContext 44 | if context.hasChanges { 45 | do { 46 | try context.save() 47 | } catch { 48 | // Replace this implementation with code to handle the error appropriately. 49 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 50 | let nserror = error as NSError 51 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 52 | } 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Models/PlaygroundModel.xcdatamodeld/PlaygroundModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Models/Todo+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import CoreData 5 | import Foundation 6 | 7 | @objc(Todo) 8 | public class Todo: NSManagedObject { 9 | 10 | static var allTodos: NSFetchRequest { 11 | let request: NSFetchRequest = Todo.fetchRequest() 12 | request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] 13 | return request 14 | } 15 | 16 | static func create(in ctx: NSManagedObjectContext) -> Todo { 17 | NSEntityDescription.insertNewObject(forEntityName: "Todo", into: ctx) as! Todo 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUIPlayground/SwiftUIPlayground.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | @main 7 | struct SwiftUIPlayground: App { 8 | var body: some Scene { 9 | WindowGroup { 10 | MainView() 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/AccountView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import ImagePickerView 5 | import SwiftUI 6 | 7 | /// https://stackoverflow.com/questions/61844491/how-can-i-create-an-unfilled-frame-in-swiftui 8 | struct AccountView: View { 9 | var body: some View { 10 | VStack(alignment: .leading, spacing: 8) { 11 | Text("Account Balance") 12 | .font(.largeTitle) 13 | Text("Account Name") 14 | .font(.headline) 15 | HStack(spacing: 0) { 16 | Text("There are ") 17 | .font(.caption) 18 | .foregroundColor(.gray) 19 | Text("6 ") 20 | .font(.caption) 21 | .fontWeight(.bold) 22 | .foregroundColor(.blue) 23 | Text("unreconciled transactions.") 24 | .font(.caption) 25 | .foregroundColor(.gray) 26 | } 27 | } 28 | .padding(20) 29 | .background(Color.white) 30 | .overlay( 31 | RoundedRectangle(cornerRadius: 20) 32 | .stroke(Color.blue, lineWidth: 2)) 33 | } 34 | 35 | } 36 | 37 | struct AccountView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | AccountView() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ActivityIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ActivityIndicatorView: View { 7 | 8 | @State private var progress = 0.5 9 | 10 | var body: some View { 11 | 12 | VStack(spacing: 20) { 13 | ProgressView() 14 | ProgressView(value: progress) 15 | 16 | Button("More") { 17 | withAnimation { 18 | progress += 0.05 19 | } 20 | } 21 | } 22 | 23 | } 24 | 25 | } 26 | 27 | struct ActivityIndicator_Previews: PreviewProvider { 28 | static var previews: some View { 29 | ActivityIndicatorView() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/AlertExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct AlertExampleView: View { 7 | 8 | @State var showingAlert: Bool = false 9 | 10 | var body: some View { 11 | VStack { 12 | Button("Show alert") { 13 | self.showingAlert = true 14 | } 15 | } 16 | .alert(isPresented: $showingAlert) { 17 | Alert( 18 | title: Text("Hello"), 19 | message: Text("This is an alert"), 20 | primaryButton: .default(Text("Ok")) { 21 | print("OK button tapped") 22 | }, 23 | secondaryButton: .cancel() 24 | ) 25 | } 26 | } 27 | } 28 | 29 | struct AlertExampleView_Previews: PreviewProvider { 30 | static var previews: some View { 31 | AlertExampleView() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/AllWidgetsView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct AllWidgetsView: View { 7 | 8 | var body: some View { 9 | NavigationView { 10 | Form { 11 | Group { 12 | Section(header: Text("Button")) { 13 | NavigationLink(destination: ButtonExampleView()) { 14 | Text("Button Example") 15 | } 16 | } 17 | Section(header: Text("DatePicker")) { 18 | DatePickerExampleView() 19 | } 20 | Section(header: Text("EditButton")) { 21 | NavigationLink(destination: EditButtonExampleView()) { 22 | Text("Edit Button Example") 23 | } 24 | } 25 | Section(header: Text("Form")) { 26 | NavigationLink(destination: ContactFormView()) { 27 | Text("Form Example") 28 | } 29 | } 30 | Section(header: Text("Image")) { 31 | ImageExampleView() 32 | .padding() 33 | } 34 | Section(header: Text("List")) { 35 | ListExampleView() 36 | } 37 | Section(header: Text("NavigationView/NavigationLink")) { 38 | NavigationLink(destination: NavigationContentView()) { 39 | Text("Navigation Example") 40 | } 41 | } 42 | Section(header: Text("Picker")) { 43 | NavigationLink(destination: PickerExampleView()) { 44 | Text("Picker Example") 45 | } 46 | } 47 | Section(header: Text("ScrollView")) { 48 | ScrollViewExample() 49 | } 50 | Section(header: Text("Section")) { 51 | NavigationLink(destination: ContactFormView()) { 52 | Text("See Form Example") 53 | } 54 | } 55 | } 56 | // it's true, a view function builder cannot take more than 10 views as of Xcode 12.5 57 | Group { 58 | Section(header: Text("Slider")) { 59 | SliderExampleView() 60 | } 61 | Section(header: Text("Stepper")) { 62 | StepperExampleView() 63 | } 64 | Section(header: Text("TabView")) { 65 | Text("This is embedded in a TabView, see MainView.swift") 66 | } 67 | Section(header: Text("Text")) { 68 | TextExampleView() 69 | } 70 | Section(header: Text("TextField, SecureField")) { 71 | TextFieldExampleView() 72 | } 73 | Section(header: Text("Toggle")) { 74 | ToggleExampleView() 75 | } 76 | Section(header: Text("SceneView")) { 77 | SceneViewExample() 78 | } 79 | } 80 | } 81 | .navigationBarTitle("All Widgets") 82 | } 83 | .navigationViewStyle(StackNavigationViewStyle()) 84 | } 85 | 86 | } 87 | 88 | struct AllWidgetsView_Previews: PreviewProvider { 89 | static var previews: some View { 90 | AllWidgetsView() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ButtonExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ButtonExampleView: View { 7 | 8 | @State var count = 0 9 | 10 | var body: some View { 11 | VStack(spacing: 10.0) { 12 | Button("Button was tapped \(count) times") { 13 | self.count += 1 14 | } 15 | 16 | Button( 17 | action: { 18 | self.count += 1 19 | }, label: { 20 | Text("Another Button") 21 | } 22 | ) 23 | 24 | Button( 25 | action: { 26 | self.count += 1 27 | }, label: { 28 | Image(systemName: "sun.max") 29 | } 30 | ) 31 | } 32 | .padding([.top, .bottom], 10) 33 | 34 | } 35 | } 36 | 37 | struct ButtonExampleView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | ButtonExampleView() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/CameraButtonView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct CameraButtonView: View { 7 | 8 | @State var recording = false 9 | var action: ((_ recording: Bool) -> Void)? 10 | 11 | var body: some View { 12 | 13 | ZStack { 14 | Circle() 15 | .stroke(lineWidth: 6) 16 | .foregroundColor(.white) 17 | .frame(width: 65, height: 65) 18 | 19 | RoundedRectangle(cornerRadius: recording ? 8 : self.innerCircleWidth / 2) 20 | .foregroundColor(.white) 21 | .frame(width: self.innerCircleWidth, height: self.innerCircleWidth) 22 | 23 | } 24 | .animation(.linear(duration: 0.2)) 25 | .padding(20) 26 | .onTapGesture { 27 | withAnimation { 28 | self.recording.toggle() 29 | self.action?(self.recording) 30 | } 31 | } 32 | 33 | } 34 | 35 | var innerCircleWidth: CGFloat { 36 | self.recording ? 32 : 55 37 | } 38 | } 39 | 40 | struct CameraButtonView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | Group { 43 | 44 | CameraButtonView(recording: false) 45 | .previewLayout(PreviewLayout.sizeThatFits) 46 | .previewDisplayName("not recording") 47 | .background(Color.gray) 48 | 49 | CameraButtonView(recording: true) 50 | .previewLayout(PreviewLayout.sizeThatFits) 51 | .previewDisplayName("recording") 52 | .background(Color.gray) 53 | 54 | ZStack { 55 | Image("turtlerock") 56 | .resizable() 57 | .aspectRatio(contentMode: .fit) 58 | HStack { 59 | Spacer() 60 | CameraButtonView() 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ChartExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import KeyboardAware 5 | import SwiftUI 6 | 7 | struct MonthValue: Identifiable { 8 | var id = UUID() 9 | var month: Int 10 | var value: Int 11 | } 12 | 13 | /// Inspired by https://stackoverflow.com/questions/61846963/how-to-ensure-view-appears-above-other-views-when-iterating-with-foreach-in-swif 14 | struct ChartExampleView: View { 15 | 16 | let dataPoints = (1 ... 12).map { MonthValue(month: $0, value: Int.random(in: 0 ... 100)) } 17 | 18 | var body: some View { 19 | ZStack { 20 | Color.yellow 21 | .edgesIgnoringSafeArea(.all) 22 | HStack(alignment: .bottom, spacing: 15.0) { 23 | ForEach(dataPoints) { dataPoint in 24 | VStack(spacing: 5) { 25 | LinearGradient(gradient: Gradient(colors: [.green, .blue]), startPoint: .top, endPoint: .bottom) 26 | .frame(width: 12, height: CGFloat(dataPoint.value)) 27 | .cornerRadius(5) 28 | Text("\(dataPoint.month)") 29 | .font(.caption) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | struct ChartExample_Previews: PreviewProvider { 38 | static var previews: some View { 39 | ChartExampleView() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ChartExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct MonthValue: Identifiable { 7 | var id = UUID() 8 | var month: Int 9 | var value: Int 10 | } 11 | 12 | /// Inspired by https://stackoverflow.com/questions/61846963/how-to-ensure-view-appears-above-other-views-when-iterating-with-foreach-in-swif 13 | struct ChartExampleView: View { 14 | 15 | let dataPoints = (1 ... 12).map { MonthValue(month: $0, value: Int.random(in: 0 ... 100)) } 16 | 17 | var body: some View { 18 | ZStack { 19 | Color.yellow 20 | .edgesIgnoringSafeArea(.all) 21 | HStack(alignment: .bottom, spacing: 15.0) { 22 | ForEach(dataPoints) { dataPoint in 23 | VStack(spacing: 5) { 24 | LinearGradient(gradient: Gradient(colors: [.green, .blue]), startPoint: .top, endPoint: .bottom) 25 | .frame(width: 12, height: CGFloat(dataPoint.value)) 26 | .cornerRadius(5) 27 | Text("\(dataPoint.month)") 28 | .font(.caption) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | struct ChartExampleView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | ChartExampleView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ClipImageSquareView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// Clip image to square in SwiftUI 7 | /// see https://stackoverflow.com/questions/58290963/clip-image-to-square-in-swiftui 8 | struct ClipImageSquareView: View { 9 | var body: some View { 10 | HStack { 11 | ForEach(0 ..< 3, id: \.self) { _ in 12 | ZStack { 13 | Image("cookies") 14 | .resizable() 15 | .aspectRatio(contentMode: .fill) 16 | .layoutPriority(-1) 17 | VStack { 18 | Spacer() 19 | Text("Cookie") 20 | .frame(minWidth: 0, maxWidth: .infinity) 21 | .background(Color.white) 22 | } 23 | } 24 | .clipped() 25 | .aspectRatio(1, contentMode: .fit) 26 | .border(Color.red) 27 | } 28 | } 29 | } 30 | } 31 | 32 | struct ClipImageSquareView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | ClipImageSquareView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/CloseButtonExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import Foundation 5 | import SwiftUI 6 | 7 | struct CloseButtonExampleView: View { 8 | 9 | var body: some View { 10 | Color.yellow 11 | .ignoresSafeArea() 12 | .overlay(self.closeButton, alignment: .topTrailing) 13 | } 14 | 15 | @ViewBuilder var closeButton: some View { 16 | Button( 17 | action: {}, 18 | label: { 19 | ZStack { 20 | Circle() 21 | .foregroundColor(.green) 22 | .frame(width: 30, height: 30) 23 | Image(systemName: "xmark") 24 | .foregroundColor(.white) 25 | } 26 | } 27 | ) 28 | .padding(20) 29 | .ignoresSafeArea() 30 | } 31 | 32 | } 33 | 34 | struct CloseButtonExampleView_Previews: PreviewProvider { 35 | 36 | static var previewView: some View { 37 | CloseButtonExampleView() 38 | } 39 | 40 | static var previews: some View { 41 | previewView 42 | .previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)")) 43 | previewView 44 | .previewDevice(PreviewDevice(rawValue: "iPhone 12")) 45 | previewView 46 | .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Max")) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/CollectionViewExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | import UIKit 6 | 7 | /// inspired by https://stackoverflow.com/questions/61952997/uicollectionview-with-swiftui-drag-and-drop-reordering-possible 8 | /// see also https://stackoverflow.com/questions/61033980/swiftui-wrapping-uicollectionview-and-use-compositional-layout 9 | struct CollectionViewExample: View { 10 | var body: some View { 11 | CollectionComponent() 12 | } 13 | } 14 | 15 | struct CollectionComponent: UIViewRepresentable { 16 | func makeCoordinator() -> CollectionComponent.Coordinator { 17 | Coordinator(data: []) 18 | } 19 | 20 | class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate { 21 | var data: [String] = [] 22 | 23 | init(data: [String]) { 24 | 25 | for index in 0 ... 20 { 26 | self.data.append("\(index)") 27 | } 28 | 29 | } 30 | 31 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 32 | self.data.count 33 | } 34 | 35 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 36 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell 37 | cell.cellView.rootView = AnyView(CellView(text: self.data[indexPath.item])) 38 | return cell 39 | } 40 | 41 | } 42 | 43 | func makeUIView(context: Context) -> UICollectionView { 44 | 45 | let layout = UICollectionViewFlowLayout() 46 | layout.scrollDirection = .vertical 47 | layout.itemSize = CGSize(width: 150, height: 150) 48 | layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) 49 | 50 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 51 | collectionView.backgroundColor = .systemBackground 52 | collectionView.dataSource = context.coordinator 53 | collectionView.delegate = context.coordinator 54 | collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell") 55 | 56 | return collectionView 57 | } 58 | 59 | func updateUIView(_ uiView: UICollectionView, context: Context) {} 60 | } 61 | 62 | class Cell: UICollectionViewCell { 63 | public var cellView = UIHostingController(rootView: AnyView(EmptyView())) 64 | 65 | override public init(frame: CGRect) { 66 | super.init(frame: frame) 67 | self.configure() 68 | } 69 | 70 | public required init?(coder aDecoder: NSCoder) { 71 | super.init(coder: aDecoder) 72 | self.configure() 73 | } 74 | 75 | private func configure() { 76 | contentView.addSubview(self.cellView.view) 77 | 78 | self.cellView.view.preservesSuperviewLayoutMargins = false 79 | self.cellView.view.translatesAutoresizingMaskIntoConstraints = false 80 | 81 | NSLayoutConstraint.activate([ 82 | self.cellView.view.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), 83 | self.cellView.view.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor), 84 | self.cellView.view.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), 85 | self.cellView.view.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), 86 | ]) 87 | } 88 | } 89 | 90 | struct CellView: View { 91 | let text: String 92 | 93 | var body: some View { 94 | ZStack { 95 | Text(text) 96 | } 97 | .frame(width: 150, height: 150) 98 | .background(Color.blue) 99 | } 100 | } 101 | 102 | struct CollectionViewExample_Previews: PreviewProvider { 103 | static var previews: some View { 104 | CollectionViewExample() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ColorCycleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// from https://stackoverflow.com/questions/61831799/changes-are-not-reflected-in-foreach-in-swiftui 7 | struct ColorCycleView: View { 8 | @State var index: Int = 0 9 | 10 | var colors = [Color.red, Color.blue, Color.orange, Color.green] 11 | 12 | var body: some View { 13 | NavigationView { 14 | VStack { 15 | Rectangle() 16 | .fill(self.colors[index]) 17 | .frame(width: 100, height: 100) 18 | } 19 | .navigationBarTitle("Color cycle") 20 | .navigationBarItems(trailing: 21 | Button("Next color") { 22 | self.index = (self.index + 1) % self.colors.count 23 | } 24 | ) 25 | } 26 | } 27 | } 28 | 29 | struct ColorCycleView_Previews: PreviewProvider { 30 | static var previews: some View { 31 | ColorCycleView() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ConditionalDatePickerView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// Conditional DatePicker in SwiftUI 7 | /// see https://stackoverflow.com/questions/61831882/swiftui-list-conditional-items-break-animations 8 | struct ConditionalDatePickerView: View { 9 | 10 | @State var showDatePicker = false 11 | 12 | var body: some View { 13 | Form { 14 | Section(header: Text("Section 1")) { 15 | Toggle(isOn: $showDatePicker) { 16 | Text("Show Date Picker") 17 | } 18 | if self.showDatePicker { 19 | DatePicker(selection: .constant(Date()), label: { Text("Date") }) 20 | } 21 | } 22 | Section(header: Text("Section 2")) { 23 | Text("Hello") 24 | } 25 | } 26 | .listStyle(GroupedListStyle()) 27 | } 28 | } 29 | 30 | struct ConditionalDatePickerView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | ConditionalDatePickerView() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ContactFormView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct Contact: Identifiable { 7 | var id = UUID() 8 | var name: String 9 | var subscribed: Bool 10 | var birthday: Date 11 | 12 | static let alice = Contact(name: "Alice", subscribed: false, birthday: Date(year: 1970, month: 7, day: 8)) 13 | 14 | static let bob = Contact(name: "Bob", subscribed: true, birthday: Date(year: 1980, month: 3, day: 4)) 15 | } 16 | 17 | struct ContactFormView: View { 18 | 19 | @State var contact = Contact.bob 20 | 21 | var body: some View { 22 | Form { 23 | Section(header: Text("Personal information")) { 24 | HStack { 25 | Text("Name") 26 | TextField("Username", text: $contact.name) 27 | .multilineTextAlignment(.trailing) 28 | } 29 | Toggle(isOn: $contact.subscribed) { 30 | Text("Subscribed") 31 | } 32 | DatePicker(selection: $contact.birthday, label: { Text("Birthday") }) 33 | } 34 | 35 | PickerExampleView() 36 | 37 | } 38 | .navigationBarTitle("Contact") 39 | } 40 | 41 | } 42 | 43 | struct ContactFormView_Previews: PreviewProvider { 44 | static var previews: some View { 45 | NavigationView { 46 | ContactFormView() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/CustomAlertView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct CustomAlertView: View { 7 | 8 | @State var alertVisible = false 9 | 10 | var body: some View { 11 | Button( 12 | action: { 13 | self.alertVisible = true 14 | }, 15 | label: { 16 | Text("Show Alert") 17 | } 18 | ) 19 | .overlay(self.alertView) 20 | 21 | } 22 | 23 | @ViewBuilder var alertView: some View { 24 | if alertVisible { 25 | VStack { 26 | Text("Hello Alert!") 27 | Button("Ok") { 28 | self.alertVisible = false 29 | } 30 | } 31 | .frame(width: 200) 32 | .padding() 33 | .background(Color.white) 34 | .cornerRadius(10) 35 | .shadow(radius: 2) 36 | } 37 | } 38 | 39 | } 40 | 41 | struct CustomAlertView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | Group { 44 | CustomAlertView() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/DarkModeExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// inspired by https://stackoverflow.com/questions/61313187/how-can-i-fit-a-shape-in-swift-ui-to-accommodate-the-length-and-width-of-a-text 7 | 8 | struct DarkModeExampleView: View { 9 | @Environment(\.colorScheme) var colorScheme 10 | 11 | var body: some View { 12 | VStack { 13 | Button( 14 | action: { 15 | print("Button Tapped") 16 | }, 17 | label: { 18 | HStack { 19 | Image(systemName: "tag.fill") 20 | Text("Tickets") 21 | } 22 | } 23 | ) 24 | .buttonStyle(RoundedButtonStyle()) 25 | } 26 | .navigationBarTitle("\(String(describing: colorScheme))") 27 | } 28 | 29 | } 30 | 31 | /// Reusable Button style 32 | struct RoundedButtonStyle: ButtonStyle { 33 | func makeBody(configuration: Self.Configuration) -> some View { 34 | ZStack { 35 | configuration.label 36 | .font(.headline) 37 | .padding(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12)) 38 | .background( 39 | LinearGradient(gradient: Gradient(colors: [Color("ButtonColor"), Color("ButtonColorGradient")]), startPoint: .top, endPoint: .bottom) 40 | .clipShape(RoundedRectangle(cornerRadius: 8)) 41 | ) 42 | } 43 | } 44 | } 45 | 46 | struct DarkModeExampleView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | Group { 49 | ForEach([ColorScheme.light, .dark], id: \.self) { colorScheme in 50 | NavigationView { 51 | DarkModeExampleView() 52 | } 53 | .environment(\.colorScheme, colorScheme) 54 | } 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/DashboardView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct CircularProgressView: View { 7 | private let lineWidth: CGFloat = 6 8 | 9 | var body: some View { 10 | Circle() 11 | .stroke(lineWidth: 6) 12 | .scaledToFit() 13 | .opacity(0.3) 14 | .foregroundColor(Color.gray.opacity(0.5)) 15 | .overlay(Circle() 16 | .trim(from: 0.0, to: 0.4) 17 | .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round)) 18 | .foregroundColor(Color.blue) 19 | .rotationEffect(Angle(degrees: 270.0)) 20 | .animation(.linear)) 21 | .overlay( 22 | Text("Text") 23 | ) 24 | 25 | } 26 | } 27 | 28 | struct DashboardView: View { 29 | var body: some View { 30 | HStack(spacing: 10) { 31 | CircularProgressView() 32 | CircularProgressView() 33 | CircularProgressView() 34 | } 35 | } 36 | } 37 | 38 | struct DashboardView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | VStack { 41 | DashboardView() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/DatePickerExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct DatePickerExampleView: View { 7 | 8 | @State var date = Date() 9 | 10 | var body: some View { 11 | DatePicker( 12 | selection: $date, 13 | label: { Text("Date") } 14 | ) 15 | } 16 | } 17 | 18 | struct DatePickerExampleView_Previews: PreviewProvider { 19 | static var previews: some View { 20 | DatePickerExampleView() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/DragGestureView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// Using a DragGesture to move a view horizontally 7 | /// see https://stackoverflow.com/questions/57020557/why-doesnt-this-swiftui-view-animate 8 | struct DragGestureView: View { 9 | 10 | @GestureState var dragOffset = CGSize.zero 11 | @State var offset: CGFloat = 0 12 | 13 | var dragGesture: some Gesture { 14 | DragGesture() 15 | .updating($dragOffset) { value, state, _ in 16 | state = value.translation 17 | } 18 | .onEnded { gesture in 19 | // keep the offset already moved without animation 20 | self.offset += gesture.translation.height 21 | withAnimation(Animation.easeOut(duration: 3)) { 22 | self.offset += gesture.predictedEndTranslation.height - gesture.translation.height 23 | } 24 | } 25 | } 26 | 27 | var body: some View { 28 | VStack { 29 | ForEach(1 ..< 5) { _ in 30 | Color.red 31 | .frame(minHeight: 20, maxHeight: 100) 32 | } 33 | } 34 | .offset(x: 0, y: offset + dragOffset.height) 35 | .gesture(dragGesture) 36 | } 37 | 38 | } 39 | 40 | struct DragGestureView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | DragGestureView() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/EditButtonExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct EditButtonExampleView: View { 7 | 8 | @State var names = ["Alice", "Bob", "Carol"] 9 | 10 | var body: some View { 11 | List { 12 | ForEach(names, id: \.self) { name in 13 | Text(name) 14 | } 15 | .onDelete(perform: delete) 16 | } 17 | .navigationBarTitle("Names") 18 | .navigationBarItems(trailing: EditButton()) 19 | } 20 | 21 | func delete(at offsets: IndexSet) { 22 | self.names.remove(atOffsets: offsets) 23 | } 24 | 25 | } 26 | 27 | struct EditButtonExampleView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | EditButtonExampleView() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/EditableListExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | // see https://stackoverflow.com/a/62005070/128083 7 | 8 | struct Position { 9 | var id = UUID() 10 | var count: Int 11 | var name: String 12 | } 13 | 14 | class BookingModel: ObservableObject { 15 | 16 | @Published var positions: [Position] 17 | 18 | init(positions: [Position] = []) { 19 | self.positions = positions 20 | } 21 | 22 | } 23 | 24 | struct EditableListExample: View { 25 | 26 | @ObservedObject var bookingModel = BookingModel( 27 | positions: [ 28 | Position(count: 1, name: "Candy"), 29 | Position(count: 0, name: "Bread"), 30 | ] 31 | ) 32 | 33 | var body: some View { 34 | // >>> Passing a binding into an Array via index: 35 | List(bookingModel.positions.indexed(), id: \.element.id) { i, _ in 36 | PositionRowView(position: self.$bookingModel.positions[i]) 37 | } 38 | } 39 | } 40 | 41 | struct PositionRowView: View { 42 | 43 | @Binding var position: Position 44 | 45 | var body: some View { 46 | Stepper( 47 | value: $position.count, 48 | label: { 49 | Text("\(position.count)x \(position.name)") 50 | } 51 | ) 52 | } 53 | 54 | } 55 | 56 | struct EditableListExample_Previews: PreviewProvider { 57 | static var previews: some View { 58 | EditableListExample() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/EnumeratedListView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import Foundation 5 | import SwiftUI 6 | 7 | // see https://stackoverflow.com/a/67132409/128083 8 | 9 | struct Light: Identifiable { 10 | var id: Int 11 | var active: Bool = false 12 | } 13 | 14 | class HomeModel: ObservableObject { 15 | @Published var lights = (1 ... 5).map { Light(id: $0) } 16 | } 17 | 18 | struct EnumeratedListView: View { 19 | 20 | @StateObject var homeModel = HomeModel() 21 | 22 | var body: some View { 23 | Form { 24 | ForEach(self.homeModel.lights.indexed(), id: \.element.id) { idx, light in 25 | Toggle("Light \(light.id)", isOn: $homeModel.lights[idx].active) 26 | } 27 | } 28 | } 29 | 30 | } 31 | 32 | struct EnumeratedList_Previews: PreviewProvider { 33 | static var previews: some View { 34 | EnumeratedListView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/EnvironmentView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// List of all the environment values: https://developer.apple.com/documentation/swiftui/environmentvalues 7 | /// Custom environment keys: https://swiftwithmajid.com/2019/08/21/the-power-of-environment-in-swiftui/#custom-environment-keys 8 | /// Custom editable environment keys like editMode: https://stackoverflow.com/a/61847419/128083 9 | struct EnvironmentView: View { 10 | @Environment(\.locale) var locale 11 | @Environment(\.sizeCategory) var sizeCategory 12 | @Environment(\.font) var font 13 | @Environment(\.isEnabled) var isEnabled 14 | @Environment(\.colorScheme) var colorScheme 15 | @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? 16 | @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? 17 | 18 | var body: some View { 19 | VStack(alignment: .leading) { 20 | Text("Locale: \(String(describing: locale))") 21 | Text("Size category: \(String(describing: sizeCategory))") 22 | Text("horizontalSizeClass: \(String(describing: horizontalSizeClass))") 23 | Text("verticalSizeClass: \(String(describing: verticalSizeClass))") 24 | Text("font: \(String(describing: font))") 25 | Text("isEnabled: \(String(describing: isEnabled))") 26 | Text("colorScheme: \(String(describing: colorScheme))") 27 | Button("Example Button") {} 28 | } 29 | .environment(\.isEnabled, false) 30 | 31 | } 32 | } 33 | 34 | struct EnvironmentView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | EnvironmentView() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/Core Data/Binding+OptionalFallback.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | // https://stackoverflow.com/questions/57021722/swiftui-optional-textfield 7 | func ?? (lhs: Binding, rhs: T) -> Binding { 8 | Binding( 9 | get: { lhs.wrappedValue ?? rhs }, 10 | set: { lhs.wrappedValue = $0 } 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/Core Data/TodoView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import CoreData 5 | import SwiftUI 6 | 7 | struct TodoView: View { 8 | @ObservedObject var todo: Todo 9 | @Environment(\.managedObjectContext) var managedObjectContext 10 | 11 | var body: some View { 12 | Form { 13 | TextField("Todo", text: $todo.name ?? "") 14 | } 15 | .onDisappear { 16 | try? self.managedObjectContext.save() 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/Core Data/TodosView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import CoreData 5 | import SwiftUI 6 | 7 | struct TodosView: View { 8 | @FetchRequest(fetchRequest: Todo.allTodos) var todos: FetchedResults 9 | @Environment(\.managedObjectContext) var managedObjectContext 10 | 11 | var body: some View { 12 | NavigationView { 13 | List { 14 | ForEach(self.todos) { todo in 15 | NavigationLink(destination: TodoView(todo: todo)) { 16 | Text(todo.name ?? "") 17 | } 18 | } 19 | .onDelete(perform: deleteRows) 20 | } 21 | .navigationBarTitle("Todos", displayMode: .inline) 22 | .navigationBarItems(trailing: addButton) 23 | } 24 | } 25 | 26 | func deleteRows(at offsets: IndexSet) { 27 | for index in offsets { 28 | self.managedObjectContext.delete(self.todos[index]) 29 | } 30 | } 31 | 32 | var addButton: some View { 33 | Button( 34 | action: { 35 | let todo = Todo.create(in: self.managedObjectContext) 36 | todo.name = "Example \(Date())" 37 | try! self.managedObjectContext.save() 38 | }, 39 | label: { 40 | Image(systemName: "plus") 41 | } 42 | ) 43 | } 44 | 45 | } 46 | 47 | struct TodosView_Previews: PreviewProvider { 48 | static var previews: some View { 49 | TodosView() 50 | .environment(\.managedObjectContext, CoreDataStack.shared.persistentContainer.viewContext) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/Core Image/ImageEffectView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import Combine 5 | import CoreImage 6 | import CoreImage.CIFilterBuiltins 7 | import ImagePickerView 8 | import SwiftUI 9 | 10 | /// see https://stackoverflow.com/questions/61929273/core-image-photo-effect-react-change-slowly-in-real-iphone-when-changing-intens/61930650#61930650 11 | class ImageEffect: ObservableObject { 12 | 13 | @Published var filterIntensity = 0.5 14 | @Published var inputImage: UIImage? 15 | @Published var outputImage: UIImage? 16 | 17 | @Published var currentFilter = CIFilter.sepiaTone() 18 | let context = CIContext() 19 | var subscriptions = Set() 20 | let queue = DispatchQueue(label: "Image processing") 21 | 22 | init() { 23 | self.$inputImage 24 | .map { inputImage -> CIImage? in 25 | guard let inputImage = inputImage else { return nil } 26 | return CIImage(image: inputImage) 27 | } 28 | .combineLatest(self.$filterIntensity) 29 | .debounce(for: .milliseconds(50), scheduler: self.queue) 30 | .map { inputImage, filterIntensity -> UIImage? in 31 | guard let inputImage = inputImage else { return nil } 32 | 33 | self.currentFilter.inputImage = inputImage 34 | self.currentFilter.intensity = Float(filterIntensity) 35 | 36 | guard let outputImage = self.currentFilter.outputImage else { return nil } 37 | 38 | guard let cgImage = self.context.createCGImage(outputImage, from: outputImage.extent) else { return nil } 39 | 40 | return UIImage(cgImage: cgImage) 41 | 42 | } 43 | .receive(on: RunLoop.main) 44 | .sink { image in 45 | self.outputImage = image 46 | } 47 | .store(in: &self.subscriptions) 48 | } 49 | 50 | } 51 | 52 | struct ImageEffectView: View { 53 | 54 | @ObservedObject var imageEffect = ImageEffect() 55 | @State private var showingImagePicker = false 56 | 57 | var body: some View { 58 | NavigationView { 59 | VStack { 60 | ZStack { 61 | Rectangle() 62 | .fill(Color.secondary) 63 | 64 | if imageEffect.outputImage != nil { 65 | Image(uiImage: imageEffect.outputImage!) 66 | .resizable() 67 | .scaledToFit() 68 | } else { 69 | Text("Tap to select a picture") 70 | .foregroundColor(.white) 71 | .font(.headline) 72 | } 73 | } 74 | .onTapGesture { 75 | self.showingImagePicker = true 76 | } 77 | 78 | HStack { 79 | Text("Intensity") 80 | Slider(value: $imageEffect.filterIntensity) 81 | } 82 | .padding(.vertical) 83 | 84 | } 85 | .padding([.horizontal, .bottom]) 86 | .navigationBarTitle("Instafilter") 87 | .sheet(isPresented: $showingImagePicker) { 88 | ImagePickerView(sourceType: .photoLibrary) { image in 89 | self.imageEffect.inputImage = image 90 | } 91 | } 92 | } 93 | } 94 | 95 | } 96 | 97 | struct ImageEffectView_Previews: PreviewProvider { 98 | static var previews: some View { 99 | ImageEffectView() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/Foundation/TodosURLSessionExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import Combine 5 | import SwiftUI 6 | 7 | struct TypiTodo: Codable, Identifiable { 8 | var id: Int 9 | var title: String 10 | } 11 | 12 | class TodosModel: ObservableObject { 13 | 14 | @Published var todos = [TypiTodo]() 15 | @Published var state = State.ready 16 | 17 | enum State { 18 | case ready 19 | case loading(Cancellable) 20 | case loaded 21 | case error(Error) 22 | } 23 | 24 | let url = URL(string: "https://jsonplaceholder.typicode.com/todos/")! 25 | let urlSession = URLSession.shared 26 | 27 | var dataTask: AnyPublisher<[TypiTodo], Error> { 28 | self.urlSession 29 | .dataTaskPublisher(for: self.url) 30 | .map(\.data) 31 | .decode(type: [TypiTodo].self, decoder: JSONDecoder()) 32 | .receive(on: RunLoop.main) 33 | .eraseToAnyPublisher() 34 | } 35 | 36 | func load() { 37 | assert(Thread.isMainThread) 38 | self.state = .loading(self.dataTask.sink( 39 | receiveCompletion: { completion in 40 | switch completion { 41 | case .finished: 42 | break 43 | case let .failure(error): 44 | self.state = .error(error) 45 | } 46 | }, 47 | receiveValue: { value in 48 | self.state = .loaded 49 | self.todos = value 50 | } 51 | )) 52 | } 53 | 54 | func loadIfNeeded() { 55 | assert(Thread.isMainThread) 56 | guard case .ready = self.state else { return } 57 | self.load() 58 | } 59 | } 60 | 61 | /// Best Practices to use URLSession to load JSON data for SwiftUI Views 62 | /// see also: https://stackoverflow.com/questions/61855811/best-practices-to-use-urlsession-to-load-json-data-for-swiftui-views 63 | /// Base components for abstracting this same approach: https://github.com/ralfebert/EndpointModel 64 | struct TodosURLSessionExampleView: View { 65 | 66 | @ObservedObject var model = TodosModel() 67 | 68 | var body: some View { 69 | List(model.todos) { todo in 70 | Text(todo.title) 71 | } 72 | .overlay(StatusOverlay(model: model)) 73 | .onAppear { self.model.loadIfNeeded() } 74 | } 75 | } 76 | 77 | struct StatusOverlay: View { 78 | 79 | @ObservedObject var model: TodosModel 80 | 81 | @ViewBuilder 82 | var body: some View { 83 | switch model.state { 84 | case .ready: 85 | EmptyView() 86 | case .loading: 87 | ProgressView() 88 | case .loaded: 89 | EmptyView() 90 | case let .error(error): 91 | VStack(spacing: 10) { 92 | Text(error.localizedDescription) 93 | .frame(maxWidth: 300) 94 | Button("Retry") { 95 | self.model.load() 96 | } 97 | } 98 | .padding() 99 | .background(Color.yellow) 100 | } 101 | } 102 | 103 | } 104 | 105 | struct TodosURLSessionExampleView_Previews: PreviewProvider { 106 | static var previews: some View { 107 | Group { 108 | TodosURLSessionExampleView(model: TodosModel()) 109 | TodosURLSessionExampleView(model: self.exampleLoadedModel) 110 | TodosURLSessionExampleView(model: self.exampleLoadingModel) 111 | TodosURLSessionExampleView(model: self.exampleErrorModel) 112 | } 113 | } 114 | 115 | static var exampleLoadedModel: TodosModel { 116 | let todosModel = TodosModel() 117 | todosModel.todos = [TypiTodo(id: 1, title: "Drink water"), TypiTodo(id: 2, title: "Enjoy the sun")] 118 | todosModel.state = .loaded 119 | return todosModel 120 | } 121 | 122 | static var exampleLoadingModel: TodosModel { 123 | let todosModel = TodosModel() 124 | todosModel.state = .loading(ExampleCancellable()) 125 | return todosModel 126 | } 127 | 128 | static var exampleErrorModel: TodosModel { 129 | let todosModel = TodosModel() 130 | todosModel.state = .error(ExampleError.exampleError) 131 | return todosModel 132 | } 133 | 134 | enum ExampleError: Error { 135 | case exampleError 136 | } 137 | 138 | struct ExampleCancellable: Cancellable { 139 | func cancel() {} 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/MessageUI/MailView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import MessageUI 5 | import SwiftUI 6 | 7 | /// https://stackoverflow.com/questions/56784722/swiftui-send-email 8 | struct MailView: UIViewControllerRepresentable { 9 | 10 | @Binding var isShowing: Bool 11 | @Binding var result: Result? 12 | 13 | class Coordinator: NSObject, MFMailComposeViewControllerDelegate { 14 | 15 | @Binding var isShowing: Bool 16 | @Binding var result: Result? 17 | 18 | init(isShowing: Binding, 19 | result: Binding?>) 20 | { 21 | _isShowing = isShowing 22 | _result = result 23 | } 24 | 25 | func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { 26 | defer { 27 | isShowing = false 28 | } 29 | guard error == nil else { 30 | self.result = .failure(error!) 31 | return 32 | } 33 | self.result = .success(result) 34 | } 35 | } 36 | 37 | func makeCoordinator() -> Coordinator { 38 | Coordinator(isShowing: $isShowing, result: $result) 39 | } 40 | 41 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> MFMailComposeViewController { 42 | let vc = MFMailComposeViewController() 43 | vc.mailComposeDelegate = context.coordinator 44 | return vc 45 | } 46 | 47 | func updateUIViewController(_ uiViewController: MFMailComposeViewController, 48 | context: UIViewControllerRepresentableContext) {} 49 | } 50 | 51 | struct MailExampleView: View { 52 | 53 | @State var result: Result? = nil 54 | @State var isShowingMailView = false 55 | 56 | var body: some View { 57 | 58 | VStack { 59 | if MFMailComposeViewController.canSendMail() { 60 | Button("Show mail view") { 61 | self.isShowingMailView.toggle() 62 | } 63 | } else { 64 | Text("Can't send emails from this device") 65 | } 66 | if result != nil { 67 | Text("Result: \(String(describing: result))") 68 | .lineLimit(nil) 69 | } 70 | } 71 | .sheet(isPresented: $isShowingMailView) { 72 | MailView(isShowing: self.$isShowingMailView, result: self.$result) 73 | } 74 | 75 | } 76 | 77 | } 78 | 79 | struct MailView_Previews: PreviewProvider { 80 | static var previews: some View { 81 | MailExampleView() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Frameworks/PencilKit/PencilKitView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import PencilKit 5 | import SwiftUI 6 | 7 | struct CanvasRepresentable: UIViewRepresentable { 8 | 9 | func makeUIView(context: Context) -> PKCanvasView { 10 | let canvasView = PKCanvasView() 11 | canvasView.isOpaque = false 12 | canvasView.drawingPolicy = .anyInput 13 | canvasView.delegate = context.coordinator 14 | canvasView.tool = PKInkingTool(.pen, color: .black, width: 1) 15 | return canvasView 16 | } 17 | 18 | func updateUIView(_ uiView: PKCanvasView, context: Context) {} 19 | 20 | func makeCoordinator() -> Coordinator { 21 | Coordinator() 22 | } 23 | 24 | class Coordinator: NSObject, PKCanvasViewDelegate {} 25 | 26 | } 27 | 28 | struct PencilKitView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | CanvasRepresentable() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ImageExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ImageExampleView: View { 7 | var body: some View { 8 | HStack(spacing: 30.0) { 9 | Image("cookies") 10 | .resizable() 11 | .aspectRatio(contentMode: .fit) 12 | .frame(maxWidth: 100, maxHeight: 100) 13 | 14 | Image(systemName: "sun.max") 15 | .font(.system(size: 60)) 16 | 17 | } 18 | 19 | } 20 | } 21 | 22 | struct ImageExampleView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | ImageExampleView() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ImagePickerView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import ImagePickerView 5 | import SwiftUI 6 | 7 | struct ImagePickerExampleView: View { 8 | 9 | @State var showImagePicker: Bool = false 10 | @State var image: UIImage? 11 | 12 | var body: some View { 13 | VStack { 14 | if image != nil { 15 | Image(uiImage: image!) 16 | .resizable() 17 | .aspectRatio(contentMode: .fit) 18 | } 19 | Button("Pick image") { 20 | self.showImagePicker.toggle() 21 | } 22 | } 23 | .sheet(isPresented: $showImagePicker) { 24 | ImagePickerView(sourceType: .photoLibrary) { image in 25 | self.image = image 26 | } 27 | } 28 | } 29 | } 30 | 31 | struct ImagePickerExampleView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | ImagePickerExampleView() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/KeyboardAwareView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import KeyboardAware 5 | import SwiftUI 6 | 7 | struct KeyboardAwareView: View { 8 | @State var text = "example" 9 | 10 | var body: some View { 11 | NavigationView { 12 | ScrollView { 13 | VStack(alignment: .leading) { 14 | ForEach(0 ..< 20) { i in 15 | Text("Text \(i):") 16 | TextField("Text", text: self.$text) 17 | .textFieldStyle(RoundedBorderTextFieldStyle()) 18 | .padding(.bottom, 10) 19 | } 20 | } 21 | .padding() 22 | } 23 | .keyboardAware() 24 | .navigationBarTitle("Keyboard Example") 25 | } 26 | 27 | } 28 | } 29 | 30 | struct KeyboardAwareView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | KeyboardAwareView() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Layout/LayoutSameHeightView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | // https://stackoverflow.com/questions/61905699/how-to-make-subview-fit-all-available-vertical-space-in-hstack 7 | struct LayoutSameHeightView: View { 8 | var body: some View { 9 | HStack(alignment: .top) { 10 | VStack { 11 | Image("cookies") 12 | .resizable() 13 | .aspectRatio(contentMode: .fit) 14 | Text("Lorem Ipsum Lorem Ipsum Lorem Ipsum") 15 | 16 | } 17 | .frame(maxHeight: .infinity, alignment: .center) // << here !! 18 | .background(Color.blue) 19 | VStack { 20 | Image("turtlerock") 21 | .resizable() 22 | .aspectRatio(contentMode: .fit) 23 | Text("Hello") 24 | } 25 | .frame(maxHeight: .infinity, alignment: .center) // << here !! 26 | .background(Color.red) 27 | } 28 | .fixedSize(horizontal: false, vertical: true) // << here !! 29 | } 30 | } 31 | 32 | struct LayoutSameHeightView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | LayoutSameHeightView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/Layout/RelativePositionExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// see https://stackoverflow.com/questions/61952141/swiftui-how-to-overlay-an-image-with-multiple-shapes-and-dependent-positioning 7 | /// see https://stackoverflow.com/questions/61953736/changing-the-anchor-point-for-a-view-when-placing-using-position 8 | struct RelativePositionExampleView: View { 9 | var body: some View { 10 | Image("cookies") 11 | .resizable() 12 | .aspectRatio(contentMode: .fit) 13 | .overlay( 14 | GeometryReader { geometry in 15 | Text("Hello") 16 | .background(Color.yellow) 17 | .position(x: geometry.size.width * 0.7, y: geometry.size.height * 0.7) 18 | } 19 | ) 20 | } 21 | } 22 | 23 | struct RelativePositionExampleView_Previews: PreviewProvider { 24 | static var previews: some View { 25 | RelativePositionExampleView() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/LinkedStateChangeView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// from https://stackoverflow.com/questions/61831034/swiftui-display-doesnt-update-on-child-link-changing-the-state 7 | struct LinkedStateChangeView: View { 8 | @State var enabled: Bool = false 9 | @State var items: [String] = ["item"] 10 | 11 | var body: some View { 12 | NavigationView { 13 | Section { 14 | ForEach(self.items, id: \.self) { _ in 15 | NavigationLink(destination: Toggle(isOn: self.$enabled) { Text("enable") }) { 16 | if self.enabled { 17 | Text("enabled") 18 | } else { 19 | Text("disabled") 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | struct LinkedStateChangeView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | LinkedStateChangeView() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ListExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ListExampleView: View { 7 | 8 | var names = ["Alice", "Bob"] 9 | 10 | struct Person { 11 | var name: String 12 | } 13 | 14 | var personArray = [Person(name: "Alice"), Person(name: "Bob")] 15 | 16 | var contactsArray = [Contact.alice, Contact.bob] 17 | 18 | var body: some View { 19 | Group { 20 | NavigationLink("List of Views", destination: 21 | List { 22 | Text("Alice") 23 | Text("Bob") 24 | }) 25 | NavigationLink("List of Array of simple values", destination: 26 | List(names, id: \.self) { name in 27 | Text(name) 28 | }) 29 | NavigationLink("List of Array", destination: 30 | List(personArray, id: \.name) { person in 31 | Text(person.name) 32 | }) 33 | NavigationLink("List of Array with Identifiable objects", destination: 34 | List(contactsArray) { contact in 35 | Text(contact.name) 36 | }) 37 | NavigationLink("Editable List with Binding", destination: 38 | EditableListExample()) 39 | } 40 | } 41 | } 42 | 43 | struct ListExampleView_Previews: PreviewProvider { 44 | static var previews: some View { 45 | NavigationView { 46 | Form { 47 | ListExampleView() 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/MainView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct MainView: View { 7 | 8 | @State var currentTab = Tab.widgets 9 | 10 | enum Tab { 11 | case widgets 12 | case landmarks 13 | } 14 | 15 | var body: some View { 16 | TabView(selection: $currentTab) { 17 | AllWidgetsView().tabItem { 18 | VStack { 19 | Image(systemName: "rectangle.grid.3x2") 20 | Text("Widgets") 21 | } 22 | }.tag(Tab.widgets) 23 | CategoryHome() 24 | .environmentObject(UserData()) 25 | .tabItem { 26 | VStack { 27 | Image(systemName: "location.north") 28 | Text("Landmarks") 29 | } 30 | }.tag(Tab.landmarks) 31 | } 32 | } 33 | } 34 | 35 | struct MainView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | MainView() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/MenuButtonExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct MenuButtonExampleView: View { 7 | 8 | var body: some View { 9 | Color.yellow 10 | .ignoresSafeArea() 11 | .overlay(self.menuButton, alignment: .topTrailing) 12 | } 13 | 14 | @ViewBuilder var menuButton: some View { 15 | Menu( 16 | content: { 17 | Button( 18 | action: { 19 | debugPrint("Action") 20 | }, 21 | label: { 22 | Label("Action", systemImage: "xmark") 23 | } 24 | ) 25 | 26 | }, 27 | label: { 28 | Button( 29 | action: {}, 30 | label: { 31 | ZStack { 32 | Circle() 33 | .foregroundColor(.green) 34 | .frame(width: 30, height: 30) 35 | Image(systemName: "ellipsis") 36 | .foregroundColor(.white) 37 | } 38 | } 39 | ) 40 | .padding(20) 41 | } 42 | ) 43 | } 44 | 45 | } 46 | 47 | struct MenuButtonExampleView_Previews: PreviewProvider { 48 | 49 | static var previewView: some View { 50 | MenuButtonExampleView() 51 | } 52 | 53 | static var previews: some View { 54 | previewView 55 | .previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)")) 56 | previewView 57 | .previewDevice(PreviewDevice(rawValue: "iPhone 12")) 58 | previewView 59 | .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Max")) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ModalPickerView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// see https://stackoverflow.com/questions/61923913/modal-picker-not-scrolling-right-swiftui 7 | struct ModalPickerView: View { 8 | 9 | @State var showingModal = false 10 | @State var hours: Int = 0 11 | @State var minutes: Int = 0 12 | 13 | var body: some View { 14 | 15 | ZStack { 16 | VStack { 17 | Button("Show me") { 18 | self.showingModal = true 19 | } 20 | 21 | if showingModal { 22 | VStack(alignment: .center) { 23 | ZStack { 24 | Color.black.opacity(0.4) 25 | .edgesIgnoringSafeArea(.vertical) 26 | 27 | VStack(spacing: 20) { 28 | Text("Time between meals") 29 | .bold().padding() 30 | .frame(maxWidth: .infinity) 31 | .background(Color.yellow) 32 | .foregroundColor(Color.white) 33 | 34 | HStack { 35 | Picker("", selection: $hours) { 36 | ForEach(0 ..< 4, id: \.self) { i in 37 | Text("\(i) hours").tag(i) 38 | } 39 | } 40 | .frame(width: 150, height: 120) 41 | .clipped() 42 | 43 | Picker("", selection: $minutes) { 44 | ForEach(0 ..< 60, id: \.self) { i in 45 | Text("\(i) min").tag(i) 46 | } 47 | } 48 | .frame(width: 150, height: 120) 49 | .clipped() 50 | } 51 | 52 | Spacer() 53 | 54 | Button(action: { 55 | self.showingModal = false 56 | }) { 57 | Text("Close") 58 | }.padding() 59 | } 60 | .frame(width: 300, height: 300) 61 | .background(Color.white) 62 | .mask(RoundedRectangle(cornerRadius: 20)) 63 | .shadow(radius: 20) 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | struct ModalPickerView_Previews: PreviewProvider { 73 | static var previews: some View { 74 | ModalPickerView() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/NavigationViewExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct NavigationContentView: View { 7 | 8 | @Environment(\.presentationMode) var presentationMode 9 | 10 | var body: some View { 11 | VStack(spacing: 15.0) { 12 | Text("Example Content") 13 | NavigationLink(destination: NavigationContentView()) { 14 | Text("Navigate to other page") 15 | } 16 | Button("Go Back") { 17 | self.presentationMode.wrappedValue.dismiss() 18 | } 19 | NavigationLink(destination: ProgrammaticNavigationExampleView()) { 20 | Text("Programmatic Navigation Example") 21 | } 22 | } 23 | .navigationBarTitle("Content") 24 | } 25 | 26 | } 27 | 28 | struct NavigationViewExample_Previews: PreviewProvider { 29 | static var previews: some View { 30 | NavigationView { 31 | NavigationContentView() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/OptionalsExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct Person { 7 | var name: String 8 | } 9 | 10 | struct OptionalsExampleView: View { 11 | 12 | var person: Person? = Person(name: "Bob") 13 | 14 | var body: some View { 15 | VStack { 16 | if let person = person { 17 | VStack { 18 | Text("Name:") 19 | Text(person.name) 20 | } 21 | } else { 22 | Text("No person available") 23 | } 24 | } 25 | 26 | } 27 | 28 | } 29 | 30 | struct OptionalsExampleView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | Group { 33 | OptionalsExampleView() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/PickerExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | enum Weather: CaseIterable { 7 | case sunny 8 | case cloudy 9 | case rainy 10 | } 11 | 12 | extension Weather { 13 | 14 | var image: Image { 15 | switch self { 16 | 17 | case .sunny: 18 | return Image(systemName: "sun.max") 19 | case .cloudy: 20 | return Image(systemName: "cloud") 21 | case .rainy: 22 | return Image(systemName: "cloud.rain") 23 | } 24 | } 25 | 26 | } 27 | 28 | struct PickerExampleView: View { 29 | 30 | @State var selection = Weather.sunny 31 | 32 | var body: some View { 33 | Section { 34 | self.examplePicker 35 | } 36 | 37 | Section { 38 | self.examplePicker 39 | .pickerStyle(SegmentedPickerStyle()) 40 | } 41 | .padding() 42 | } 43 | 44 | var examplePicker: some View { 45 | Picker( 46 | selection: $selection, 47 | label: Text("Weather"), 48 | content: { 49 | ForEach(Weather.allCases, id: \.self) { weather in 50 | weather.image.tag(weather) 51 | } 52 | } 53 | ) 54 | } 55 | 56 | } 57 | 58 | struct PickerExampleView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | PickerExampleView() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/PlayersProgressView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct PlayerLoopView: View { 7 | @ObservedObject var player: Player 8 | 9 | var body: some View { 10 | ZStack { 11 | Circle() 12 | .stroke(style: StrokeStyle(lineWidth: 10.0)) 13 | .foregroundColor(Color.purple) 14 | .opacity(0.3) 15 | 16 | Circle() 17 | .trim( 18 | from: 0, 19 | to: player.isPlaying ? 1.0 : 0.0 20 | ) 21 | .stroke( 22 | style: StrokeStyle(lineWidth: 10.0, lineCap: .round, lineJoin: .round) 23 | ) 24 | .animation( 25 | player.isPlaying ? 26 | Animation.linear(duration: player.duration) 27 | .repeatForever(autoreverses: false) : nil 28 | ) 29 | .rotationEffect(Angle(degrees: -90)) 30 | .foregroundColor(Color.purple) 31 | } 32 | .frame(width: 100, height: 100) 33 | .padding() 34 | } 35 | } 36 | 37 | struct PlayersProgressView: View { 38 | @ObservedObject var engine = Engine() 39 | 40 | var body: some View { 41 | NavigationView { 42 | VStack { 43 | ForEach(self.engine.players) { player in 44 | HStack { 45 | Text("Player") 46 | PlayerLoopView(player: player) 47 | } 48 | } 49 | } 50 | .navigationBarItems(trailing: 51 | HStack { 52 | Button("Add Player") { 53 | self.engine.addPlayer() 54 | } 55 | Button("Play All") { 56 | self.engine.playAll() 57 | } 58 | Button("Stop All") { 59 | self.engine.stopAll() 60 | } 61 | }.padding() 62 | ) 63 | } 64 | } 65 | } 66 | 67 | class Player: ObservableObject, Identifiable { 68 | var id = UUID() 69 | @Published var isPlaying: Bool = false 70 | var duration: Double = 10 71 | 72 | func play() { 73 | self.isPlaying = true 74 | } 75 | 76 | func stop() { 77 | self.isPlaying = false 78 | } 79 | } 80 | 81 | class Engine: ObservableObject { 82 | @Published var players = [Player]() 83 | 84 | func addPlayer() { 85 | let player = Player() 86 | players.append(player) 87 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 88 | player.isPlaying = true 89 | } 90 | } 91 | 92 | func stopAll() { 93 | self.players.forEach { $0.stop() } 94 | } 95 | 96 | func playAll() { 97 | self.players.forEach { $0.play() } 98 | } 99 | } 100 | 101 | struct PlayersProgressView_Previews: PreviewProvider { 102 | static var previews: some View { 103 | PlayersProgressView() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/PlaygroundView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct PlaygroundView: View { 7 | var body: some View { 8 | Text("Hello, World!") 9 | } 10 | } 11 | 12 | struct PlaygroundView_Previews: PreviewProvider { 13 | static var previews: some View { 14 | PlaygroundView() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ProgrammaticNavigationExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | /// Example for navigation using NavigationLink tag/selection 7 | /// inspired by https://stackoverflow.com/questions/59040566/navigationlink-ontapgesture-and-navigation-not-firing-consistently 8 | /// you can't build programatic navigation using that, see: https://stackoverflow.com/questions/61920405/programatic-navigation-for-navigationview-using-tag-selection-or-isactive#comment109519536_61920405 9 | struct ProgrammaticNavigationExampleView: View { 10 | 11 | enum NavDestination { 12 | case red 13 | case green 14 | } 15 | 16 | @State var destination: NavDestination? 17 | 18 | var body: some View { 19 | VStack(spacing: 20) { 20 | NavigationLink(destination: Color.red, tag: NavDestination.red, selection: $destination) { 21 | EmptyView() 22 | } 23 | NavigationLink(destination: Color.green, tag: NavDestination.green, selection: $destination) { 24 | EmptyView() 25 | } 26 | Text("Page 1") 27 | Button("Show random page") { 28 | self.destination = [.red, .green].randomElement()! 29 | } 30 | } 31 | .navigationBarTitle("Page 1") 32 | } 33 | 34 | } 35 | 36 | struct ProgrammaticNavigationExample_Previews: PreviewProvider { 37 | static var previews: some View { 38 | NavigationView { 39 | ProgrammaticNavigationExampleView() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/QRCodeScannerExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import CodeScanner 5 | import SwiftUI 6 | 7 | struct NextView: View { 8 | 9 | var scannedCode: String 10 | 11 | var body: some View { 12 | Text(scannedCode) 13 | } 14 | } 15 | 16 | /// see https://stackoverflow.com/a/61968288/128083 17 | struct QRCodeScannerExampleView: View { 18 | @State var isPresentingScanner = false 19 | @State var scannedCode: String? 20 | 21 | var body: some View { 22 | NavigationView { 23 | VStack(spacing: 10) { 24 | if self.scannedCode != nil { 25 | NavigationLink("Next page", destination: NextView(scannedCode: scannedCode!), isActive: .constant(true)).hidden() 26 | } 27 | Button("Scan Code") { 28 | self.isPresentingScanner = true 29 | } 30 | .sheet(isPresented: $isPresentingScanner) { 31 | self.scannerSheet 32 | } 33 | Text("Scan a QR code to begin") 34 | } 35 | 36 | } 37 | } 38 | 39 | var scannerSheet: some View { 40 | CodeScannerView( 41 | codeTypes: [.qr], 42 | completion: { result in 43 | if case let .success(code) = result { 44 | self.scannedCode = code 45 | self.isPresentingScanner = false 46 | } 47 | } 48 | ) 49 | } 50 | } 51 | 52 | struct QRCodeScannerExampleView_Previews: PreviewProvider { 53 | static var previews: some View { 54 | QRCodeScannerExampleView() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SceneViewExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SceneKit 5 | import SwiftUI 6 | 7 | class RendererDelegate: NSObject, ObservableObject, SCNSceneRendererDelegate { 8 | 9 | func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { 10 | print(renderer.currentViewport.size) 11 | } 12 | 13 | } 14 | 15 | struct SceneViewExample: View { 16 | 17 | @StateObject var delegate = RendererDelegate() 18 | 19 | var body: some View { 20 | SceneView(scene: self.scene, delegate: self.delegate) 21 | .ignoresSafeArea() 22 | } 23 | 24 | var scene: SCNScene { 25 | let scene = SCNScene(named: "art.scnassets/ship.scn")! 26 | 27 | let ship = scene.rootNode.childNode(withName: "ship", recursively: true)! 28 | 29 | // animate the 3d object 30 | ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1))) 31 | 32 | return scene 33 | } 34 | } 35 | 36 | struct SceneViewExample_Previews: PreviewProvider { 37 | static var previews: some View { 38 | SceneViewExample() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ScrollViewExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ScrollViewExample: View { 7 | var body: some View { 8 | ScrollView(.horizontal, showsIndicators: true, content: { 9 | HStack { 10 | ForEach(1 ..< 14, id: \.self) { day in 11 | VStack { 12 | Image(systemName: "sun.max") 13 | .font(.system(size: 30)) 14 | .padding() 15 | Text("Day \(day)") 16 | } 17 | } 18 | } 19 | .padding() 20 | }) 21 | } 22 | } 23 | 24 | struct ScrollViewExample_Previews: PreviewProvider { 25 | static var previews: some View { 26 | ScrollViewExample() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SelectionEffectView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | // see https://stackoverflow.com/questions/61925943/is-there-a-way-to-blur-the-background-of-a-tapped-element-in-swiftui 7 | struct SelectionEffectView: View { 8 | let images = ["sun.max", "moon", "cloud"] 9 | 10 | @State private var selected: String? = nil 11 | 12 | var body: some View { 13 | VStack { 14 | ForEach(images, id: \.self) { name in 15 | Image(systemName: name) 16 | .resizable() 17 | .aspectRatio(contentMode: .fit) 18 | .frame(width: 100, height: 100) 19 | .onTapGesture { 20 | if self.selected == name { 21 | self.selected = nil 22 | } else { 23 | self.selected = name 24 | } 25 | } 26 | .blur(radius: self.selected != nil && self.selected != name ? 10 : 0) 27 | .scaleEffect(self.selected == name ? 1.2 : 1) 28 | } 29 | } 30 | .animation(.spring()) 31 | } 32 | } 33 | 34 | struct SelectionEffectView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | SelectionEffectView() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ShadowsView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ShadowsView: View { 7 | var body: some View { 8 | Color.yellow 9 | .ignoresSafeArea() 10 | .overlay(self.scrollView, alignment: .bottom) 11 | } 12 | 13 | var scrollView: some View { 14 | VStack { 15 | ForEach(1 ... 3, id: \.self) { _ in 16 | Color.white 17 | .frame(height: 100) 18 | .cornerRadius(10) 19 | .shadow(color: Color.red, radius: 30, x: 0, y: 0) 20 | } 21 | } 22 | .compositingGroup() 23 | .padding() 24 | .frame(height: 200, alignment: .top) 25 | } 26 | } 27 | 28 | struct ShadowsView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | ShadowsView() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ShareSheetExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | import SwiftUI 7 | import UIKit 8 | 9 | struct ActivityViewController: UIViewControllerRepresentable { 10 | 11 | var activityItems: [Any] 12 | var applicationActivities: [UIActivity]? 13 | 14 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIActivityViewController { 15 | let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities) 16 | return controller 17 | } 18 | 19 | func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext) {} 20 | 21 | } 22 | 23 | /// see also https://stackoverflow.com/questions/56533564/showing-uiactivityviewcontroller-in-switui 24 | struct ShareSheetExampleView: View { 25 | 26 | @State private var isSharePresented: Bool = false 27 | 28 | var body: some View { 29 | Button("Share") { 30 | self.isSharePresented = true 31 | } 32 | .sheet( 33 | isPresented: $isSharePresented, 34 | onDismiss: { 35 | print("Dismissed") 36 | }, 37 | content: { 38 | ActivityViewController(activityItems: [URL(string: "https://www.apple.com")!]) 39 | } 40 | ) 41 | } 42 | } 43 | 44 | struct ShareSheetExampleView_Previews: PreviewProvider { 45 | static var previews: some View { 46 | ShareSheetExampleView() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SimpleGridViewExample.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | extension Array { 7 | func chunks(_ size: Int) -> [[Element]] { 8 | stride(from: 0, to: self.count, by: size).map { ($0 ..< Swift.min($0 + size, self.count)).map { self[$0] } } 9 | } 10 | } 11 | 12 | struct Product: Identifiable, Hashable { 13 | var id = UUID() 14 | var name: String 15 | } 16 | 17 | struct SimpleGridViewExample: View { 18 | 19 | var products = [Product(name: "p1"), Product(name: "p2"), Product(name: "p3"), Product(name: "p4"), Product(name: "p5")] 20 | 21 | var body: some View { 22 | ScrollView { 23 | VStack(alignment: .leading) { 24 | ForEach(products.chunks(2), id: \.self) { chunk in 25 | HStack { 26 | ForEach(chunk, id: \.self) { product in 27 | VStack { 28 | 29 | Image(systemName: "sun.max") 30 | .resizable() 31 | .frame(maxWidth: 100, maxHeight: 100) 32 | .aspectRatio(contentMode: .fit) 33 | 34 | Text(product.name) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | } 44 | 45 | struct SimpleGridViewExample_Previews: PreviewProvider { 46 | static var previews: some View { 47 | SimpleGridViewExample() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SizePreferenceExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct SizePreferenceKey: PreferenceKey { 7 | static let defaultValue: CGSize = .zero 8 | 9 | static func reduce(value: inout CGSize, nextValue: () -> CGSize) { 10 | value = nextValue() 11 | } 12 | } 13 | 14 | struct SizeReaderView: View { 15 | 16 | var body: some View { 17 | GeometryReader { geometry in 18 | Color.clear.preference( 19 | key: SizePreferenceKey.self, 20 | value: geometry.size 21 | ) 22 | } 23 | } 24 | } 25 | 26 | struct SizePreferenceExampleView: View { 27 | 28 | @State var text = "Lorem ipsum" 29 | @State var size: CGSize = .zero 30 | 31 | var body: some View { 32 | VStack { 33 | Button( 34 | action: { 35 | self.text = Array(repeating: "Lorem ipsum", count: Int.random(in: 1 ... 10)).joined(separator: " ") 36 | }, 37 | label: { 38 | Text(self.text) 39 | .padding() 40 | } 41 | ) 42 | .background(SizeReaderView()) 43 | .background(Color.yellow) 44 | .frame(maxWidth: 200) 45 | 46 | Text(String(describing: self.size)) 47 | .frame(width: self.size.width, height: self.size.height) 48 | .background(Color.green) 49 | 50 | } 51 | .onPreferenceChange(SizePreferenceKey.self) { size in 52 | self.size = size 53 | } 54 | } 55 | 56 | } 57 | 58 | struct SizePreferenceExampleView_Previews: PreviewProvider { 59 | 60 | static var previews: some View { 61 | SizePreferenceExampleView() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SizedUIViewRepresentableView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | // How do you pass the size from UIKit up to SwiftUI in a UIViewRepresentable? 7 | // https://stackoverflow.com/questions/61832221/passing-uikit-uiview-size-to-swiftui-in-uiviewrepresentable 8 | struct YellowBoxView: UIViewRepresentable { 9 | 10 | func makeUIView(context: Context) -> YellowBoxUIKitView { 11 | let view = YellowBoxUIKitView() 12 | view.setContentHuggingPriority(.required, for: .horizontal) 13 | view.setContentHuggingPriority(.required, for: .vertical) 14 | return view 15 | } 16 | 17 | func updateUIView(_ uiView: YellowBoxUIKitView, context: Context) {} 18 | 19 | } 20 | 21 | /// Some UIKit view that wants to have a specific size 22 | class YellowBoxUIKitView: UIView { 23 | 24 | init() { 25 | super.init(frame: .zero) 26 | self.backgroundColor = .yellow 27 | } 28 | 29 | @available(*, unavailable) 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) is not supported") 32 | } 33 | 34 | override var intrinsicContentSize: CGSize { 35 | CGSize(width: 100, height: 100) 36 | } 37 | 38 | } 39 | 40 | struct SizedUIViewRepresentableView: View { 41 | var body: some View { 42 | YellowBoxView() 43 | } 44 | } 45 | 46 | struct SizedUIViewRepresentableView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | SizedUIViewRepresentableView() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SliderExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct SliderExampleView: View { 7 | 8 | @State var value = 50.0 9 | 10 | var body: some View { 11 | Slider(value: $value, in: 1.0 ... 100.0) { 12 | Text("Value \(value)") 13 | } 14 | } 15 | } 16 | 17 | struct SliderExample_Previews: PreviewProvider { 18 | static var previews: some View { 19 | SliderExampleView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/StateExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct CounterView: View { 7 | let name: String 8 | @State var count = 1 9 | 10 | var body: some View { 11 | Button("Counter \(name) \(count)") { self.count += 1 } 12 | .padding() 13 | .border(Color.blue) 14 | } 15 | } 16 | 17 | /// Demonstrates that SwiftUI state is coupled to view appearance / disappearance 18 | struct StateExampleView: View { 19 | 20 | @State var outerCount = 1 21 | @State var showB = false 22 | 23 | var body: some View { 24 | VStack { 25 | 26 | Button("Outer Counter \(outerCount)") { self.outerCount += 1 } 27 | .padding() 28 | 29 | Toggle(isOn: $showB) { 30 | Text("Toggle A/B") 31 | } 32 | 33 | if showB { 34 | CounterView(name: "B") 35 | } else { 36 | CounterView(name: "A") 37 | } 38 | 39 | if showB { 40 | CounterView(name: "B") 41 | } else { 42 | CounterView(name: "A") 43 | } 44 | 45 | CounterView(name: "C") 46 | 47 | } 48 | .border(Color.red) 49 | .padding() 50 | } 51 | } 52 | 53 | struct StateExampleView_Previews: PreviewProvider { 54 | static var previews: some View { 55 | StateExampleView() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/StepperExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct StepperExampleView: View { 7 | @State var value = 50 8 | 9 | var body: some View { 10 | Stepper("Value \(value)", value: $value, in: 1 ... 100) 11 | } 12 | } 13 | 14 | struct StepperExampleView_Previews: PreviewProvider { 15 | static var previews: some View { 16 | StepperExampleView() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/StrangeStateGlitchExampleView copy.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | class FlipModel: ObservableObject { 7 | @Published var flipped = true { 8 | didSet { 9 | print("new value for flipped: \(self.flipped)") 10 | self.counterModel?.counter += 1 11 | } 12 | } 13 | 14 | var counterModel: CounterModel? 15 | } 16 | 17 | class CounterModel: ObservableObject { 18 | @Published var counter = 0 19 | } 20 | 21 | struct StrangeStateGlitchExampleView: View { 22 | 23 | @StateObject var flipModel = FlipModel() 24 | @StateObject var counterModel = CounterModel() 25 | 26 | // MARK: - View 27 | 28 | var body: some View { 29 | VStack(spacing: 10) { 30 | Button("Toggle value from SwiftUI") { 31 | self.flipModel.flipped.toggle() 32 | } 33 | Button("Toggle value from external thread") { 34 | DispatchQueue.main.async { 35 | self.flipModel.flipped.toggle() 36 | } 37 | } 38 | OtherView(flipModel: self.flipModel) 39 | Text("Flipped (ExampleView): \(String(describing: flipModel.flipped))") 40 | Text("Flipped \(self.counterModel.counter) times") 41 | } 42 | .onAppear { 43 | self.flipModel.counterModel = counterModel 44 | } 45 | 46 | } 47 | 48 | } 49 | 50 | struct OtherView: View { 51 | 52 | @ObservedObject var flipModel: FlipModel 53 | 54 | var body: some View { 55 | Text("Flipped (OtherView): \(String(describing: flipModel.flipped))") 56 | } 57 | } 58 | 59 | struct StrangeStateGlitchExampleView_Previews: PreviewProvider { 60 | 61 | static var previews: some View { 62 | StrangeStateGlitchExampleView() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/SwiftUISwitchView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | enum Status { 7 | case loggedIn, loggedOut, expired 8 | } 9 | 10 | struct SwiftUISwitchView: View { 11 | 12 | @State var userStatus: Status = .loggedIn 13 | 14 | var body: some View { 15 | VStack { 16 | Text("switch for function builders was added in Xcode 12") 17 | 18 | switch self.userStatus { 19 | case .loggedIn: 20 | Text("Welcome!") 21 | case .loggedOut: 22 | Image(systemName: "person.fill") 23 | case .expired: 24 | Text("Session expired") 25 | } 26 | 27 | } 28 | } 29 | } 30 | 31 | struct SwitchUsageInSwiftUI_Previews: PreviewProvider { 32 | static var previews: some View { 33 | SwiftUISwitchView() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/TextExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct TextExampleView: View { 7 | var body: some View { 8 | VStack(spacing: 20.0) { 9 | Text("Hamlet") 10 | .font(.title) 11 | 12 | Text("by William Shakespeare") 13 | .font(.system(size: 12, weight: .light, design: .serif)) 14 | .italic() 15 | 16 | Text("To be, or not to be, that is the question:") 17 | .frame(width: 100) 18 | 19 | Text("Brevity is the soul of wit.") 20 | .frame(width: 100) 21 | .lineLimit(1) 22 | } 23 | } 24 | } 25 | 26 | struct TextExampleView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | TextExampleView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/TextFieldExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct TextFieldExampleView: View { 7 | 8 | @State var value = "ABC" 9 | @State var numberValue: Int = 123 10 | 11 | var body: some View { 12 | VStack(spacing: 20) { 13 | VStack(alignment: .leading) { 14 | Text("TextField") 15 | TextField("Value", text: $value) 16 | .textFieldStyle(RoundedBorderTextFieldStyle()) 17 | } 18 | 19 | VStack(alignment: .leading) { 20 | Text("TextField with formatter") 21 | TextField("Number value", value: $numberValue, formatter: NumberFormatter()) 22 | .textFieldStyle(RoundedBorderTextFieldStyle()) 23 | } 24 | 25 | VStack(alignment: .leading) { 26 | Text("SecureField") 27 | SecureField("Value", text: $value) 28 | .textFieldStyle(RoundedBorderTextFieldStyle()) 29 | } 30 | } 31 | } 32 | } 33 | 34 | struct TextFieldExampleView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | TextFieldExampleView() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/ToggleExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import SwiftUI 5 | 6 | struct ToggleExampleView: View { 7 | @State var value = true 8 | 9 | var body: some View { 10 | Toggle(isOn: $value) { 11 | Text("Value \(String(describing: value))") 12 | } 13 | } 14 | } 15 | 16 | struct ToggleExampleView_Previews: PreviewProvider { 17 | static var previews: some View { 18 | ToggleExampleView() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/UserDefaultsExampleView.swift: -------------------------------------------------------------------------------- 1 | // SwiftUIPlayground 2 | // https://github.com/ralfebert/SwiftUIPlayground/ 3 | 4 | import Combine 5 | import SwiftUI 6 | 7 | /// see https://stackoverflow.com/questions/56822195/how-do-i-use-userdefaults-with-swiftui 8 | /// this assumes the UserDefaults are only changed using the ObservableObject wrapper object 9 | @propertyWrapper 10 | struct UserDefault { 11 | let key: String 12 | let defaultValue: T 13 | 14 | init(_ key: String, defaultValue: T) { 15 | self.key = key 16 | self.defaultValue = defaultValue 17 | } 18 | 19 | var wrappedValue: T { 20 | get { 21 | UserDefaults.standard.object(forKey: self.key) as? T ?? self.defaultValue 22 | } 23 | set { 24 | UserDefaults.standard.set(newValue, forKey: self.key) 25 | } 26 | } 27 | } 28 | 29 | class TextSettings: ObservableObject { 30 | 31 | @UserDefault("TextCount", defaultValue: 1) 32 | var count: Int { 33 | willSet { 34 | self.objectWillChange.send() 35 | } 36 | } 37 | 38 | @UserDefault("TextUppercase", defaultValue: true) 39 | var upcase: Bool { 40 | willSet { 41 | self.objectWillChange.send() 42 | } 43 | } 44 | 45 | } 46 | 47 | struct TextSettingsView: View { 48 | @EnvironmentObject var settings: TextSettings 49 | 50 | var body: some View { 51 | Form { 52 | Picker(selection: $settings.count, label: 53 | Text("Text Repeat Count")) { 54 | ForEach(Array(1 ... 5), id: \.self) { value in 55 | Text(String(value)).tag(value) 56 | } 57 | } 58 | 59 | Toggle(isOn: $settings.upcase) { 60 | Text("Uppercase") 61 | } 62 | 63 | } 64 | } 65 | } 66 | 67 | /// Example for a shared @EnvironmentObject/ObservableObject 68 | /// see https://stackoverflow.com/questions/61941322/swiftui-updating-text-after-changes-in-settings/61943534#61943534 69 | struct UserDefaultsExampleView: View { 70 | @EnvironmentObject var settings: TextSettings 71 | 72 | var body: some View { 73 | Text(self.text) 74 | .navigationBarItems(trailing: NavigationLink("Settings", destination: TextSettingsView())) 75 | } 76 | 77 | var text: String { 78 | var str = String(repeating: "Hello ", count: Int(settings.count)) 79 | if self.settings.upcase { 80 | str = str.uppercased() 81 | } 82 | return str 83 | } 84 | 85 | } 86 | 87 | struct TextWithSettingExampleView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | NavigationView { 90 | UserDefaultsExampleView() 91 | } 92 | .environmentObject(TextSettings()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/art.scnassets/ship.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/Views/art.scnassets/ship.scn -------------------------------------------------------------------------------- /SwiftUIPlayground/Views/art.scnassets/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/SwiftUIPlayground/Views/art.scnassets/texture.png -------------------------------------------------------------------------------- /docs/Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Button.png -------------------------------------------------------------------------------- /docs/DatePicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/DatePicker.png -------------------------------------------------------------------------------- /docs/EditButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/EditButton.png -------------------------------------------------------------------------------- /docs/Form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Form.png -------------------------------------------------------------------------------- /docs/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Image.png -------------------------------------------------------------------------------- /docs/List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/List.png -------------------------------------------------------------------------------- /docs/NavigationView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/NavigationView.png -------------------------------------------------------------------------------- /docs/Picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Picker.png -------------------------------------------------------------------------------- /docs/ScrollView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/ScrollView.png -------------------------------------------------------------------------------- /docs/Section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Section.png -------------------------------------------------------------------------------- /docs/SecureField.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/SecureField.png -------------------------------------------------------------------------------- /docs/Slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Slider.png -------------------------------------------------------------------------------- /docs/Stepper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Stepper.png -------------------------------------------------------------------------------- /docs/TabView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/TabView.png -------------------------------------------------------------------------------- /docs/Text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Text.png -------------------------------------------------------------------------------- /docs/TextField.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/TextField.png -------------------------------------------------------------------------------- /docs/Toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/Toggle.png -------------------------------------------------------------------------------- /docs/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ralfebert/SwiftUIPlayground/b9c3533aba05fa5412dcddcf37358e8960ccab72/docs/overview.png -------------------------------------------------------------------------------- /docs/views.csv: -------------------------------------------------------------------------------- 1 | swiftui_view,uikit_view,example 2 | Button,UIButton,ButtonExampleView 3 | DatePicker,UIDatePicker,DatePickerExampleView 4 | EditButton,,EditButtonExampleView 5 | Form,,ContactFormView 6 | Image,UIImageView,ImageExampleView 7 | List,UITableView,ListExampleView 8 | NavigationView,UINavigationController,NavigationViewExample 9 | Picker,UIPickerView,PickerExampleView 10 | ScrollView,UIScrollView,ScrollViewExample 11 | Section,,ContactFormView 12 | SecureField,UITextField,TextFieldExampleView 13 | Slider,UISlider,SliderExampleView 14 | Stepper,UIStepper,StepperExampleView 15 | TabView,UITabBarController,MainView 16 | Text,UILabel,TextExampleView 17 | TextField,UITextField,TextFieldExampleView 18 | Toggle,UISwitch,ToggleExampleView 19 | -------------------------------------------------------------------------------- /docs/views.rb: -------------------------------------------------------------------------------- 1 | #!/bin/env ruby 2 | 3 | require 'csv' 4 | require 'mdtable' 5 | require 'pry' 6 | require 'active_support' 7 | require 'active_support/core_ext/object/blank' 8 | 9 | views = CSV.parse(File.read("views.csv"), headers: true).map { |row| OpenStruct.new(row) } 10 | 11 | links = [] 12 | 13 | text = "" 14 | 15 | data = [['SwiftUI view', 'UIKit View', 'Example']] + views.map { |view| 16 | swiftui_link = "https://developer.apple.com/documentation/swiftui/#{view.swiftui_view.downcase}" 17 | 18 | swiftui_handle = "swiftui-#{view.swiftui_view.downcase}" 19 | example_handle = "example-#{view.swiftui_view.downcase}" 20 | example_link = "https://github.com/ralfebert/SwiftUIPlayground/blob/master/SwiftUIPlayground/Views/#{view.example}.swift" 21 | 22 | links << "[#{swiftui_handle}]: #{swiftui_link}" 23 | links << "[#{example_handle}]: #{example_link}" 24 | 25 | uikit_link = "" 26 | uikit_equivalent = unless view.uikit_view.blank? 27 | uikit_handle = "uikit-#{view.uikit_view.downcase}" 28 | uikit_link = "https://developer.apple.com/documentation/uikit/#{view.uikit_view.downcase}" 29 | uikit_link = " ([#{view.uikit_view}](#{uikit_link}) in UIKit)" 30 | links << "[#{uikit_handle}]: #{uikit_link}" 31 | "[#{view.uikit_view}][#{uikit_handle}]" 32 | end 33 | 34 | text += "* [#{view.swiftui_view}](#{swiftui_link})#{uikit_link}: [example](#{example_link})\n" 35 | 36 | [ 37 | "[#{view.swiftui_view}][#{swiftui_handle}]", 38 | uikit_equivalent || '', 39 | %Q{[][#{example_handle}]} 40 | ] 41 | 42 | } 43 | 44 | puts MDTable.convert(data) 45 | puts "\n" 46 | puts links.join("\n") 47 | puts "\n" 48 | puts "\n" 49 | puts text 50 | --------------------------------------------------------------------------------