├── Color Picker Demo ├── Color Picker Demo.mov ├── Color Picker Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── ale_patron.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── ale_patron.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Color Picker Demo │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Color_Picker_DemoApp.swift │ ├── ContentView.swift │ ├── Info.plist │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── Directions ├── Directions.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Directions │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── SceneDelegate.swift ├── Expandable Button Panel ├── Expandable Button Panel.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Expandable Button Panel │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── SceneDelegate.swift ├── Guess the Number ├── Final │ ├── Guess the Number.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcuserdata │ │ │ │ └── alep.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── Guess the Number │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ │ ├── GuessingGame.swift │ │ ├── Info.plist │ │ ├── NumberGuessingGame.swift │ │ ├── NumberGuessingGameView.swift │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── SceneDelegate.swift └── Skeleton │ ├── Guess the Number.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ └── Guess the Number │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── GuessingGame.swift │ ├── Info.plist │ ├── NumberGuessingGame.swift │ ├── NumberGuessingGameView.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── SceneDelegate.swift ├── Lists and Navigation ├── Lists and Navigation.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Lists and Navigation │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── SceneDelegate.swift ├── Music Search ├── Music Search.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── ale_patron.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Music Search.xcscheme │ └── xcuserdata │ │ └── ale_patron.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Music Search │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── ImageCache.swift │ ├── ImageLoader.swift │ ├── Info.plist │ ├── Music_SearchApp.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Song.swift │ └── SongListViewModel.swift ├── Programming Quotes ├── Programming Quotes.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Programming Quotes │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── Programming_QuotesApp.swift ├── Progress View ├── Progress View.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Progress View │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── Progress_ViewApp.swift ├── Random Joke UIKit ├── Random Joke UIKit.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Random Joke UIKit │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── Random Joke ├── Random Joke.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Random Joke │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── Todos ├── Todos.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Todos │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── SceneDelegate.swift ├── TwitterUI ├── TwitterUI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alep.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── TwitterUI │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Tweet.swift │ └── TwitterUIApp.swift └── Weather ├── Weather.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── alep.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── Weather.xcscheme └── xcuserdata │ └── alep.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── Weather ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Info.plist ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── Weather.swift ├── WeatherApp.swift ├── WeatherService.swift ├── WeatherView.swift └── WeatherViewModel.swift /Color Picker Demo/Color Picker Demo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Color Picker Demo/Color Picker Demo.mov -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo.xcodeproj/project.xcworkspace/xcuserdata/ale_patron.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Color Picker Demo/Color Picker Demo.xcodeproj/project.xcworkspace/xcuserdata/ale_patron.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo.xcodeproj/xcuserdata/ale_patron.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Color Picker Demo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/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 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/Color_Picker_DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color_Picker_DemoApp.swift 3 | // Color Picker Demo 4 | // 5 | // Created by Alejandrina Patron on 6/24/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct Color_Picker_DemoApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Color Picker Demo 4 | // 5 | // Created by Alejandrina Patron on 6/24/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @State private var selectedColor = Color.black 13 | 14 | var body: some View { 15 | VStack(alignment: .center) { 16 | Text("Color Picker Demo").foregroundColor(selectedColor).font(.largeTitle) 17 | ColorPicker( 18 | "Pick a color", 19 | selection: $selectedColor 20 | ).frame(width: 150, height: 150) 21 | Spacer() 22 | }.padding(.vertical, 70) 23 | } 24 | } 25 | 26 | struct ContentView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ContentView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Color Picker Demo/Color Picker Demo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Directions/Directions.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Directions/Directions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Directions/Directions.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Directions/Directions.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Directions/Directions.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Directions.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Directions/Directions/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Directions 4 | // 5 | // Created by Alejandrina Patrón López on 9/11/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Directions/Directions/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 | -------------------------------------------------------------------------------- /Directions/Directions/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Directions/Directions/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 | -------------------------------------------------------------------------------- /Directions/Directions/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Directions 4 | // 5 | 6 | import MapKit 7 | import SwiftUI 8 | 9 | struct ContentView: View { 10 | 11 | @State private var directions: [String] = [] 12 | @State private var showDirections = false 13 | 14 | var body: some View { 15 | VStack { 16 | MapView(directions: $directions) 17 | 18 | Button(action: { 19 | self.showDirections.toggle() 20 | }, label: { 21 | Text("Show directions") 22 | }) 23 | .disabled(directions.isEmpty) 24 | .padding() 25 | }.sheet(isPresented: $showDirections, content: { 26 | VStack(spacing: 0) { 27 | Text("Directions") 28 | .font(.largeTitle) 29 | .bold() 30 | .padding() 31 | 32 | Divider().background(Color.blue) 33 | 34 | List(0.. MapViewCoordinator { 48 | return MapViewCoordinator() 49 | } 50 | 51 | func makeUIView(context: Context) -> MKMapView { 52 | let mapView = MKMapView() 53 | mapView.delegate = context.coordinator 54 | 55 | let region = MKCoordinateRegion( 56 | center: CLLocationCoordinate2D(latitude: 40.71, longitude: -74), 57 | span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)) 58 | mapView.setRegion(region, animated: true) 59 | 60 | // NYC 61 | let p1 = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 40.71, longitude: -74)) 62 | 63 | // Boston 64 | let p2 = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 42.36, longitude: -71.05)) 65 | 66 | let request = MKDirections.Request() 67 | request.source = MKMapItem(placemark: p1) 68 | request.destination = MKMapItem(placemark: p2) 69 | request.transportType = .automobile 70 | 71 | let directions = MKDirections(request: request) 72 | directions.calculate { response, error in 73 | guard let route = response?.routes.first else { return } 74 | mapView.addAnnotations([p1, p2]) 75 | mapView.addOverlay(route.polyline) 76 | mapView.setVisibleMapRect( 77 | route.polyline.boundingMapRect, 78 | edgePadding: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20), 79 | animated: true) 80 | self.directions = route.steps.map { $0.instructions }.filter { !$0.isEmpty } 81 | } 82 | return mapView 83 | } 84 | 85 | func updateUIView(_ uiView: MKMapView, context: Context) { 86 | } 87 | 88 | class MapViewCoordinator: NSObject, MKMapViewDelegate { 89 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { 90 | let renderer = MKPolylineRenderer(overlay: overlay) 91 | renderer.strokeColor = .systemBlue 92 | renderer.lineWidth = 5 93 | return renderer 94 | } 95 | } 96 | } 97 | 98 | struct ContentView_Previews: PreviewProvider { 99 | static var previews: some View { 100 | ContentView() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Directions/Directions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Directions/Directions/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Directions/Directions/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Directions 4 | // 5 | // Created by Alejandrina Patrón López on 9/11/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Expandable Button Panel/Expandable Button Panel.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Expandable Button Panel.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Expandable Button Panel 4 | // 5 | // Created by Alejandrina Patrón López on 8/4/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/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 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/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 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Expandable Button Panel 4 | // 5 | // Created by Alejandrina Patrón López on 8/4/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | 13 | @State private var showAlert: Bool = false 14 | @State private var alertLabel: String = "" 15 | 16 | var body: some View { 17 | NavigationView { 18 | ZStack { 19 | // List 20 | List(1...20, id: \.self) { i in 21 | Text("Item #\(i)") 22 | .padding() 23 | } 24 | 25 | // Floating Button Panel 26 | VStack { 27 | Spacer() 28 | HStack { 29 | Spacer() 30 | ExpandableButtonPanel( 31 | primaryItem: ExpandableButtonItem(label: "➕"), 32 | secondaryItems: [ 33 | ExpandableButtonItem(label: "🌞") { 34 | self.alertLabel = "🌞" 35 | self.showAlert.toggle() 36 | }, 37 | ExpandableButtonItem(label: "🥑") { 38 | self.alertLabel = "🥑" 39 | self.showAlert.toggle() 40 | } 41 | ] 42 | ) 43 | .padding() 44 | } 45 | } 46 | } 47 | .alert(isPresented: $showAlert) { 48 | return Alert(title: Text("You selected \(self.alertLabel)")) 49 | } 50 | .navigationBarTitle("Numbers") 51 | } 52 | } 53 | } 54 | 55 | struct ExpandableButtonItem: Identifiable { 56 | let id = UUID() 57 | let label: String 58 | private(set) var action: (() -> Void)? = nil 59 | } 60 | 61 | struct ExpandableButtonPanel: View { 62 | 63 | let primaryItem: ExpandableButtonItem 64 | let secondaryItems: [ExpandableButtonItem] 65 | 66 | private let noop: () -> Void = {} 67 | private let size: CGFloat = 70 68 | private var cornerRadius: CGFloat { 69 | get { size / 2 } 70 | } 71 | private let shadowColor = Color.black.opacity(0.4) 72 | private let shadowPosition: (x: CGFloat, y: CGFloat) = (x: 2, y: 2) 73 | private let shadowRadius: CGFloat = 3 74 | 75 | @State private var isExpanded = false 76 | 77 | var body: some View { 78 | VStack { 79 | ForEach(secondaryItems) { item in 80 | Button(item.label, action: item.action ?? self.noop) 81 | .frame( 82 | width: self.isExpanded ? self.size : 0, 83 | height: self.isExpanded ? self.size : 0) 84 | } 85 | 86 | Button(primaryItem.label, action: { 87 | withAnimation { 88 | self.isExpanded.toggle() 89 | } 90 | self.primaryItem.action?() 91 | }) 92 | .frame(width: size, height: size) 93 | } 94 | .background(Color(UIColor.systemPurple)) 95 | .cornerRadius(cornerRadius) 96 | .font(.title) 97 | .shadow( 98 | color: shadowColor, 99 | radius: shadowRadius, 100 | x: shadowPosition.x, 101 | y: shadowPosition.y 102 | ) 103 | } 104 | } 105 | 106 | struct ContentView_Previews: PreviewProvider { 107 | static var previews: some View { 108 | ContentView() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Expandable Button Panel/Expandable Button Panel/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Expandable Button Panel 4 | // 5 | // Created by Alejandrina Patrón López on 8/4/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Guess the Number/Final/Guess the Number.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Guess the Number.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/19/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/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 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/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 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/GuessingGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuessingGame.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/25/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Model 12 | struct GuessingGame { 13 | enum GameStatus { 14 | case won 15 | case lost 16 | case playing 17 | } 18 | 19 | var elementToGuess: GuessElement 20 | var maxGuesses: Int 21 | 22 | private(set) var gameStatus: GameStatus 23 | private var numberOfGuesses: Int 24 | 25 | init(elementToGuess: GuessElement, maxGuesses: Int) { 26 | self.elementToGuess = elementToGuess 27 | self.maxGuesses = maxGuesses 28 | gameStatus = .playing 29 | numberOfGuesses = 0 30 | } 31 | 32 | mutating func makeGuess(withElement element: GuessElement) { 33 | numberOfGuesses += 1 34 | if element == elementToGuess { 35 | gameStatus = .won 36 | } else if numberOfGuesses >= maxGuesses { 37 | gameStatus = .lost 38 | } else { 39 | gameStatus = .playing 40 | } 41 | } 42 | 43 | mutating func reset(with elementToGuess: GuessElement) { 44 | self.elementToGuess = elementToGuess 45 | numberOfGuesses = 0 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/NumberGuessingGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberGuessingGame.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/25/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // ViewModel 12 | class NumberGuessingGame: ObservableObject { 13 | 14 | @Published var currentGuess = "" 15 | @Published var showAlert = false 16 | @Published var alertTitle = "" 17 | @Published var guessLabel = "Guess the number!" 18 | 19 | private var model: GuessingGame 20 | private var maxGuesses = 3 21 | 22 | init() { 23 | model = GuessingGame(elementToGuess: NumberGuessingGame.generateGuess(), maxGuesses: maxGuesses) 24 | } 25 | 26 | func makeGuess(withNumber number: Int) { 27 | model.makeGuess(withElement: number) 28 | switch model.gameStatus { 29 | case .lost: 30 | alertTitle = "You lost! 😢" 31 | showAlert = true 32 | guessLabel = "Guess the Number!" 33 | case .won: 34 | alertTitle = "You won! 🥳" 35 | showAlert = true 36 | guessLabel = "Guess the Number!" 37 | case .playing: 38 | guessLabel = "Try again!" 39 | } 40 | currentGuess = "" 41 | } 42 | 43 | func reset() { 44 | model.reset(with: NumberGuessingGame.generateGuess()) 45 | showAlert = false 46 | } 47 | 48 | private static func generateGuess() -> Int { 49 | return Int.random(in: 1...10) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/NumberGuessingGameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/19/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct NumberGuessingGameView: View { 12 | 13 | @ObservedObject private var gameViewModel: NumberGuessingGame 14 | 15 | public init(gameViewModel: NumberGuessingGame) { 16 | self.gameViewModel = gameViewModel 17 | } 18 | 19 | var body: some View { 20 | VStack { 21 | Text(self.gameViewModel.guessLabel) 22 | TextField( 23 | "Type a number", 24 | text: self.$gameViewModel.currentGuess 25 | ) 26 | .keyboardType(.numberPad) 27 | .textFieldStyle(RoundedBorderTextFieldStyle()) 28 | .multilineTextAlignment(.center) 29 | 30 | Button( 31 | "Enter guess", 32 | action: { 33 | if let userGuess = Int(self.gameViewModel.currentGuess) { 34 | self.gameViewModel.makeGuess(withNumber: userGuess) 35 | } 36 | } 37 | ).alert(isPresented: $gameViewModel.showAlert) { 38 | return Alert( 39 | title: Text(self.gameViewModel.alertTitle), 40 | message: nil, 41 | dismissButton: .default(Text("Start new game"), action: { 42 | self.gameViewModel.reset() 43 | }) 44 | ) 45 | } 46 | }.padding() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Guess the Number/Final/Guess the Number/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/19/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = NumberGuessingGameView(gameViewModel: NumberGuessingGame()) 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Guess the Number/Skeleton/Guess the Number.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Guess the Number.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/19/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/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 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/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 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/GuessingGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuessingGame.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/25/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Model 12 | struct GuessingGame { 13 | enum GameStatus { 14 | case won 15 | case lost 16 | case playing 17 | } 18 | 19 | var elementToGuess: GuessElement 20 | var maxGuesses: Int 21 | 22 | // TODO 23 | // private(set) var gameStatus: GameStatus 24 | // private var numberOfGuesses: Int 25 | 26 | mutating func makeGuess(withElement element: GuessElement) { 27 | // TODO 28 | } 29 | 30 | mutating func reset(with elementToGuess: GuessElement) { 31 | // TODO 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/NumberGuessingGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberGuessingGame.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/25/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // ViewModel 12 | class NumberGuessingGame: ObservableObject { 13 | 14 | @Published var currentGuess = "" 15 | @Published var showAlert = false 16 | @Published var alertTitle = "" 17 | @Published var guessLabel = "Guess the number!" 18 | 19 | // private var model: GuessingGame 20 | // private var maxGuesses = 3 21 | 22 | func makeGuess(withNumber number: Int) { 23 | // TODO 24 | } 25 | 26 | func reset() { 27 | // TODO 28 | } 29 | 30 | private static func generateGuess() -> Int { 31 | // TODO 32 | return 1 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/NumberGuessingGameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/19/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct NumberGuessingGameView: View { 12 | 13 | @ObservedObject private var gameViewModel: NumberGuessingGame 14 | 15 | public init(gameViewModel: NumberGuessingGame) { 16 | self.gameViewModel = gameViewModel 17 | } 18 | 19 | var body: some View { 20 | // TODO 21 | EmptyView() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Guess the Number/Skeleton/Guess the Number/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Guess the Number 4 | // 5 | // Created by Alejandrina Patrón López on 7/19/20. 6 | // Copyright © 2020 Alejandrina Patrón López. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = NumberGuessingGameView(gameViewModel: NumberGuessingGame()) 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Lists and Navigation/Lists and Navigation.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Lists and Navigation.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Lists and Navigation 4 | // 5 | // Created by Alejandrina Patrón López on 9/3/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/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 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/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 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Listas y Navegación 4 | // 5 | 6 | 7 | import SwiftUI 8 | 9 | struct ContentView: View { 10 | 11 | private let emojiList: [EmojiItem] = [ 12 | EmojiItem( 13 | emoji: "👾", 14 | name: "Alien Monster", 15 | description: "A friendly-looking, tentacled space creature with two eyes."), 16 | EmojiItem( 17 | emoji: "🥑", 18 | name: "Avocado", 19 | description: "A pear-shaped avocado, sliced in half to show its yellow-green flesh and " 20 | + "large brown pit."), 21 | EmojiItem( 22 | emoji: "🍟", 23 | name: "French Fries", 24 | description: "Thin-cut, golden-brown French fries, served in a red carton, as at " 25 | + "McDonald’s."), 26 | EmojiItem( 27 | emoji: "🍕", 28 | name: "Pizza", 29 | description: "A slice of pepperoni pizza, topped with black olives on Google. WhatsApp " 30 | + "adds green pepper, Samsung white onion."), 31 | EmojiItem( 32 | emoji: "🧩", 33 | name: "Puzzle Piece", 34 | description: "Puzzle Piece was approved as part of Unicode 11.0 in 2018 under the name " 35 | + "“Jigsaw Puzzle Piece” and added to Emoji 11.0 in 2018."), 36 | EmojiItem( 37 | emoji: "🚀", 38 | name: "Rocket", 39 | description: "A rocket being propelled into space."), 40 | EmojiItem( 41 | emoji: "🗽", 42 | name: "Statue of Liberty", 43 | description: "The Statue of Liberty, often used as a depiction of New York City."), 44 | EmojiItem( 45 | emoji: "🧸", 46 | name: "Teddy Bear", 47 | description: "A classic teddy bear, as snuggled by a child when going to sleep."), 48 | EmojiItem( 49 | emoji: "🦄", 50 | name: "Unicorn", 51 | description: "The face of a unicorn, a mythical creature in the form of a white horse with " 52 | + "a single, long horn on its forehead."), 53 | EmojiItem( 54 | emoji: "👩🏽‍💻", 55 | name: "Woman Technologist", 56 | description: "A woman behind a computer screen, working in the field of technology."), 57 | EmojiItem( 58 | emoji: "🗺", 59 | name: "World Map", 60 | description: "A rectangular map of the world. Generally depicted as a paper map creased at " 61 | + "its folds, Earth’s surface shown in green on blue ocean."), 62 | ] 63 | 64 | var body: some View { 65 | NavigationView { 66 | List(emojiList) { emojiItem in 67 | NavigationLink(destination: DetailsView(emojiItem: emojiItem)) { 68 | HStack { 69 | EmojiCircleView(emojiItem: emojiItem) 70 | Text(emojiItem.name) 71 | .font(.headline) 72 | }.padding(7) 73 | } 74 | } 75 | .navigationBarTitle("Emoji") 76 | } 77 | } 78 | } 79 | 80 | struct DetailsView: View { 81 | 82 | let emojiItem: EmojiItem 83 | 84 | var body: some View { 85 | VStack(alignment: .leading) { 86 | HStack { 87 | EmojiCircleView(emojiItem: emojiItem) 88 | .padding(.trailing, 5) 89 | 90 | Text(emojiItem.name) 91 | .font(.largeTitle) 92 | .bold() 93 | 94 | Spacer() 95 | } 96 | 97 | Text(emojiItem.description) 98 | .padding(.top) 99 | 100 | Spacer() 101 | } 102 | .padding() 103 | .navigationBarTitle(Text(emojiItem.name), displayMode: .inline) 104 | } 105 | } 106 | 107 | struct EmojiCircleView: View { 108 | let emojiItem: EmojiItem 109 | 110 | var body: some View { 111 | ZStack { 112 | Text(emojiItem.emoji) 113 | .shadow(radius: 3) 114 | .font(.largeTitle) 115 | .frame(width: 65, height: 65) 116 | .overlay( 117 | Circle().stroke(Color.purple, lineWidth: 3) 118 | ) 119 | } 120 | } 121 | } 122 | 123 | struct EmojiItem: Identifiable { 124 | let id = UUID() 125 | let emoji: String 126 | let name: String 127 | let description: String 128 | } 129 | 130 | struct ContentView_Previews: PreviewProvider { 131 | static var previews: some View { 132 | ContentView() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Lists and Navigation/Lists and Navigation/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Lists and Navigation 4 | // 5 | // Created by Alejandrina Patrón López on 9/3/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Music Search/Music Search.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Music Search/Music Search.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Music Search/Music Search.xcodeproj/project.xcworkspace/xcuserdata/ale_patron.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Music Search/Music Search.xcodeproj/project.xcworkspace/xcuserdata/ale_patron.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Music Search/Music Search.xcodeproj/xcshareddata/xcschemes/Music Search.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Music Search/Music Search.xcodeproj/xcuserdata/ale_patron.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Music Search.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | ED4EF5E825411B24006C9E60 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Music Search/Music Search/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Music Search/Music Search/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 | -------------------------------------------------------------------------------- /Music Search/Music Search/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Music Search/Music Search/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Music Search 4 | // 5 | // SwiftUI Tutorial - Music Search App (JSON, URLSession, NSCache) 6 | 7 | import Combine 8 | import SwiftUI 9 | import UIKit 10 | 11 | struct ContentView: View { 12 | 13 | @State private var searchTerm: String = "" 14 | 15 | @ObservedObject var viewModel: SongListViewModel 16 | 17 | var body: some View { 18 | NavigationView { 19 | VStack { 20 | SearchBar(searchTerm: $viewModel.artistSearch) 21 | if viewModel.songs.isEmpty { 22 | ZeroStateView() 23 | } else { 24 | List(viewModel.songs) { song in 25 | SongView(song: song) 26 | }.listStyle(PlainListStyle()) 27 | } 28 | }.navigationBarTitle("Music Search") 29 | } 30 | } 31 | } 32 | 33 | struct ZeroStateView: View { 34 | var body: some View { 35 | VStack { 36 | Spacer() 37 | Image(systemName: "music.note") 38 | .font(.system(size: 85)) 39 | .padding(.bottom) 40 | Text("Start searching for music...") 41 | .font(.title) 42 | Spacer() 43 | } 44 | .padding() 45 | .foregroundColor(Color(.systemIndigo)) 46 | } 47 | } 48 | 49 | struct SongView: View { 50 | 51 | public let song: Song 52 | 53 | @StateObject private var imageLoader: ImageLoader = ImageLoader() 54 | 55 | var body: some View { 56 | HStack { 57 | ArtworkView(image: imageLoader.image) 58 | .padding(.trailing) 59 | VStack(alignment: .leading) { 60 | Text(song.trackName) 61 | Text(song.artistName) 62 | .font(.footnote) 63 | .foregroundColor(.gray) 64 | } 65 | } 66 | .onAppear { 67 | imageLoader.loadImage(forSong: song) 68 | } 69 | .padding() 70 | } 71 | } 72 | 73 | struct ArtworkView: View { 74 | let image: Image? 75 | 76 | var body: some View { 77 | ZStack { 78 | if image != nil { 79 | image 80 | } else { 81 | Color(.systemIndigo) 82 | Image(systemName: "music.note") 83 | .font(.largeTitle) 84 | .foregroundColor(.white) 85 | } 86 | } 87 | .frame(width: 50, height: 50) 88 | .shadow(radius: 5) 89 | .padding(.trailing, 5) 90 | } 91 | } 92 | 93 | struct SearchBar: UIViewRepresentable { 94 | typealias UIViewType = UISearchBar 95 | 96 | @Binding var searchTerm: String 97 | 98 | func makeUIView(context: Context) -> UISearchBar { 99 | let searchBar = UISearchBar(frame: .zero) 100 | searchBar.delegate = context.coordinator 101 | searchBar.searchBarStyle = .minimal 102 | searchBar.placeholder = "Shawn Mendes" 103 | return searchBar 104 | } 105 | 106 | func updateUIView(_ uiView: UISearchBar, context: Context) { 107 | } 108 | 109 | func makeCoordinator() -> SearchBarCoordinator { 110 | return SearchBarCoordinator(searchTerm: $searchTerm) 111 | } 112 | 113 | class SearchBarCoordinator: NSObject, UISearchBarDelegate { 114 | 115 | @Binding var searchTerm: String 116 | 117 | init(searchTerm: Binding) { 118 | self._searchTerm = searchTerm 119 | } 120 | 121 | func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 122 | searchTerm = searchBar.text ?? "" 123 | UIApplication.shared.windows.first { $0.isKeyWindow }?.endEditing(true) 124 | } 125 | } 126 | } 127 | 128 | //struct ContentView_Previews: PreviewProvider { 129 | // static var previews: some View { 130 | // ContentView() 131 | // } 132 | //} 133 | -------------------------------------------------------------------------------- /Music Search/Music Search/ImageCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCache.swift 3 | // Music Search 4 | // 5 | 6 | import UIKit 7 | 8 | class ImageCache { 9 | public static var shared = ImageCache() 10 | 11 | private var cache = NSCache() 12 | 13 | func getValue(forKey key: String) -> UIImage? { 14 | return cache.object(forKey: NSString(string: key)) 15 | } 16 | 17 | func setValue(_ image: UIImage, forKey key: String) { 18 | cache.setObject(image, forKey: NSString(string: key)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Music Search/Music Search/ImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.swift 3 | // Music Search 4 | // 5 | // Created by Alejandrina Patron on 10/25/20. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | class ImageLoader: ObservableObject { 12 | @Published public private(set) var image: Image? 13 | 14 | private var dataTask: URLSessionDataTask? 15 | private let keyPrefix = "artwork-" 16 | 17 | deinit { 18 | dataTask?.cancel() 19 | } 20 | 21 | func loadImage(forSong song: Song) { 22 | if let cachedImage = ImageCache.shared.getValue(forKey: "\(keyPrefix)\(song.id)") { 23 | DispatchQueue.main.async { 24 | self.image = Image(uiImage: cachedImage) 25 | return 26 | } 27 | } 28 | 29 | guard let imageUrl = URL(string: song.artworkUrl) else { return } 30 | dataTask = URLSession.shared.dataTask(with: imageUrl) { data, _, _ in 31 | guard let data = data else { return } 32 | guard let remoteImage = UIImage(data: data) else { return } 33 | DispatchQueue.main.async { 34 | self.image = Image(uiImage: remoteImage) 35 | ImageCache.shared.setValue(remoteImage, forKey: "\(self.keyPrefix)\(song.id)") 36 | } 37 | } 38 | dataTask?.resume() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Music Search/Music Search/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Music Search/Music Search/Music_SearchApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Music_SearchApp.swift 3 | // Music Search 4 | // 5 | // Created by Alejandrina Patron on 10/21/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct Music_SearchApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView(viewModel: SongListViewModel()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Music Search/Music Search/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Music Search/Music Search/Song.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Song.swift 3 | // Music Search 4 | // 5 | // Created by Alejandrina Patron on 10/21/20. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | struct SongResponse: Decodable { 12 | var songs: [Song] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case songs = "results" 16 | } 17 | } 18 | 19 | struct Song: Decodable, Identifiable { 20 | let id: Int 21 | let trackName: String 22 | let artistName: String 23 | let artworkUrl: String 24 | 25 | enum CodingKeys: String, CodingKey { 26 | case id = "trackId" 27 | case trackName 28 | case artistName 29 | case artworkUrl = "artworkUrl60" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Music Search/Music Search/SongListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SongListViewModel.swift 3 | // Music Search 4 | // 5 | // Created by Alejandrina Patron on 10/25/20. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | 11 | class SongListViewModel: ObservableObject { 12 | @Published var artistSearch: String = "" 13 | @Published public private(set) var songs: [Song] = [] 14 | 15 | private var disposables = Set() 16 | 17 | init() { 18 | $artistSearch 19 | .sink(receiveValue: loadSongs(forArtist:)) 20 | .store(in: &disposables) 21 | } 22 | 23 | private func loadSongs(forArtist artist: String) { 24 | guard let url = buildUrl(forArtist: artist) else { return } 25 | 26 | URLSession.shared.dataTask(with: url) { data, _, _ in 27 | guard let data = data else { return } 28 | if let songResponse = try? JSONDecoder().decode(SongResponse.self, from: data) { 29 | DispatchQueue.main.async { 30 | self.songs = songResponse.songs 31 | } 32 | } 33 | }.resume() 34 | } 35 | 36 | private func buildUrl(forArtist artist: String) -> URL? { 37 | guard !artist.isEmpty else { return nil } 38 | 39 | let queryItems = [ 40 | URLQueryItem(name: "term", value: artist), 41 | URLQueryItem(name: "entity", value: "song"), 42 | ] 43 | var urlComps = URLComponents(string: "https://itunes.apple.com/search") 44 | urlComps?.queryItems = queryItems 45 | 46 | return urlComps?.url 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Programming Quotes/Programming Quotes.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Programming Quotes.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/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 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Programming Quotes 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ContentView: View { 9 | 10 | @State private var quoteData: QuoteData? 11 | 12 | var body: some View { 13 | HStack { 14 | Spacer() 15 | 16 | VStack(alignment: .trailing) { 17 | Spacer() 18 | 19 | Text(quoteData?.en ?? "") 20 | .font(.title2) 21 | Text("- \(quoteData?.author ?? "")") 22 | .font(.title2) 23 | .padding(.top) 24 | 25 | Spacer() 26 | 27 | Button(action: loadData) { 28 | Image(systemName: "arrow.clockwise") 29 | } 30 | .font(.title) 31 | .padding(.top) 32 | } 33 | } 34 | .multilineTextAlignment(.trailing) 35 | .padding() 36 | .onAppear(perform: loadData) 37 | } 38 | 39 | private func loadData() { 40 | guard let url = URL(string: "https://programming-quotes-api.herokuapp.com/quotes/random") else { 41 | return 42 | } 43 | URLSession.shared.dataTask(with: url) { data, response, error in 44 | guard let data = data else { return } 45 | if let decodedData = try? JSONDecoder().decode(QuoteData.self, from: data) { 46 | DispatchQueue.main.async { 47 | self.quoteData = decodedData 48 | } 49 | } 50 | }.resume() 51 | } 52 | } 53 | 54 | struct QuoteData: Decodable { 55 | var _id: String 56 | var en: String 57 | var author: String 58 | var id: String 59 | } 60 | 61 | struct ContentView_Previews: PreviewProvider { 62 | static var previews: some View { 63 | ContentView() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Programming Quotes/Programming Quotes/Programming_QuotesApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Programming_QuotesApp.swift 3 | // Programming Quotes 4 | // 5 | // Created by Alejandrina Patrón López on 9/28/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct Programming_QuotesApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Progress View/Progress View.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F67709872533A00B006E67C9 /* Progress_ViewApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F67709862533A00B006E67C9 /* Progress_ViewApp.swift */; }; 11 | F67709892533A00B006E67C9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F67709882533A00B006E67C9 /* ContentView.swift */; }; 12 | F677098B2533A00D006E67C9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F677098A2533A00D006E67C9 /* Assets.xcassets */; }; 13 | F677098E2533A00D006E67C9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F677098D2533A00D006E67C9 /* Preview Assets.xcassets */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | F67709832533A00B006E67C9 /* Progress View.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Progress View.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | F67709862533A00B006E67C9 /* Progress_ViewApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Progress_ViewApp.swift; sourceTree = ""; }; 19 | F67709882533A00B006E67C9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 20 | F677098A2533A00D006E67C9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 21 | F677098D2533A00D006E67C9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 22 | F677098F2533A00D006E67C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | /* End PBXFileReference section */ 24 | 25 | /* Begin PBXFrameworksBuildPhase section */ 26 | F67709802533A00B006E67C9 /* Frameworks */ = { 27 | isa = PBXFrameworksBuildPhase; 28 | buildActionMask = 2147483647; 29 | files = ( 30 | ); 31 | runOnlyForDeploymentPostprocessing = 0; 32 | }; 33 | /* End PBXFrameworksBuildPhase section */ 34 | 35 | /* Begin PBXGroup section */ 36 | F677097A2533A00B006E67C9 = { 37 | isa = PBXGroup; 38 | children = ( 39 | F67709852533A00B006E67C9 /* Progress View */, 40 | F67709842533A00B006E67C9 /* Products */, 41 | ); 42 | sourceTree = ""; 43 | }; 44 | F67709842533A00B006E67C9 /* Products */ = { 45 | isa = PBXGroup; 46 | children = ( 47 | F67709832533A00B006E67C9 /* Progress View.app */, 48 | ); 49 | name = Products; 50 | sourceTree = ""; 51 | }; 52 | F67709852533A00B006E67C9 /* Progress View */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | F67709862533A00B006E67C9 /* Progress_ViewApp.swift */, 56 | F67709882533A00B006E67C9 /* ContentView.swift */, 57 | F677098A2533A00D006E67C9 /* Assets.xcassets */, 58 | F677098F2533A00D006E67C9 /* Info.plist */, 59 | F677098C2533A00D006E67C9 /* Preview Content */, 60 | ); 61 | path = "Progress View"; 62 | sourceTree = ""; 63 | }; 64 | F677098C2533A00D006E67C9 /* Preview Content */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | F677098D2533A00D006E67C9 /* Preview Assets.xcassets */, 68 | ); 69 | path = "Preview Content"; 70 | sourceTree = ""; 71 | }; 72 | /* End PBXGroup section */ 73 | 74 | /* Begin PBXNativeTarget section */ 75 | F67709822533A00B006E67C9 /* Progress View */ = { 76 | isa = PBXNativeTarget; 77 | buildConfigurationList = F67709922533A00D006E67C9 /* Build configuration list for PBXNativeTarget "Progress View" */; 78 | buildPhases = ( 79 | F677097F2533A00B006E67C9 /* Sources */, 80 | F67709802533A00B006E67C9 /* Frameworks */, 81 | F67709812533A00B006E67C9 /* Resources */, 82 | ); 83 | buildRules = ( 84 | ); 85 | dependencies = ( 86 | ); 87 | name = "Progress View"; 88 | productName = "Progress View"; 89 | productReference = F67709832533A00B006E67C9 /* Progress View.app */; 90 | productType = "com.apple.product-type.application"; 91 | }; 92 | /* End PBXNativeTarget section */ 93 | 94 | /* Begin PBXProject section */ 95 | F677097B2533A00B006E67C9 /* Project object */ = { 96 | isa = PBXProject; 97 | attributes = { 98 | LastSwiftUpdateCheck = 1200; 99 | LastUpgradeCheck = 1200; 100 | TargetAttributes = { 101 | F67709822533A00B006E67C9 = { 102 | CreatedOnToolsVersion = 12.0; 103 | }; 104 | }; 105 | }; 106 | buildConfigurationList = F677097E2533A00B006E67C9 /* Build configuration list for PBXProject "Progress View" */; 107 | compatibilityVersion = "Xcode 9.3"; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = F677097A2533A00B006E67C9; 115 | productRefGroup = F67709842533A00B006E67C9 /* Products */; 116 | projectDirPath = ""; 117 | projectRoot = ""; 118 | targets = ( 119 | F67709822533A00B006E67C9 /* Progress View */, 120 | ); 121 | }; 122 | /* End PBXProject section */ 123 | 124 | /* Begin PBXResourcesBuildPhase section */ 125 | F67709812533A00B006E67C9 /* Resources */ = { 126 | isa = PBXResourcesBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | F677098E2533A00D006E67C9 /* Preview Assets.xcassets in Resources */, 130 | F677098B2533A00D006E67C9 /* Assets.xcassets in Resources */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXResourcesBuildPhase section */ 135 | 136 | /* Begin PBXSourcesBuildPhase section */ 137 | F677097F2533A00B006E67C9 /* Sources */ = { 138 | isa = PBXSourcesBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | F67709892533A00B006E67C9 /* ContentView.swift in Sources */, 142 | F67709872533A00B006E67C9 /* Progress_ViewApp.swift in Sources */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXSourcesBuildPhase section */ 147 | 148 | /* Begin XCBuildConfiguration section */ 149 | F67709902533A00D006E67C9 /* Debug */ = { 150 | isa = XCBuildConfiguration; 151 | buildSettings = { 152 | ALWAYS_SEARCH_USER_PATHS = NO; 153 | CLANG_ANALYZER_NONNULL = YES; 154 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 155 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 156 | CLANG_CXX_LIBRARY = "libc++"; 157 | CLANG_ENABLE_MODULES = YES; 158 | CLANG_ENABLE_OBJC_ARC = YES; 159 | CLANG_ENABLE_OBJC_WEAK = YES; 160 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 161 | CLANG_WARN_BOOL_CONVERSION = YES; 162 | CLANG_WARN_COMMA = YES; 163 | CLANG_WARN_CONSTANT_CONVERSION = YES; 164 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 165 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 166 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 167 | CLANG_WARN_EMPTY_BODY = YES; 168 | CLANG_WARN_ENUM_CONVERSION = YES; 169 | CLANG_WARN_INFINITE_RECURSION = YES; 170 | CLANG_WARN_INT_CONVERSION = YES; 171 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 172 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 173 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 174 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 175 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 176 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 177 | CLANG_WARN_STRICT_PROTOTYPES = YES; 178 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 179 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 180 | CLANG_WARN_UNREACHABLE_CODE = YES; 181 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 182 | COPY_PHASE_STRIP = NO; 183 | DEBUG_INFORMATION_FORMAT = dwarf; 184 | ENABLE_STRICT_OBJC_MSGSEND = YES; 185 | ENABLE_TESTABILITY = YES; 186 | GCC_C_LANGUAGE_STANDARD = gnu11; 187 | GCC_DYNAMIC_NO_PIC = NO; 188 | GCC_NO_COMMON_BLOCKS = YES; 189 | GCC_OPTIMIZATION_LEVEL = 0; 190 | GCC_PREPROCESSOR_DEFINITIONS = ( 191 | "DEBUG=1", 192 | "$(inherited)", 193 | ); 194 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 195 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 196 | GCC_WARN_UNDECLARED_SELECTOR = YES; 197 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 198 | GCC_WARN_UNUSED_FUNCTION = YES; 199 | GCC_WARN_UNUSED_VARIABLE = YES; 200 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 201 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 202 | MTL_FAST_MATH = YES; 203 | ONLY_ACTIVE_ARCH = YES; 204 | SDKROOT = iphoneos; 205 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 206 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 207 | }; 208 | name = Debug; 209 | }; 210 | F67709912533A00D006E67C9 /* Release */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_ANALYZER_NONNULL = YES; 215 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_ENABLE_OBJC_WEAK = YES; 221 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 222 | CLANG_WARN_BOOL_CONVERSION = YES; 223 | CLANG_WARN_COMMA = YES; 224 | CLANG_WARN_CONSTANT_CONVERSION = YES; 225 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 226 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 227 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 228 | CLANG_WARN_EMPTY_BODY = YES; 229 | CLANG_WARN_ENUM_CONVERSION = YES; 230 | CLANG_WARN_INFINITE_RECURSION = YES; 231 | CLANG_WARN_INT_CONVERSION = YES; 232 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 233 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 234 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 235 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 236 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 237 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 238 | CLANG_WARN_STRICT_PROTOTYPES = YES; 239 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 240 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 241 | CLANG_WARN_UNREACHABLE_CODE = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | COPY_PHASE_STRIP = NO; 244 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 245 | ENABLE_NS_ASSERTIONS = NO; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu11; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 256 | MTL_ENABLE_DEBUG_INFO = NO; 257 | MTL_FAST_MATH = YES; 258 | SDKROOT = iphoneos; 259 | SWIFT_COMPILATION_MODE = wholemodule; 260 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 261 | VALIDATE_PRODUCT = YES; 262 | }; 263 | name = Release; 264 | }; 265 | F67709932533A00D006E67C9 /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 269 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 270 | CODE_SIGN_STYLE = Automatic; 271 | DEVELOPMENT_ASSET_PATHS = "\"Progress View/Preview Content\""; 272 | ENABLE_PREVIEWS = YES; 273 | INFOPLIST_FILE = "Progress View/Info.plist"; 274 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 275 | LD_RUNPATH_SEARCH_PATHS = ( 276 | "$(inherited)", 277 | "@executable_path/Frameworks", 278 | ); 279 | PRODUCT_BUNDLE_IDENTIFIER = "alep.Progress-View"; 280 | PRODUCT_NAME = "$(TARGET_NAME)"; 281 | SWIFT_VERSION = 5.0; 282 | TARGETED_DEVICE_FAMILY = "1,2"; 283 | }; 284 | name = Debug; 285 | }; 286 | F67709942533A00D006E67C9 /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 290 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 291 | CODE_SIGN_STYLE = Automatic; 292 | DEVELOPMENT_ASSET_PATHS = "\"Progress View/Preview Content\""; 293 | ENABLE_PREVIEWS = YES; 294 | INFOPLIST_FILE = "Progress View/Info.plist"; 295 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 296 | LD_RUNPATH_SEARCH_PATHS = ( 297 | "$(inherited)", 298 | "@executable_path/Frameworks", 299 | ); 300 | PRODUCT_BUNDLE_IDENTIFIER = "alep.Progress-View"; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | SWIFT_VERSION = 5.0; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | }; 305 | name = Release; 306 | }; 307 | /* End XCBuildConfiguration section */ 308 | 309 | /* Begin XCConfigurationList section */ 310 | F677097E2533A00B006E67C9 /* Build configuration list for PBXProject "Progress View" */ = { 311 | isa = XCConfigurationList; 312 | buildConfigurations = ( 313 | F67709902533A00D006E67C9 /* Debug */, 314 | F67709912533A00D006E67C9 /* Release */, 315 | ); 316 | defaultConfigurationIsVisible = 0; 317 | defaultConfigurationName = Release; 318 | }; 319 | F67709922533A00D006E67C9 /* Build configuration list for PBXNativeTarget "Progress View" */ = { 320 | isa = XCConfigurationList; 321 | buildConfigurations = ( 322 | F67709932533A00D006E67C9 /* Debug */, 323 | F67709942533A00D006E67C9 /* Release */, 324 | ); 325 | defaultConfigurationIsVisible = 0; 326 | defaultConfigurationName = Release; 327 | }; 328 | /* End XCConfigurationList section */ 329 | }; 330 | rootObject = F677097B2533A00B006E67C9 /* Project object */; 331 | } 332 | -------------------------------------------------------------------------------- /Progress View/Progress View.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Progress View/Progress View.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Progress View/Progress View.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Progress View/Progress View.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Progress View/Progress View.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Progress View.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Progress View/Progress View/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Progress View/Progress View/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 | -------------------------------------------------------------------------------- /Progress View/Progress View/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Progress View/Progress View/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Progress View 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ContentView: View { 9 | 10 | @State private var progress: Double = 0 11 | private let total: Double = 1 12 | 13 | @State private var dataTask: URLSessionDataTask? 14 | @State private var observation: NSKeyValueObservation? 15 | @State private var image: UIImage? 16 | 17 | var body: some View { 18 | VStack { 19 | ZStack { 20 | if image == nil { 21 | ProgressView("Descargando imagen...", value: progress, total: total) 22 | .progressViewStyle(LinearProgressViewStyle()) 23 | .padding() 24 | } else { 25 | Image(uiImage: image!) 26 | .resizable() 27 | } 28 | } 29 | 30 | Spacer() 31 | 32 | HStack { 33 | Spacer() 34 | Button(action: { 35 | reset() 36 | downloadPhoto() 37 | }) { 38 | Image(systemName: "arrow.clockwise") 39 | } 40 | .font(.largeTitle) 41 | } 42 | .padding() 43 | }.onAppear(perform: downloadPhoto) 44 | } 45 | 46 | private func downloadPhoto() { 47 | guard let url = URL(string: "https://source.unsplash.com/random/4000x8000") else { return } 48 | 49 | dataTask = URLSession.shared.dataTask(with: url) { data, _, _ in 50 | observation?.invalidate() 51 | guard let data = data else { return } 52 | DispatchQueue.main.async { 53 | image = UIImage(data: data) 54 | } 55 | } 56 | 57 | observation = dataTask?.progress.observe(\.fractionCompleted) { observationProgress, _ in 58 | DispatchQueue.main.async { 59 | progress = observationProgress.fractionCompleted 60 | } 61 | } 62 | 63 | dataTask?.resume() 64 | } 65 | 66 | private func reset() { 67 | dataTask?.cancel() 68 | progress = 0 69 | image = nil 70 | } 71 | } 72 | 73 | struct ContentView_Previews: PreviewProvider { 74 | static var previews: some View { 75 | ContentView() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Progress View/Progress View/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Progress View/Progress View/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Progress View/Progress View/Progress_ViewApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Progress_ViewApp.swift 3 | // Progress View 4 | // 5 | // Created by Alejandrina Patrón López on 10/11/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct Progress_ViewApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Random Joke UIKit/Random Joke UIKit.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Random Joke UIKit.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Random Joke UIKit 4 | // 5 | // Created by Ale Patrón on 11/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/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 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/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 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Random Joke UIKit 4 | // 5 | // Created by Ale Patrón on 11/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Random Joke UIKit/Random Joke UIKit/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Programming Quotes UIKit 4 | // 5 | // Created by Ale Patrón on 11/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | private lazy var label: UILabel = { 13 | let view = UILabel(frame: .zero) 14 | view.translatesAutoresizingMaskIntoConstraints = false 15 | view.numberOfLines = 0 16 | view.text = "Hello world!" 17 | return view 18 | }() 19 | 20 | private lazy var refreshButton: UIButton = { 21 | let button = UIButton(frame: .zero) 22 | let image = UIImage(systemName: "arrow.clockwise") 23 | button.setImage(image, for: .normal) 24 | button.addTarget(self, action: #selector(loadData), for: .touchUpInside) 25 | button.translatesAutoresizingMaskIntoConstraints = false 26 | return button 27 | }() 28 | 29 | private var joke: Joke? { 30 | didSet { 31 | guard let joke = joke else { return } 32 | label.text = "\(joke.setup)\n\(joke.punchline)" 33 | label.sizeToFit() 34 | } 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | // Label and button setup 41 | view.addSubview(label) 42 | view.addSubview(refreshButton) 43 | addConstraints() 44 | 45 | // Get joke from API 46 | loadData() 47 | } 48 | 49 | private func addConstraints() { 50 | label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 51 | label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 52 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 53 | refreshButton.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 10).isActive = true 54 | refreshButton.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true 55 | } 56 | 57 | @objc private func loadData() { 58 | guard let url = URL(string: "https://official-joke-api.appspot.com/random_joke") else { 59 | return 60 | } 61 | URLSession.shared.dataTask(with: url) { data, response, error in 62 | guard let data = data else { return } 63 | if let decodedData = try? JSONDecoder().decode(Joke.self, from: data) { 64 | DispatchQueue.main.async { 65 | self.joke = decodedData 66 | } 67 | } 68 | }.resume() 69 | } 70 | } 71 | 72 | struct Joke: Decodable { 73 | var id: Int 74 | var type: String 75 | var setup: String 76 | var punchline: String 77 | } 78 | -------------------------------------------------------------------------------- /Random Joke/Random Joke.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Random Joke/Random Joke.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Random Joke/Random Joke.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Random Joke/Random Joke.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Random Joke/Random Joke.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Random Joke.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Random Joke 4 | // 5 | // Created by Alejandrina Patrón López on 12/14/20. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/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 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/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 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Random Joke 4 | // 5 | // Created by Alejandrina Patrón López on 12/14/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Random Joke/Random Joke/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Random Joke 4 | // 5 | // Created by Alejandrina Patrón López on 12/14/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | private lazy var label: UILabel = { 13 | let label = UILabel(frame: .zero) 14 | label.translatesAutoresizingMaskIntoConstraints = false 15 | label.numberOfLines = 0 16 | label.text = "Loading joke..." 17 | label.sizeToFit() 18 | return label 19 | }() 20 | 21 | private lazy var refreshButton: UIButton = { 22 | let button = UIButton(frame: .zero) 23 | let image = UIImage(systemName: "arrow.clockwise") 24 | button.setImage(image, for: .normal) 25 | button.addTarget(self, action: #selector(loadData), for: .touchUpInside) 26 | button.translatesAutoresizingMaskIntoConstraints = false 27 | return button 28 | }() 29 | 30 | private var dataTask: URLSessionDataTask? 31 | 32 | private var joke: Joke? { 33 | didSet { 34 | guard let joke = joke else { return } 35 | label.text = "\(joke.setup)\n\(joke.punchline)" 36 | label.sizeToFit() 37 | } 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | // Do any additional setup after loading the view. 43 | 44 | view.addSubview(label) 45 | view.addSubview(refreshButton) 46 | setConstraints() 47 | 48 | loadData() 49 | } 50 | 51 | private func setConstraints() { 52 | label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true 53 | label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true 54 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 55 | refreshButton.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 10).isActive = true 56 | refreshButton.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true 57 | } 58 | 59 | @objc private func loadData() { 60 | guard let url = URL(string: "https://official-joke-api.appspot.com/jokes/random") else { 61 | return 62 | } 63 | 64 | dataTask?.cancel() 65 | dataTask = URLSession.shared.dataTask(with: url) { data, response, error in 66 | guard let data = data else { return } 67 | if let decodedData = try? JSONDecoder().decode(Joke.self, from: data) { 68 | DispatchQueue.main.async { 69 | self.joke = decodedData 70 | } 71 | } 72 | } 73 | dataTask?.resume() 74 | } 75 | } 76 | 77 | struct Joke: Decodable { 78 | let id: Int 79 | let type: String 80 | let setup: String 81 | let punchline: String 82 | } 83 | -------------------------------------------------------------------------------- /Todos/Todos.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Todos/Todos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Todos/Todos.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Todos/Todos.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Todos/Todos.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Todos.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Todos/Todos/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Todos 4 | // 5 | // Created by Alejandrina Patrón López on 8/25/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Todos/Todos/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 | -------------------------------------------------------------------------------- /Todos/Todos/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Todos/Todos/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 | -------------------------------------------------------------------------------- /Todos/Todos/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Todos 4 | // 5 | // Created by Alejandrina Patrón López on 8/25/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | 13 | @State private var newTodo = "" 14 | @State private var allTodos: [TodoItem] = [] 15 | private let todosKey = "todosKey" 16 | 17 | var body: some View { 18 | NavigationView { 19 | VStack { 20 | HStack { 21 | TextField("Add todo...", text: $newTodo) 22 | .textFieldStyle(RoundedBorderTextFieldStyle()) 23 | 24 | Button(action: { 25 | guard !self.newTodo.isEmpty else { return } 26 | self.allTodos.append(TodoItem(todo: self.newTodo)) 27 | self.newTodo = "" 28 | self.saveTodos() 29 | }) { 30 | Image(systemName: "plus") 31 | } 32 | .padding(.leading, 5) 33 | }.padding() 34 | 35 | List { 36 | ForEach(allTodos) { todoItem in 37 | Text(todoItem.todo) 38 | }.onDelete(perform: deleteTodo) 39 | } 40 | } 41 | .navigationBarTitle("Todos") 42 | }.onAppear(perform: loadTodos) 43 | } 44 | 45 | private func saveTodos() { 46 | UserDefaults.standard.set(try? PropertyListEncoder().encode(self.allTodos), forKey: todosKey) 47 | } 48 | 49 | private func loadTodos() { 50 | if let todosData = UserDefaults.standard.value(forKey: todosKey) as? Data { 51 | if let todosList = try? PropertyListDecoder().decode(Array.self, from: todosData) { 52 | self.allTodos = todosList 53 | } 54 | } 55 | } 56 | 57 | private func deleteTodo(at offsets: IndexSet) { 58 | self.allTodos.remove(atOffsets: offsets) 59 | saveTodos() 60 | } 61 | } 62 | 63 | struct TodoItem: Codable, Identifiable { 64 | let id = UUID() 65 | let todo: String 66 | } 67 | 68 | struct ContentView_Previews: PreviewProvider { 69 | static var previews: some View { 70 | ContentView() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Todos/Todos/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Todos/Todos/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Todos/Todos/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Todos 4 | // 5 | // Created by Alejandrina Patrón López on 8/25/20. 6 | // Copyright © 2020 Ale Patrón. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/TwitterUI/TwitterUI.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TwitterUI/TwitterUI.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TwitterUI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/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 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TwitterUI 4 | // 5 | 6 | import SwiftUI 7 | 8 | extension Color { 9 | static var twitterBlue: Color = Color(red: 29/255, green: 161/255, blue: 241/255) 10 | } 11 | 12 | struct ContentView: View { 13 | 14 | private let tweets: [Tweet] = [ 15 | Tweet(authorName: "Alejandrina Patrón", 16 | authorUsername: "ale_patron", 17 | timestampText: "4h", 18 | text: "good morning 🌞", 19 | numberOfReplies: 2, 20 | numberOfRetweets: 0, 21 | numberOfLikes: 0), 22 | Tweet(authorName: "Jack", 23 | authorUsername: "jack", 24 | timestampText: "15h", 25 | text: "just setting up my twttr", 26 | numberOfReplies: 589, 27 | numberOfRetweets: 368, 28 | numberOfLikes: 450), 29 | Tweet(authorName: "Donald J. Trump", 30 | authorUsername: "realDonaldTrump", 31 | timestampText: "6h", 32 | text: "Despite the negative press covfefe", 33 | numberOfReplies: 2890, 34 | numberOfRetweets: 4565, 35 | numberOfLikes: 896), 36 | Tweet(authorName: "Jack", 37 | authorUsername: "jack", 38 | timestampText: "15h", 39 | text: "this is a tweet with a lot of text because I need to test multi-line tweets in my new SwiftUI twitter app :)", 40 | numberOfReplies: 589, 41 | numberOfRetweets: 368, 42 | numberOfLikes: 450), 43 | Tweet(authorName: "Barack Obama", 44 | authorUsername: "BarackObama", 45 | timestampText: "12h", 46 | text: "No one is born hating another person because of the color of his skin or his background or his religion...", 47 | numberOfReplies: 5589, 48 | numberOfRetweets: 3568, 49 | numberOfLikes: 4350), 50 | Tweet(authorName: "Jack", 51 | authorUsername: "jack", 52 | timestampText: "15h", 53 | text: "this is a tweet with a lot of text because I need to test multi-line tweets in my new SwiftUI twitter app :)", 54 | numberOfReplies: 589, 55 | numberOfRetweets: 368, 56 | numberOfLikes: 450), 57 | Tweet(authorName: "Jack", 58 | authorUsername: "jack", 59 | timestampText: "15h", 60 | text: "this is a tweet with a lot of text because I need to test multi-line tweets in my new SwiftUI twitter app :)", 61 | numberOfReplies: 589, 62 | numberOfRetweets: 368, 63 | numberOfLikes: 450), 64 | ] 65 | 66 | 67 | @State private var selectedTab = 0 68 | 69 | var body: some View { 70 | ZStack { 71 | TabView(selection: $selectedTab) { 72 | FeedView(tweets: tweets).tabItem { 73 | Image(systemName: "house") 74 | }.tag(0) 75 | Text("Tab Content 1").tabItem { 76 | Image(systemName: "magnifyingglass") 77 | }.tag(1) 78 | Text("Tab Content 2").tabItem { 79 | Image(systemName: "bell") 80 | }.tag(2) 81 | Text("Tab Content 3").tabItem { 82 | Image(systemName: "envelope") 83 | }.tag(3) 84 | }.accentColor(.twitterBlue) 85 | 86 | VStack { 87 | Spacer() 88 | HStack { 89 | Spacer() 90 | NewTweetButton() 91 | .padding(.bottom, 65) 92 | .padding(.trailing) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | struct NewTweetButton: View { 100 | var body: some View { 101 | Button(action: {}) { 102 | Image(systemName: "pencil") 103 | .font(.largeTitle) 104 | .foregroundColor(.white) 105 | .padding() 106 | } 107 | .background(Color.twitterBlue) 108 | .mask(Circle()) 109 | .shadow(radius: 5) 110 | } 111 | } 112 | 113 | struct FeedView: View { 114 | let tweets: [Tweet] 115 | 116 | var body: some View { 117 | NavigationView { 118 | List(tweets) { tweet in 119 | TweetView(tweet: tweet) 120 | } 121 | .listStyle(PlainListStyle()) 122 | .navigationBarTitle("Twitter", displayMode: .inline) 123 | .navigationBarItems( 124 | leading: 125 | Button(action: {}) { 126 | Image(systemName: "person.crop.circle.fill") 127 | .foregroundColor(.twitterBlue) 128 | }, 129 | trailing: 130 | Button(action: {}) { 131 | Image(systemName: "moon.stars") 132 | .foregroundColor(.twitterBlue) 133 | } 134 | ) 135 | } 136 | } 137 | } 138 | 139 | struct TweetView: View { 140 | let tweet: Tweet 141 | 142 | var body: some View { 143 | HStack(alignment: .top) { 144 | Image(systemName: "person.crop.circle.fill") 145 | .font(.system(size: 55)) 146 | .padding(.top) 147 | .padding(.trailing, 5) 148 | .foregroundColor(.twitterBlue) 149 | 150 | VStack(alignment: .leading) { 151 | HStack { 152 | Text(tweet.authorName) 153 | .bold() 154 | .lineLimit(1) 155 | Text("@\(tweet.authorUsername) • \(tweet.timestampText)") 156 | .lineLimit(1) 157 | .truncationMode(.middle) 158 | .foregroundColor(.gray) 159 | } 160 | .padding(.top, 5) 161 | 162 | Text(tweet.text) 163 | .lineLimit(nil) 164 | .multilineTextAlignment(.leading) 165 | 166 | TweetActionsView(tweet: tweet) 167 | .foregroundColor(.gray) 168 | .padding([.bottom, .top], 10) 169 | .padding(.trailing, 30) 170 | } 171 | } 172 | } 173 | } 174 | 175 | struct TweetActionsView: View { 176 | let tweet: Tweet 177 | 178 | var body: some View { 179 | HStack { 180 | Button(action: {}) { 181 | Image(systemName: "message") 182 | } 183 | Text(tweet.numberOfReplies > 0 ? "\(tweet.numberOfReplies)" : "") 184 | Spacer() 185 | 186 | Button(action: {}) { 187 | Image(systemName: "arrow.2.squarepath") 188 | } 189 | Text(tweet.numberOfRetweets > 0 ? "\(tweet.numberOfRetweets)" : "") 190 | Spacer() 191 | 192 | Button(action: {}) { 193 | Image(systemName: "heart") 194 | } 195 | Text(tweet.numberOfLikes > 0 ? "\(tweet.numberOfLikes)" : "") 196 | Spacer() 197 | 198 | Button(action: {}) { 199 | Image(systemName: "square.and.arrow.up") 200 | } 201 | } 202 | } 203 | } 204 | 205 | struct ContentView_Previews: PreviewProvider { 206 | static var previews: some View { 207 | ContentView() 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/Tweet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tweet.swift 3 | // TwitterUI 4 | // 5 | // Created by Alejandrina Patrón López on 11/1/20. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Tweet: Identifiable { 11 | let id = UUID() 12 | let authorName: String 13 | let authorUsername: String 14 | let timestampText: String 15 | let text: String 16 | let numberOfReplies: Int 17 | let numberOfRetweets: Int 18 | let numberOfLikes: Int 19 | } 20 | -------------------------------------------------------------------------------- /TwitterUI/TwitterUI/TwitterUIApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterUIApp.swift 3 | // TwitterUI 4 | // 5 | // Created by Alejandrina Patrón López on 10/24/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TwitterUIApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Weather/Weather.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Weather/Weather.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Weather/Weather.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apatronl/Medium/981046b3b77800adf42387fe9511d9dfd0f3c6f6/Weather/Weather.xcodeproj/project.xcworkspace/xcuserdata/alep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Weather/Weather.xcodeproj/xcshareddata/xcschemes/Weather.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 56 | 57 | 58 | 64 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Weather/Weather.xcodeproj/xcuserdata/alep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Weather.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F69FE14E2550E51A0025E8AA 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Weather/Weather/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Weather/Weather/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 | -------------------------------------------------------------------------------- /Weather/Weather/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Weather/Weather/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | NSLocationWhenInUseUsageDescription 10 | This app requires your location to provide weather data. 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 | 30 | UIApplicationSupportsIndirectInputEvents 31 | 32 | UILaunchScreen 33 | 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Weather/Weather/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Weather/Weather/Weather.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Weather.swift 3 | // Weather 4 | // 5 | 6 | import Foundation 7 | 8 | public struct Weather { 9 | let city: String 10 | let temperature: String 11 | let description: String 12 | let iconName: String 13 | 14 | init(response: APIResponse) { 15 | city = response.name 16 | temperature = "\(Int(response.main.temp))" 17 | description = response.weather.first?.description ?? "" 18 | iconName = response.weather.first?.iconName ?? "" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Weather/Weather/WeatherApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherApp.swift 3 | // Weather 4 | // 5 | 6 | import SwiftUI 7 | 8 | @main 9 | struct WeatherApp: App { 10 | var body: some Scene { 11 | WindowGroup { 12 | let weatherService = WeatherService() 13 | WeatherView(viewModel: WeatherViewModel(weatherService: weatherService)) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Weather/Weather/WeatherService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherService.swift 3 | // Weather 4 | // 5 | 6 | import CoreLocation 7 | import Foundation 8 | 9 | // Sample URL: 10 | // https://api.openweathermap.org/data/2.5/weather?lat=51.50998&lon=-0.1337&appid=YOUR_API_KEY&units=metric 11 | 12 | public final class WeatherService: NSObject { 13 | 14 | private let locationManager = CLLocationManager() 15 | private let API_KEY = "YOUR_API_KEY" // Replace with your own API key 16 | private var completionHandler: ((Weather?, LocationAuthError?) -> Void)? 17 | private var dataTask: URLSessionDataTask? 18 | 19 | public override init() { 20 | super.init() 21 | locationManager.delegate = self 22 | } 23 | 24 | public func loadWeatherData( 25 | _ completionHandler: @escaping((Weather?, LocationAuthError?) -> Void) 26 | ) { 27 | self.completionHandler = completionHandler 28 | loadDataOrRequestLocationAuth() 29 | } 30 | 31 | private func makeDataRequest(forCoordinates coordinates: CLLocationCoordinate2D) { 32 | guard let urlString = 33 | "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinates.latitude)&lon=\(coordinates.longitude)&appid=\(API_KEY)&units=metric" 34 | .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return } 35 | guard let url = URL(string: urlString) else { return } 36 | 37 | // Cancel previous task 38 | dataTask?.cancel() 39 | 40 | dataTask = URLSession.shared.dataTask(with: url) { data, response, error in 41 | guard error == nil, let data = data else { return } 42 | 43 | if let response = try? JSONDecoder().decode(APIResponse.self, from: data) { 44 | self.completionHandler?(Weather(response: response), nil) 45 | } 46 | } 47 | dataTask?.resume() 48 | } 49 | 50 | private func loadDataOrRequestLocationAuth() { 51 | switch locationManager.authorizationStatus { 52 | case .authorizedAlways, .authorizedWhenInUse: 53 | locationManager.startUpdatingLocation() 54 | case .denied, .restricted: 55 | completionHandler?(nil, LocationAuthError()) 56 | default: 57 | locationManager.requestWhenInUseAuthorization() 58 | } 59 | } 60 | } 61 | 62 | extension WeatherService: CLLocationManagerDelegate { 63 | public func locationManager( 64 | _ manager: CLLocationManager, 65 | didUpdateLocations locations: [CLLocation] 66 | ) { 67 | guard let location = locations.first else { return } 68 | makeDataRequest(forCoordinates: location.coordinate) 69 | } 70 | 71 | public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { 72 | loadDataOrRequestLocationAuth() 73 | } 74 | public func locationManager( 75 | _ manager: CLLocationManager, 76 | didFailWithError error: Error 77 | ) { 78 | print("Something went wrong: \(error.localizedDescription)") 79 | } 80 | } 81 | 82 | struct APIResponse: Decodable { 83 | let name: String 84 | let main: APIMain 85 | let weather: [APIWeather] 86 | } 87 | 88 | struct APIMain: Decodable { 89 | let temp: Double 90 | } 91 | 92 | struct APIWeather: Decodable { 93 | let description: String 94 | let iconName: String 95 | 96 | enum CodingKeys: String, CodingKey { 97 | case description 98 | case iconName = "main" 99 | } 100 | } 101 | 102 | public struct LocationAuthError: Error {} 103 | -------------------------------------------------------------------------------- /Weather/Weather/WeatherView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Weather 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct WeatherView: View { 9 | 10 | @ObservedObject var viewModel: WeatherViewModel 11 | 12 | var body: some View { 13 | VStack { 14 | Text(viewModel.cityName) 15 | .font(.largeTitle) 16 | .padding() 17 | Text(viewModel.temperature) 18 | .font(.system(size: 70)) 19 | .bold() 20 | Text(viewModel.weatherIcon) 21 | .font(.largeTitle) 22 | .padding() 23 | Text(viewModel.weatherDescription) 24 | } 25 | .alert(isPresented: $viewModel.shouldShowLocationError) { 26 | Alert( 27 | title: Text("Error"), 28 | message: Text("To see the weather, provide location access in Settings."), 29 | dismissButton: .default(Text("Open Settings")) { 30 | guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } 31 | UIApplication.shared.open(settingsURL) 32 | } 33 | ) 34 | } 35 | .onAppear(perform: viewModel.refresh) 36 | } 37 | } 38 | 39 | 40 | struct ContentView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | WeatherView(viewModel: WeatherViewModel(weatherService: WeatherService())) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Weather/Weather/WeatherViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherViewModel.swift 3 | // Weather 4 | // 5 | 6 | import Foundation 7 | 8 | private let defaultIcon = "❓" 9 | private let iconMap = [ 10 | "Drizzle" : "🌧", 11 | "Thunderstorm" : "⛈", 12 | "Rain": "🌧", 13 | "Snow": "❄️", 14 | "Clear": "☀️", 15 | "Clouds" : "☁️", 16 | ] 17 | 18 | class WeatherViewModel: ObservableObject { 19 | @Published var cityName: String = "City Name" 20 | @Published var temperature: String = "--" 21 | @Published var weatherDescription: String = "--" 22 | @Published var weatherIcon: String = defaultIcon 23 | @Published var shouldShowLocationError: Bool = false 24 | 25 | public let weatherService: WeatherService 26 | 27 | init(weatherService: WeatherService) { 28 | self.weatherService = weatherService 29 | } 30 | 31 | func refresh() { 32 | weatherService.loadWeatherData { weather, error in 33 | DispatchQueue.main.async { 34 | if let _ = error { 35 | self.shouldShowLocationError = true 36 | return 37 | } 38 | 39 | self.shouldShowLocationError = false 40 | guard let weather = weather else { return } 41 | self.cityName = weather.city 42 | self.temperature = "\(weather.temperature)ºC" 43 | self.weatherDescription = weather.description.capitalized 44 | self.weatherIcon = iconMap[weather.iconName] ?? defaultIcon 45 | } 46 | } 47 | } 48 | } 49 | --------------------------------------------------------------------------------