├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── README.md ├── SpriteKit ├── README.md └── Source │ ├── FlappyBirdLike │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── Particle Sprite Atlas.spriteatlas │ │ │ ├── Contents.json │ │ │ ├── bokeh.imageset │ │ │ ├── Contents.json │ │ │ └── bokeh.png │ │ │ └── spark.imageset │ │ │ ├── Contents.json │ │ │ └── spark.png │ ├── FlappyBirdLike │ │ ├── ActionList.sks │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ ├── Bird.spriteatlas │ │ │ │ ├── Contents.json │ │ │ │ ├── bird1.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── bird1@2x.png │ │ │ │ │ └── bird1@3x.png │ │ │ │ ├── bird2.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── bird2@2x.png │ │ │ │ │ └── bird2@3x.png │ │ │ │ ├── bird3.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── bird3@2x.png │ │ │ │ │ └── bird3@3x.png │ │ │ │ └── bird4.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── bird4@2x.png │ │ │ │ │ └── bird4@3x.png │ │ │ ├── Contents.json │ │ │ └── Environment.spriteatlas │ │ │ │ ├── Contents.json │ │ │ │ ├── ceiling.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── ceiling@2x.png │ │ │ │ └── ceiling@3x.png │ │ │ │ ├── gameoverBoard.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── gameoverBoard@2x.png │ │ │ │ └── gameoverBoard@3x.png │ │ │ │ ├── land.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── land@2x.png │ │ │ │ └── land@3x.png │ │ │ │ ├── medalBronze.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── medalBronze@2x.png │ │ │ │ └── medalBronze@3x.png │ │ │ │ ├── medalGold.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── medalGold@2x.png │ │ │ │ └── medalGold@3x.png │ │ │ │ ├── medalPlatinum.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── medalPlatinum@2x.png │ │ │ │ └── medalPlatinum@3x.png │ │ │ │ ├── medalSilver.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── medalSilver@2x.png │ │ │ │ └── medalSilver@3x.png │ │ │ │ ├── pipe.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── pipe@2x.png │ │ │ │ └── pipe@3x.png │ │ │ │ ├── playBtn.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── playBtn@2x.png │ │ │ │ └── playBtn@3x.png │ │ │ │ └── sky.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── sky@2x.png │ │ │ │ └── sky@3x.png │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Constants.swift │ │ ├── GameScene.swift │ │ ├── GameViewController.swift │ │ ├── Info.plist │ │ └── Resource │ │ │ ├── Font │ │ │ └── Minercraftory.ttf │ │ │ └── Sounds │ │ │ ├── bgm.mp3 │ │ │ ├── sfxDie.mp3 │ │ │ ├── sfxHit.mp3 │ │ │ ├── sfxPoint.mp3 │ │ │ ├── sfxSwooshing.mp3 │ │ │ └── sfxWing.mp3 │ ├── MyParticle.sks │ └── Smog.sks │ ├── README.md │ └── SwiteUIWithSprikteKit.swiftpm │ ├── .swiftpm │ └── xcode │ │ ├── package.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── hamp.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ └── hamp.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── ContentView.swift │ ├── MyApp.swift │ └── Package.swift └── SwiftUI └── Summary ├── @AppStorage └── README.md ├── @EnvironmentObject └── README.md ├── @ObservableObject & @StateObject └── README.md ├── @ViewBuilder └── README.md ├── @escaping └── README.md ├── ActionSheet └── README.md ├── Alert └── README.md ├── Animation Curves and Timing └── README.md ├── Animations └── README.md ├── AsyncImage └── README.md ├── Background Materials └── README.md ├── Backgrounds & Overlays └── README.md ├── Badges └── README.md ├── Binding PropertyWrapper └── README.md ├── Button styles └── README.md ├── Button └── README.md ├── ButtonStyle └── README.md ├── Codable └── README.md ├── Color └── README.md ├── ColorPicker └── README.md ├── Combine └── README.md ├── Conditional Statements(if-else) └── README.md ├── ContextMenu └── README.md ├── Core Data with @FetchRequest └── README.md ├── Custom AnyTransition └── README.md ├── Custom Keyboard Submit Button └── README.md ├── Custom Models └── README.md ├── Custom Shape Animation └── README.md ├── Custom Shape └── README.md ├── Dark mode └── README.md ├── Date Picker └── README.md ├── Drag Gesture └── README.md ├── Extracting functions and subviews └── README.md ├── FocusState └── README.md ├── ForEach └── README.md ├── Frames & Alignments └── README.md ├── Generics └── README.md ├── GeometryReader └── README.md ├── Gradients └── README.md ├── Grid └── README.md ├── Group └── README.md ├── Haptic └── README.md ├── Hashable Protocol └── README.md ├── High Order Function └── README.md ├── Icons └── README.md ├── If Let, Guard Let └── README.md ├── Ignore Safe Area └── README.md ├── Image └── README.md ├── Inits and Enums └── README.md ├── LazyStack └── README.md ├── List Style └── README.md ├── List Swipe Actions └── README.md ├── List └── README.md ├── Local Notification └── README.md ├── Long Press Gesture └── README.md ├── Magnification Gesture └── README.md ├── Markups & Documentation └── README.md ├── Mask └── README.md ├── MatchedGeometryEffect └── README.md ├── Multi-threading └── README.md ├── Multiple Sheets └── README.md ├── NavigationStack └── README.md ├── NavigationView └── README.md ├── OnAppear, OnDisappear └── README.md ├── OnTapGesture └── README.md ├── Padding & Spacer └── README.md ├── Path └── README.md ├── Picker └── README.md ├── Popover └── README.md ├── PreferenceKey └── README.md ├── PropertyWrappers └── README.md ├── Publishers, Subscribers └── README.md ├── README.md ├── Resizable Sheet └── README.md ├── Rotation Gesture └── README.md ├── SafeAreaInset └── README.md ├── ScrollView └── README.md ├── ScrollViewReader └── README.md ├── Shape └── README.md ├── Sheets └── README.md ├── Slider └── README.md ├── SoundEffect └── README.md ├── Stack └── README.md ├── State PropertyWrapper └── README.md ├── Stepper └── README.md ├── SwiftuiCrypto ├── README.md └── SwiftuiCrypto │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── logo.png │ ├── Contents.json │ ├── LaunchColors │ │ ├── Contents.json │ │ ├── LaunchAccentColor.colorset │ │ │ └── Contents.json │ │ └── LaunchBackgroundColor.colorset │ │ │ └── Contents.json │ ├── ThemeColors │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── BackgroundColor.colorset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── GreenColor.colorset │ │ │ └── Contents.json │ │ ├── RedColor.colorset │ │ │ └── Contents.json │ │ └── SecondaryTextColor.colorset │ │ │ └── Contents.json │ └── images │ │ ├── Contents.json │ │ ├── coingecko.imageset │ │ ├── Contents.json │ │ └── coingecko.png │ │ ├── logo-transparent.imageset │ │ ├── Contents.json │ │ ├── logo-transparent.png │ │ ├── logo-transparent@2x.png │ │ └── logo-transparent@3x.png │ │ └── logo.imageset │ │ ├── Contents.json │ │ └── logo.png │ ├── ContentView.swift │ ├── Core │ ├── Components │ │ ├── CircleButton │ │ │ ├── CircleButtonAnimationView.swift │ │ │ └── CircleButtonView.swift │ │ ├── CoinImage │ │ │ ├── CoinImageView.swift │ │ │ └── CoinImageViewModel.swift │ │ ├── CoinLogoView.swift │ │ ├── SearchBarView.swift │ │ ├── StatisticView.swift │ │ └── XMarkButton.swift │ ├── Detail │ │ ├── ViewModels │ │ │ └── DetailViewModel.swift │ │ └── Views │ │ │ ├── ChartView.swift │ │ │ └── DetailView.swift │ ├── Home │ │ ├── ViewModels │ │ │ └── HomeViewModel.swift │ │ └── Views │ │ │ ├── CoinRowView.swift │ │ │ ├── HomeStatsView.swift │ │ │ ├── HomeView.swift │ │ │ └── PortfolioView.swift │ ├── Launch │ │ └── Views │ │ │ ├── Launch Screen.storyboard │ │ │ └── LaunchView.swift │ └── Setting │ │ └── Views │ │ └── SettingView.swift │ ├── Extensions │ ├── Extension+Color.swift │ ├── Extension+Date.swift │ ├── Extension+Double.swift │ ├── Extension+PreviewProvider.swift │ ├── Extension+String.swift │ └── Extension+UIApplication.swift │ ├── Models │ ├── CoinDetailModel.swift │ ├── CoinModel.swift │ ├── MarketDataModel.swift │ ├── PortfolioContainer.xcdatamodeld │ │ └── PortfolioContainer.xcdatamodel │ │ │ └── contents │ └── StatisticModel.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Services │ ├── CoinDataService.swift │ ├── CoinDetailDataService.swift │ ├── CoinImageService.swift │ ├── MarketDataService.swift │ └── PortfolioDataService.swift │ ├── SwiftuiCryptoApp.swift │ └── Utilities │ ├── HapticManager.swift │ ├── LocalFileManager.swift │ └── NetworkingManager.swift ├── TabView └── README.md ├── Text └── README.md ├── TextField └── README.md ├── TextSelection └── README.md ├── Timer & onReceive └── README.md ├── TodoList ├── README.md └── TodoList │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Models │ └── ItemModel.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── TodoListApp.swift │ ├── ViewModels │ └── ListViewModel.swift │ └── Views │ ├── AddView.swift │ ├── ListRowView.swift │ ├── ListView.swift │ └── NoItemsView.swift ├── Toggle └── README.md ├── Toolbar └── README.md ├── Transition └── README.md ├── Typealias └── README.md ├── UIViewRepresentable └── README.md ├── URLSession and escaping closures └── README.md ├── ViewModifier └── README.md ├── Weak self └── README.md └── generateTemplate.py /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 제목 2 | 3 | ## 작업사항 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | 3 | ### macOS ### 4 | # General 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | ### macOS Patch ### 30 | # iCloud generated files 31 | *.icloud 32 | 33 | ### Xcode ### 34 | # Xcode 35 | # 36 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 37 | 38 | ## User settings 39 | xcuserdata/ 40 | 41 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 42 | *.xcscmblueprint 43 | *.xccheckout 44 | 45 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 46 | build/ 47 | DerivedData/ 48 | *.moved-aside 49 | *.pbxuser 50 | !default.pbxuser 51 | *.mode1v3 52 | !default.mode1v3 53 | *.mode2v3 54 | !default.mode2v3 55 | *.perspectivev3 56 | !default.perspectivev3 57 | 58 | ### Xcode Patch ### 59 | *.xcodeproj/* 60 | !*.xcodeproj/project.pbxproj 61 | !*.xcodeproj/xcshareddata/ 62 | !*.xcworkspace/contents.xcworkspacedata 63 | /*.gcno 64 | 65 | ### Projects ### 66 | *.xcodeproj 67 | *.xcworkspace 68 | 69 | ### Tuist derived files ### 70 | graph.dot 71 | Derived/ 72 | 73 | ### Tuist managed dependencies ### 74 | Tuist/Dependencies 75 | 76 | ### Tuist Signing ### 77 | Tuist/Signing 78 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bokeh.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "spark.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/ActionList.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/ActionList.sks -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FlappyBirdLike 4 | // 5 | // Created by yongbeomkwak on 2023/03/31. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | var window: UIWindow? 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 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 36 | } 37 | 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/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 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "bird1@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "bird1@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird1.imageset/bird1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird1.imageset/bird1@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird1.imageset/bird1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird1.imageset/bird1@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "bird2@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "bird2@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird2.imageset/bird2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird2.imageset/bird2@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird2.imageset/bird2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird2.imageset/bird2@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "bird3@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "bird3@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird3.imageset/bird3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird3.imageset/bird3@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird3.imageset/bird3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird3.imageset/bird3@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "bird4@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "bird4@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird4.imageset/bird4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird4.imageset/bird4@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird4.imageset/bird4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Bird.spriteatlas/bird4.imageset/bird4@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/ceiling.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "ceiling@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "ceiling@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/ceiling.imageset/ceiling@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/ceiling.imageset/ceiling@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/ceiling.imageset/ceiling@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/ceiling.imageset/ceiling@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/gameoverBoard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "gameoverBoard@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "gameoverBoard@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/gameoverBoard.imageset/gameoverBoard@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/gameoverBoard.imageset/gameoverBoard@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/gameoverBoard.imageset/gameoverBoard@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/gameoverBoard.imageset/gameoverBoard@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/land.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "land@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "land@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/land.imageset/land@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/land.imageset/land@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/land.imageset/land@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/land.imageset/land@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalBronze.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "medalBronze@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "medalBronze@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalBronze.imageset/medalBronze@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalBronze.imageset/medalBronze@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalBronze.imageset/medalBronze@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalBronze.imageset/medalBronze@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalGold.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "medalGold@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "medalGold@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalGold.imageset/medalGold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalGold.imageset/medalGold@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalGold.imageset/medalGold@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalGold.imageset/medalGold@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalPlatinum.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "medalPlatinum@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "medalPlatinum@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalPlatinum.imageset/medalPlatinum@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalPlatinum.imageset/medalPlatinum@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalPlatinum.imageset/medalPlatinum@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalPlatinum.imageset/medalPlatinum@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalSilver.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "medalSilver@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "medalSilver@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalSilver.imageset/medalSilver@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalSilver.imageset/medalSilver@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalSilver.imageset/medalSilver@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/medalSilver.imageset/medalSilver@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/pipe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "pipe@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "pipe@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/pipe.imageset/pipe@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/pipe.imageset/pipe@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/pipe.imageset/pipe@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/pipe.imageset/pipe@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/playBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "playBtn@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "playBtn@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/playBtn.imageset/playBtn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/playBtn.imageset/playBtn@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/playBtn.imageset/playBtn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/playBtn.imageset/playBtn@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/sky.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "sky@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "sky@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/sky.imageset/sky@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/sky.imageset/sky@2x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/sky.imageset/sky@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Assets.xcassets/Environment.spriteatlas/sky.imageset/sky@3x.png -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/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 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/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 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // FlappyBirdLike 4 | // 5 | // Created by yongbeomkwak on 2023/03/31. 6 | // 7 | 8 | import Foundation 9 | import SpriteKit 10 | 11 | struct Layer { 12 | 13 | static let sky: CGFloat = 1 14 | static let pipe: CGFloat = 2 15 | static let land: CGFloat = 3 16 | static let ceiling: CGFloat = 4 17 | static let bird: CGFloat = 5 18 | static let hud: CGFloat = 10 19 | 20 | } 21 | 22 | struct PhysicsCategory { 23 | static let bird:UInt32 = 0x1 << 0 // 1 24 | static let land:UInt32 = 0x1 << 1 // 2 25 | static let ceiling:UInt32 = 0x1 << 2 // 4 26 | static let pipe:UInt32 = 0x1 << 3 // 8 27 | static let score:UInt32 = 0x1 << 4 // 16 28 | 29 | } 30 | 31 | 32 | struct SoundFX { 33 | static let wing = SKAction.playSoundFileNamed("sfxWing.mp3", waitForCompletion: false) 34 | static let die = SKAction.playSoundFileNamed("sfxDie.mp3", waitForCompletion: false) 35 | static let hit = SKAction.playSoundFileNamed("sfxHit.mp3", waitForCompletion: false) 36 | static let point = SKAction.playSoundFileNamed("sfxPoint.mp3", waitForCompletion: false) 37 | static let swooshing = SKAction.playSoundFileNamed("sfxSwooshing.mp3", waitForCompletion: false) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // FlappyBirdLike 4 | // 5 | // Created by yongbeomkwak on 2023/03/31. 6 | // 7 | 8 | import SpriteKit 9 | 10 | class GameViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let view = self.view as! SKView? { 16 | // Load the SKScene from 'GameScene.sks' 17 | let scene = GameScene(size: view.bounds.size) 18 | // Set the scale mode to scale to fit the window 19 | scene.scaleMode = .aspectFill 20 | 21 | // Present the scene 22 | view.presentScene(scene) 23 | 24 | 25 | view.ignoresSiblingOrder = true // 노느를 그리는 순서를 컴퓨터가 알아서 하도록 (true) 26 | 27 | view.showsFPS = true // FPS 보여주기 28 | view.showsNodeCount = true 29 | view.showsPhysics = true // 피지스 바디 보여주기 30 | } 31 | } 32 | 33 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 34 | if UIDevice.current.userInterfaceIdiom == .phone { 35 | return .allButUpsideDown 36 | } else { 37 | return .all 38 | } 39 | } 40 | 41 | override var prefersStatusBarHidden: Bool { 42 | return true 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIAppFonts 6 | 7 | Minercraftory.ttf 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Font/Minercraftory.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Font/Minercraftory.ttf -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/bgm.mp3 -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxDie.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxDie.mp3 -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxHit.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxHit.mp3 -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxPoint.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxPoint.mp3 -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxSwooshing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxSwooshing.mp3 -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxWing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/FlappyBirdLike/Resource/Sounds/sfxWing.mp3 -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/MyParticle.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/MyParticle.sks -------------------------------------------------------------------------------- /SpriteKit/Source/FlappyBirdLike/Smog.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/FlappyBirdLike/Smog.sks -------------------------------------------------------------------------------- /SpriteKit/Source/README.md: -------------------------------------------------------------------------------- 1 | # SpriteKit 실습 프로젝트 2 | 3 | ## 1. SwiftUIWithSprikteKit 4 | - SwiftUI에 SpriteKitView를 이용하여 SpriteKit을 사용하는 방법 5 | 6 | - 첫번 째 터치를 인식하여 해당 위치에 빨간 사각형 노드 생성 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/hamp.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/hamp.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/.swiftpm/xcode/xcuserdata/hamp.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiteUIWithSprikteKit.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | AppModule 16 | 17 | primary 18 | 19 | 20 | SwiteUIWithSprikteKit 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SpriteKit 3 | 4 | class GameScence: SKScene { 5 | override func didMove(to view: SKView) { 6 | // 물리적인 작용이 있어 phsicsBody 필드를 선언 7 | physicsBody = SKPhysicsBody(edgeLoopFrom: frame) 8 | } 9 | 10 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 11 | 12 | 13 | //첫번 째 터치를 인식 14 | guard let touch = touches.first else {return} 15 | 16 | //현재 뷰에서 터치 위치를 찾음 17 | let location = touch.location(in: self) 18 | 19 | /* 20 | 1. W:50 H:50 빨간색 노드 정의 21 | 2. 노드 위치 지정 22 | 3. 해당 노드가 물리적인 작용을 해야하므로 물리바디 지정(모양:사각형 W:50 ,H:50) 23 | 4. 생성 24 | */ 25 | 26 | let box = SKSpriteNode(color:.red,size: CGSize(width: 50, height: 50)) 27 | box.position = location 28 | box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50)) 29 | 30 | addChild(box) 31 | 32 | 33 | } 34 | } 35 | 36 | struct ContentView: View { 37 | 38 | var scence: SKScene { 39 | let scence = GameScence() 40 | scence.size = CGSize(width: 300, height: 400) 41 | scence.scaleMode = .fill 42 | return scence 43 | 44 | } 45 | 46 | var body: some View { 47 | SpriteView(scene: scence) 48 | .frame(width: 300,height: 400) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SpriteKit/Source/SwiteUIWithSprikteKit.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "SwiteUIWithSprikteKit", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "SwiteUIWithSprikteKit", 18 | targets: ["AppModule"], 19 | bundleIdentifier: "test.SwiteUIWithSprikteKit", 20 | displayVersion: "1.0", 21 | bundleVersion: "1", 22 | appIcon: .placeholder(icon: .bird), 23 | accentColor: .presetColor(.purple), 24 | supportedDeviceFamilies: [ 25 | .pad, 26 | .phone 27 | ], 28 | supportedInterfaceOrientations: [ 29 | .portrait, 30 | .landscapeRight, 31 | .landscapeLeft, 32 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 33 | ] 34 | ) 35 | ], 36 | targets: [ 37 | .executableTarget( 38 | name: "AppModule", 39 | path: "." 40 | ) 41 | ] 42 | ) -------------------------------------------------------------------------------- /SwiftUI/Summary/@AppStorage/README.md: -------------------------------------------------------------------------------- 1 | # @AppStorage 2 | - 사용자 설정과 같은 간단한 데이터를 저장하고 검색할 수 있는 프로퍼티 래퍼 3 | 4 | ### UserDefaults 5 | - 앱의 설정 데이터, 사용자 기본값 같은 간단한 상태 정보 등을 저장하고 검색하는 데 사용되는 인터페이스이다. 6 | - `key-value` 형식으로 데이터를 저장하며, 각 데이터 항목에는 유일한 키와 해당 데이터의 값이 포함된다. 7 | ```swift 8 | // 값 저장 9 | UserDefaults.standard.set(true, forKey: "isOnboardingShown") 10 | UserDefaults.standard.set(10, forKey: "tapCount") 11 | UserDefaults.standard.set("Snack", forKey: "userName") 12 | 13 | // 값 검색 14 | let isOnboardingShown = UserDefaults.standard.bool(forKey: "isOnboardingShown") // true 15 | let tapCount = UserDefaults.standard.integer(forKey: "tapCount") // 10 16 | let userName = UserDefaults.standard.string(forKey: "userName") // "Snack" 17 | ``` 18 | - 저장된 데이터는 앱이 종료되어도 유지된다. 19 | ```swift 20 | struct AppStorageStudy: View { 21 | 22 | @State var currentUserName: String? 23 | 24 | var body: some View { 25 | VStack(spacing: 20) { 26 | Text(currentUserName ?? "DefaultName") 27 | Button("Save") { 28 | // 저장할 데이터 29 | let name: String = "Snack" 30 | currentUserName = name 31 | // 키 값으로 데이터 저장 32 | UserDefaults.standard.set(name, forKey: "userName") 33 | } 34 | } 35 | .onAppear { 36 | // 키 값에 따른 데이터 검색 37 | currentUserName = UserDefaults.standard.string(forKey: "userName") 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | ### @AppStorage 44 | - `@AppStorage`를 사용하면 영구적으로 유지할 데이터가 `UserDefaults`에 자동으로 저장되고 검색할 수 있다. 45 | - `@AppStorage` 키워드와 함께 해당 데이터를 식별할 키값을 같이 선언한다. 46 | ```swift 47 | struct AppStorageStudy: View { 48 | // 키 값으로 데이터 저장 49 | @AppStorage("userName") var currentUserName: String? 50 | 51 | var body: some View { 52 | VStack(spacing: 20) { 53 | Text(currentUserName ?? "DefaultName") 54 | Button("Save") { 55 | let name: String = "Snack" 56 | currentUserName = name 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | - `@AppStorage`로 선언된 프로퍼티는 값을 읽거나 변경할 때마다 `UserDefaults`가 자동으로 업데이트되고 해당 값을 사용할 수 있다. 63 | -------------------------------------------------------------------------------- /SwiftUI/Summary/@escaping/README.md: -------------------------------------------------------------------------------- 1 | # @escaping 2 | - 클로저가 함수의 파라미터로 전달될 때 함수를 벗어나서(함수의 실행이 종료된 후) 실행되는 클로저 3 | 4 | ### Non-Escaping Closure 5 | - 일반적으로 함수의 파라미터로 전달된 클로저는 함수 내에서만 실행되며, 함수의 범위를 벗어나면 자동으로 소멸한다. 6 | ```swift 7 | class EscapingViewModel: ObservableObject { 8 | 9 | @Published var text: String = "Hello" 10 | 11 | func getData() { 12 | downloadData { data in 13 | text = data 14 | } 15 | } 16 | 17 | func downloadData(completion: (_ data: String) -> Void) { 18 | // 클로저 실행 후 함수 종료 19 | completion("New Data!") 20 | } 21 | } 22 | ``` 23 | 24 | ### Escaping Closure 25 | - `@escaping` 속성이 지정된 클로저는 함수의 범위를 벗어나도 계속해서 실행될 수 있다. 26 | - 주로 클로저가 비동기(asynchronous) 작업을 수행하는 경우, `@escaping` 속성을 사용하여 클로저가 함수의 범위를 벗어날 수 있도록 허용할 수 있다. 27 | ```swift 28 | class EscapingViewModel: ObservableObject { 29 | 30 | @Published var text: String = "Hello" 31 | 32 | func getData() { 33 | // 비동기 처리를 위한 약한 참조 처리 34 | downloadData { [weak self] data in 35 | self?.text = data 36 | } 37 | } 38 | 39 | func downloadData(completion: @escaping (_ data: String) -> Void) { 40 | // 함수 종료 후 2초 뒤 클로저 실행 41 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { 42 | completion("New Data!") 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ### Model 생성 및 Typealias 활용 49 | - 데이터 모델을 생성하여 여러 데이터에 더 쉽게 접근할 수 있다. 50 | ```swift 51 | class EscapingViewModel: ObservableObject { 52 | 53 | @Published var text: String = "Hello" 54 | 55 | func getData() { 56 | downloadData { [weak self] DownloadResult in 57 | // 데이터 접근 58 | self?.text = DownloadResult.data 59 | } 60 | } 61 | 62 | func downloadData(completion: @escaping (DownloadResult) -> Void) { 63 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { 64 | let result = DownloadResult(data: "New Data") 65 | completion(result) 66 | } 67 | } 68 | } 69 | 70 | // 데이터 모델 생성 71 | struct DownloadResult { 72 | let data: String 73 | } 74 | ``` 75 | - `typealias`를 활용하여 코드를 더 간결하게 정리할 수 있다. 76 | ```swift 77 | class EscapingViewModel: ObservableObject { 78 | 79 | @Published var text: String = "Hello" 80 | 81 | func getData() { 82 | downloadData { [weak self] DownloadResult in 83 | self?.text = DownloadResult.data 84 | } 85 | } 86 | 87 | // 지정한 타입의 클로저 파라미터 88 | func downloadData(completion: @escaping DownloadCompletion) { 89 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { 90 | let result = DownloadResult(data: "New Data") 91 | completion(result) 92 | } 93 | } 94 | } 95 | 96 | struct DownloadResult { 97 | let data: String 98 | } 99 | 100 | // 임의 타입명 지정 101 | typealias DownloadCompletion = (DownloadResult) -> Void 102 | ``` 103 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Animation Curves and Timing/README.md: -------------------------------------------------------------------------------- 1 | # **#26 Animation Curves and Timing** 2 | 3 | ```swift 4 | @State var isAnimating: Bool = false 5 | let timing:Double = 5.0 //애니메이션 진행 시간을 지정하는 변수 6 | 7 | var body: some View { 8 | VStack { 9 | Button("Button") { 10 | isAnimating.toggle() 11 | } 12 | RoundedRectangle(cornerRadius: 20) 13 | .frame(width: isAnimating ? 350 : 50, height: 100) 14 | .animation(Animation 15 | .linear(duration: timing) //일정한 속도 16 | .easeIn(duration: timing) //느림-빠름 17 | .easeInOut(duration: timing) //느림-빠름-느림 18 | .easeOut(duration: timing) //빠름-느림 19 | )} 20 | } 21 | ``` 22 | ![화면 기록 2023-05-02 오전 1 35 33](https://user-images.githubusercontent.com/87987002/235489079-3c6d7e73-1158-49f9-92e9-a1632eb36c4c.gif) 23 | 24 | 커브는 애니메이션의 속도를 조정하고 duration은 애니메이션이 진행되는 시간을 지정한다. 25 |
26 |
27 |
28 | 29 | ## Spring Curves 30 | ```swift 31 | .animation(Animation 32 | .spring( 33 | response: 0.5, //애니메이션 경과 시간 34 | dampingFraction: 0.7, //스프링의 강도, 값이 낮을 수록 강도가 높아짐. 35 | blendDuration: 1.0)) //보통 1.0 36 | ``` 37 | 38 | ![화면 기록 2023-05-02 오전 1 49 13](https://user-images.githubusercontent.com/87987002/235491276-497f9105-bdb1-4552-b83c-fb55a62b3822.gif) 39 | 40 | 유용하게 쓸수 있는 애니메이션. 기본값으로도 많이 쓰고 커스텀해서도 쓸 수 있다. 41 | 42 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Animations/README.md: -------------------------------------------------------------------------------- 1 | # **#25 Animations** 2 | 3 | ```swift 4 | @State var isanimated: Bool = false 5 | 6 | var body: some View { 7 | VStack { 8 | Button("Button") { 9 | withAnimation(.default) { //defeult는 fade 효과 10 | isanimated.toggle() 11 | } 12 | 13 | } 14 | Spacer() 15 | RoundedRectangle(cornerRadius: isanimated ? 50 : 25) //코너값 변화 16 | .fill(isanimated ? Color.red : Color.gray) //컬러 변화 17 | .frame( 18 | width: isanimated ? 100 : 300, //가로 변화 19 | height: isanimated ? 100 : 300) //세로 변화 20 | Spacer() 21 | } 22 | } 23 | ``` 24 | ![화면 기록 2023-05-01 오후 11 17 28](https://user-images.githubusercontent.com/87987002/235465755-77f1ed95-f79b-4663-baa7-37d502c5363d.gif) 25 | 26 | 디폴트 애니메이션은 fade. **삼항 연산자(tenary operation)** 와 **@State** 를 통해 바뀌는 속성을 확인하고 뷰를 새로 그린다. 27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | ## 애니메이션 커스터마이즈 35 | 36 | ```swift 37 | withAnimation( 38 | Animation 39 | .default //디폴트는 fade 변환 40 | .delay(2.0) //지정된 시간만큼 딜레이 41 | .repeatCount(5, autoreverses: true) //지정된 횟수만큼 반복 42 | .repeatForever(autoreverses: true) //영원히 반복 43 | ){ 44 | isanimated.toggle() 45 | } 46 | ``` 47 | 애니메이션 종류를 나열한 것. 실제로는 이렇게 쓰면 오류가 남. 속성 앞에 Animation이라고 써줘야 오류가 안 뜸.
autoreverses는 바뀌기 전 상태로 되돌아가는 것. 48 | 49 | ![화면 기록 2023-05-02 오전 12 13 21](https://user-images.githubusercontent.com/87987002/235475126-38c8e8b1-d12e-46f3-80c9-48cec408ab08.gif) 50 | ```.repeatCount(5, autoreverses: true)``` 를 실행했을 때 모습. 5번 바뀐다. 51 | 52 |
53 |
54 |
55 | 56 | 57 | ## 객체에 직접 애니메이션 추가하기 58 | ```swift 59 | RoundedRectangle(cornerRadius: isanimated ? 50 : 25) 60 | .fill(isanimated ? Color.red : Color.gray) 61 | .frame( 62 | width: isanimated ? 100 : 300, 63 | height: isanimated ? 100 : 300) 64 | .rotationEffect(Angle(degrees: isanimated ? 360 : 0)) 65 | 66 | 67 | // animation 수정자를 쓸 때에는 value값을 같이 써줘야 함. IOS 15부터 deprecated 되는 기능임. 68 | .animation(Animation 69 | .default 70 | .repeatForever(autoreverses: true), value: isanimated) // 이렇게 써줘야 된다. 71 | ``` 72 | 변수를 이용할 때에는 변수가 적용된 모든 객체에 애니메이션이 적용되지만 유일한 객체에만 애니메이션을 주고 싶으면 객체에 추가하면 됨. 이 방식은 애니메이션을 멈추거나 수정할 수 없다. 그래서 위의 방법인 변수를 이용하기를 추천. 73 | 74 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Background Materials/README.md: -------------------------------------------------------------------------------- 1 | # **#55 Background Materials** 2 | 3 | 4 | ### **`iOS 15에서 사용 가능한 Background Materials`** 5 | 쉽게 말해서 배경에 블러처리와 같은 고급효과를 만들어 줄 수 있음 6 | 7 |
8 | 9 | 10 | 11 | ```swift 12 | // 13 | // BackgroundMaterialsBootcamp.swift 14 | // SwiftUIBootcamp 15 | // 16 | // Created by David Goggins on 2023/05/25. 17 | // 18 | 19 | import SwiftUI 20 | 21 | struct BackgroundMaterialsBootcamp: View { 22 | var body: some View { 23 | VStack { 24 | Spacer() 25 | 26 | VStack { 27 | RoundedRectangle(cornerRadius: 4) 28 | .frame(width: 50, height: 4) 29 | .padding() 30 | Spacer() 31 | } 32 | .frame(height: 350) 33 | .frame(maxWidth: .infinity) 34 | // .background(Color.white.opacity(0.5)) <- 일반적인 방식 35 | // .background(.thinMaterial) <- Material 36 | // .background(.thickMaterial) 37 | // .background(.regularMaterial) 38 | // .background(.ultraThickMaterial) 39 | .background(.ultraThinMaterial) 40 | 41 | 42 | .cornerRadius(30) 43 | } 44 | .ignoresSafeArea() 45 | .background( 46 | Image("jaypark") 47 | ) 48 | } 49 | } 50 | 51 | struct BackgroundMaterialsBootcamp_Previews: PreviewProvider { 52 | static var previews: some View { 53 | BackgroundMaterialsBootcamp() 54 | } 55 | } 56 | ``` 57 | 58 | `ultraThickMaterial ← 가장 두꺼운 블러처리 느낌을 줄 수 있음` 59 | 60 | `ultraThinMaterial ← 가장 옅은 블러처리 느낌을 줄 수 있음` 61 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Badges/README.md: -------------------------------------------------------------------------------- 1 | # **#59 Badges** 2 | - list와 tab bar에만 적용된다. 3 | 4 | ## Tab bar_Badge 5 | 6 | ```swift 7 | TabView { 8 | Color.red 9 | .tabItem { 10 | Image(systemName: "heart.fill") 11 | Text("Hello") 12 | } 13 | .badge(3) 14 | } 15 | ```` 16 | 스크린샷 2023-05-24 오후 9 39 20 17 | 18 | 숫자뿐 아니라 텍스트로 뱃지에 띄울 수 있음 19 | 20 |
21 |
22 |
23 | 24 | ## List_ Badge 25 | 26 | ```swift 27 | List { 28 | Text("Hello, world!") 29 | .badge("NEW ITEMS!") 30 | } 31 | ``` 32 | 스크린샷 2023-05-24 오후 10 59 24 33 | 34 | secondary 컬러로 리스트 옆에 텍스트가 써짐. -------------------------------------------------------------------------------- /SwiftUI/Summary/Binding PropertyWrapper/README.md: -------------------------------------------------------------------------------- 1 | # @Binding 2 | - 데이터의 값을 변경하고 다른 뷰와 동기화하는 프로퍼티 래퍼(property wrapper) 3 | 4 | ### @State와 @Binding 5 | - `@State`에서 저장된 뷰의 상태를 다른 뷰와 공유하고 변경할 수 있게 한다. 6 | - `@State`는 값이 변경될 때 뷰를 다시 그리고, `@Binding`은 값이 변경될 때 뷰 간의 데이터를 동기화합니다. 7 | - `@Binding`을 선언할 때에는 `$`를 붙여 사용한다. 8 | ```swift 9 | struct BindingStudy: View { // 부모 뷰 10 | 11 | @State var backgroundColor: Color = Color.green // State 변수 선언 12 | 13 | var body: some View { 14 | ZStack { 15 | backgroundColor 16 | .edgesIgnoringSafeArea(.all) 17 | ButtonView(backgroundColor: $backgroundColor) // $를 통해 Binding 18 | 19 | } 20 | } 21 | } 22 | 23 | struct ButtonView: View { // 자식 뷰 24 | 25 | @Binding var backgroundColor: Color // @Binding을 통해 State 변수 Binding 26 | 27 | var body: some View { 28 | Button { 29 | backgroundColor = Color.orange // Binding된 변수 값 변경 30 | } label: { 31 | Text("Button") 32 | .foregroundColor(.white) 33 | .padding() 34 | .padding(.horizontal) 35 | .background(.blue) 36 | .cornerRadius(10) 37 | } 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Button styles/README.md: -------------------------------------------------------------------------------- 1 | # **#57 Button styles and control sizes** 2 | - ios 15에서는 기본 수정자로 버튼 스타일을 지정할 수 있다. 3 | 4 |
5 | 6 | ## **buttonStyle** 7 | 8 | 9 | ```swift 10 | VStack{ 11 | Button("Button Title") { 12 | } 13 | .frame(height: 55) 14 | .frame(maxWidth: .infinity) 15 | .buttonStyle(.plain) //기본 버튼 사이즈, AccentColor(blue) 없음 16 | 17 | Button("Button Title") { 18 | } 19 | .frame(height: 55) 20 | .frame(maxWidth: .infinity) 21 | .buttonStyle(.bordered) // 폰트에 AccentColor, secondary background 22 | 23 | Button("Button Title") { 24 | } 25 | .frame(height: 55) 26 | .frame(maxWidth: .infinity) 27 | .buttonStyle(.borderedProminent) //배경이 AccentColor가 됨. 28 | 29 | Button("Button Title") { 30 | } 31 | .frame(height: 55) 32 | .frame(maxWidth: .infinity) 33 | .buttonStyle(.borderless) //폰트에 AccentColor, 버튼 경계가 안보임 34 | } 35 | ``` 36 | AccentColor는 Assets파일에서 원하는 컬러로 지정할 수 있음. 37 | 38 | 39 | 스크린샷 2023-05-22 오전 4 04 56 40 | 41 |
42 |
43 |
44 | 45 | ## **controllSize** 46 | 버튼의 사이즈 조정 47 | ```swift 48 | VStack{ 49 | Button("Button Title") { 50 | } 51 | .frame(height: 55) 52 | .frame(maxWidth: .infinity) 53 | .buttonStyle(.borderedProminent) 54 | .controlSize(.large) //순서대로 large, regular, small, mini 55 | } 56 | ``` 57 | controlSize와 buttonStyle은 버튼 자체에 적용되는 게 아니라 버튼 내부의 label에 적용되는 것. 디폴트 label에 .large가 적용됨 58 | 59 | 스크린샷 2023-05-22 오전 4 31 37 60 | 61 |
62 |
63 |
64 | 65 | 66 | ```swift 67 | Button { 68 | } label: { 69 | Text("Button Title") 70 | .frame(height: 55) 71 | .frame(maxWidth: .infinity) 72 | 73 | } 74 | .controlSize(.large) 75 | .buttonStyle(.borderedProminent) 76 | ``` 77 | 78 | 79 | 그래서 label을 커스텀할 수 있는 버튼을 만든다면, 프레임이 지정된 label에 controlSize와 buttonStyle을 적용할 수도 있다.
이렇게 버튼의 크기를 리사이징할 수 있음. 80 | 81 | 82 | 스크린샷 2023-05-22 오전 4 49 32 83 | 84 | 85 |
86 |
87 |
88 | 89 | ## **buttonBorderShape** 90 | ```swift 91 | Button { 92 | } label: { 93 | Text("Button Title") 94 | .frame(height: 55) 95 | .frame(maxWidth: .infinity) 96 | 97 | } 98 | .controlSize(.large) 99 | .buttonStyle(.borderedProminent) 100 | .buttonBorderShape(.capsule) //캡슐형태 101 | ``` 102 | 103 | 스크린샷 2023-05-22 오전 4 56 57 104 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Button/README.md: -------------------------------------------------------------------------------- 1 | # **#18 Buttons** 2 | 3 | ## Button 생성 및 동작 4 | 5 | ![image.jpg1](https://user-images.githubusercontent.com/126866283/235300862-1bf0a41c-056d-41a6-9d16-d1fe70331209.png) |![image.jpg2](https://user-images.githubusercontent.com/126866283/235300883-26127abc-1e27-4882-97b2-faa7a5dc3e3e.png) |![image.jpg3](https://user-images.githubusercontent.com/126866283/235300899-71f417f4-dcff-48c4-852b-2de804d5da87.png) 6 | --- | --- | --- 7 | 8 | 9 | - **Button(title: *StringProtocol*, action: *() → Void*)** 형식 10 | - Button(”버튼 이름”, [버튼 눌렀을 시 나오는 action]) 11 | 12 | ```swift 13 | struct ButtonsBootcamp: View { 14 | @State var title: String = "This is my title" // (1) 15 | 16 | var body: some View { 17 | VStack(spacing: 20) { // 간격 20 18 | 19 | // (2) 'Press me!'를 누르면 'This is my title' -> 'BUTTON WAS PRESSED'로 바뀜 20 | Text(title) 21 | Button("Press me!") { 22 | self.title = "BUTTON WAS PRESSED" 23 | } 24 | .accentColor(.red) // 버튼 글씨 빨갛게 바꾸기 25 | ``` 26 | 27 | 빨간 “Press me!” 버튼을 누르면 ‘This is my title’ → ‘BUTTON WAS PRESSED’ 로 바뀐다. 28 | 29 |
30 | 31 | - ⭐️⭐️⭐️**Button(action: *{ }*, label: *() → Void*)** 형식 32 | 33 | ```swift 34 | // (3) 'Button'을 누르면 'BUTTON WAS PRESSED' -> 'Button #2 was pressed' 로 바뀜 35 | Button(action: { 36 | self.title = "Button #2 was pressed" 37 | }, label: { 38 | Text("Button") 39 | }) 40 | 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | 파란 “Button” 버튼을 누르면 ‘BUTTON WAS PRESSED’ → ‘Button #2 was pressed’ 로 바뀐다. 47 | 48 |
49 |
50 | 51 | ## Customize Button Design 52 | 53 | 54 | 55 | ```swift 56 | Button(action: { 57 | self.title = "Button #2 was pressed" 58 | }, label: { 59 | Text("Save".uppercased()) 60 | .font(.headline) 61 | .fontWeight(.semibold) 62 | .foregroundColor(.white) 63 | .padding() // frame 영역 넓어지게 (디폴트값 상하좌우 8) 64 | .padding(.horizontal, 20) // frame 가로 길이 길어지게 65 | .background( 66 | Color.blue // frame 파란 배경 입히기 67 | .cornerRadius(10) // 모서리 10만큼 둥글게 68 | .shadow(radius: 10) // 10만큼 그림자 69 | ) 70 | }) 71 | ``` 72 | 73 | let myColor = #colorLiteral() 선언 후, 74 | 75 |
76 | 77 | 78 | 79 | 80 | ```swift 81 | // 하트 버튼 82 | Button(action: { 83 | self.title = "Button #3" 84 | }, label: { 85 | Circle() 86 | .fill(Color.white) 87 | .frame(width: 75, height:75) 88 | .shadow(radius: 10) 89 | .overlay( 90 | Image(systemName: "heart.fill") // 하트 아이콘 91 | .font(.largeTitle) // 아이콘은 font로 크기조절 92 | .foregroundColor(Color(myColor)) // 아이콘 색상 변경 93 | ) 94 | }) 95 | ``` 96 | 97 | 98 | 99 | 100 | ```swift 101 | // FINISH 버튼 102 | Button(action: { 103 | self.title = "Button #4" 104 | }, label: { 105 | Text("Finish".uppercased()) 106 | .font(.caption) 107 | .bold() 108 | .foregroundColor(.gray) 109 | .padding() 110 | .padding(.horizontal, 10) 111 | .background( 112 | Capsule() 113 | .stroke(Color.gray, lineWidth: 2.0) 114 | ) 115 | }) 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Codable/README.md: -------------------------------------------------------------------------------- 1 | # Codable, Decodable, and Encodable 2 | 3 | 4 | 5 | ### 1. Encodable 프로토콜 6 | - 원하는 형태로 바꾸어줌 $f(x)$ 함수와 같다 7 | - 스위프트의 struct구조의 *객체*를 **json형식**으로 변한 하는 것 8 | - 자기 자신을 외부 표현으로 encode 할 수 있는 타입 9 | 10 | 11 |
12 | 13 | ### 2. Decodable 프로토콜 14 | - 그 형태를 해석해줌 $f^-(x)$ 역함수와 같다 15 | - **json형식**을 *객체*로 변환 16 | - 자기 자신을 외부 표현으로 encode 할 수 있는 타입 17 | 18 |
19 | 20 | ### 3. Codable 21 | - Codable = Encodable + Decodable 22 | 23 |
24 | 25 | ### 4. CodingKeys 26 | - 디코딩 과정에서 json key가 아닌 내가 원하는 이름으로 지정해줄 수 있게 해주는 프로토콜입니다. 27 | 28 |
29 | 30 | ```swift 31 | 32 | /* 33 | 반드시 모든 변수가 enum안에 명시되어야함 34 | 35 | 바뀔 키값들은 아래와 같은 형식으로 사용됨 36 | 37 | case 바뀐 결과값 = 바뀌기 전 값 38 | 39 | */ 40 | 41 | struct User: Codable { 42 | var userName: String 43 | var userEmail: String 44 | var tmp : String 45 | 46 | enum CodingKeys: String, CodingKey { 47 | 48 | case userName = "user_name" //user_name -> userName 49 | case userEmail = "user_email" // user_email -> userEmail 50 | case tmp // 키값이 같은 경우 tmp -> tmp 51 | } 52 | } 53 | 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Color/README.md: -------------------------------------------------------------------------------- 1 | # Color 2 | - 뷰의 색상을 변경 3 | 4 | ### System Color 5 | - 시스템에서 미리 지정된 색상을 사용할 수 있다. 6 | ```swift 7 | RoundedRectangle(cornerRadius: 24.0) 8 | .frame(width: 300, height: 200) 9 | .fill(Color.red) // 시스템 red 색상 10 | .fill(Color.primary) // 시스템 primary 색상(시스템 디스플레이 모드에 따라 변경) 11 | .fill(Color(UIColor.secondarySystemBackground) // UIKit의 system 색상(시스템 디스플레이 모드에 따라 변경) 12 | ``` 13 | 14 | ### Custom Color 15 | * 원하는 색상을 미리 지정하고 색상 이름으로 적용할 수 있다. 16 | 1. Assets 폴더의 새로운 Color Set 추가 및 이름 지정 17 | 2. inspector에서 Appearance별 색상 지정(디스플레이 모드에 상관 없이 한 가지의 색상으로 지정하고 싶을 경우, Appearances를 'None'으로 설정) 18 | 3. 색상 이름(String)으로 색상 적용 19 | 20 | ![](https://velog.velcdn.com/images/snack/post/c4895b51-0a35-4ab7-ba95-f292790af2be/image.png) 21 | ![](https://velog.velcdn.com/images/snack/post/e191ba24-6be4-4677-a458-3e3bf69793e2/image.png) 22 | ```swift 23 | RoundedRectangle(cornerRadius: 24.0) 24 | .frame(width: 300, height: 200) 25 | .fill(Color("customColor")) // 미리 지정한 "customColor" 색상 26 | ``` 27 | 28 | ### Shadow 29 | * 원하는 색상으로 뷰에 그림자를 추가할 수 있다. 30 | ```swift 31 | Rectangle() 32 | .frame(width: 300, height: 200) 33 | .shadow(color: Color("customColor"), radius: 16, x: 8, y: 8) // "customColor" 색상의 그림자 34 | ``` 35 | -------------------------------------------------------------------------------- /SwiftUI/Summary/ColorPicker/README.md: -------------------------------------------------------------------------------- 1 | # ColorPicker 2 | 3 | ## 정의 4 | 5 | 선택지가 정해져 있고 거기에서 하나의 요소를 선택할 때 사용한다. 6 | 7 |
8 |
9 | 10 | ## 구성 요소 11 | 12 | ColorPicker(titleKey: StringProtocol, selection: Binding Color) 13 | 14 | - titleKey: Label과 같은 역할 15 | - selection: 현재 선택된 색을 가르킬 바인딩 가능한 변수 16 | - supportsOpacity: 투명도 사용 여부, default = true 17 | 18 | 19 | ```swift 20 | import SwiftUI 21 | 22 | struct ContentView: View { 23 | 24 | @State var backgroundColor:Color = .green 25 | 26 | 27 | var body: some View { 28 | ZStack{ 29 | 30 | backgroundColor.edgesIgnoringSafeArea(.all) 31 | 32 | ColorPicker("Select a Color", selection: $backgroundColor,supportsOpacity: true) 33 | .padding() 34 | .background(.black) 35 | .cornerRadius(10) 36 | .foregroundColor(.white) // TitleKey 색깔 37 | 38 | 39 | } 40 | } 41 | 42 | } 43 | ``` 44 | 45 | 46 |

47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /SwiftUI/Summary/ContextMenu/README.md: -------------------------------------------------------------------------------- 1 | # Context Menu 2 | 3 | iOS 13 이후부터 인터페이스를 복잡하게 만들지 않고, onscreen items 와 관련된 추가 기능에 액세스할 수 있도록 할 수 있습니다. 4 | 5 | 다양한/여러 개의 버튼을 사용자에게 보여주는 방식 중 하나!
6 | 사용자가 object를 click & hold 하면 object 옆으로 여러 버튼으로 이루어진 context menu가 나타남

7 | 8 | 버튼이 나타날 때는 UI bounce 효과가 살짝 있고, 버튼들의 list가 나타남

9 | 10 | (alert 와 actionsheet보단 덜 유명한 방식)
11 | (점점 더 자주 쓰이게 되고 있음) 12 | 13 | 14 | context menu 를 표시하기 위해서 사람들은 시스템 정의 touch and hold gesture 또는 3D Touch 를 사용할 수 있습니다. 15 | 16 | context menu 가 열리면, 아이템의 미리보기와 명령 리스트를 보여줍니다. 사람들은 명령을 선택하거나 항목을 다른 영역, 윈도우, 앱으로 끌 수 있습니다. 17 | 18 | 밑 함수를 이용한다. 19 | 20 |
21 | 22 | ```swift 23 | .contextMenu(menuItems:() -> View) 24 | ``` 25 | 26 | ### 사용 예제 27 | 28 | ```swift 29 | Text("Hello, World!") 30 | .contextMenu { 31 | Button(action: {}) { 32 | Label("Follow Show", systemImage: "plus.circle") 33 | } 34 | Button(action: {}) { 35 | Label("Go to Show", systemImage: "airplayaudio") 36 | } 37 | Button(action: {}) { 38 | Label("Share Show", systemImage: "square.and.arrow.up") 39 | } 40 | Button(action: {}) { 41 | Label("Copy Link", systemImage: "link") 42 | } 43 | Button(action: {}) { 44 | Label("Report a Concern", systemImage: "exclamationmark.bubble") 45 | } 46 | } 47 | ``` 48 | 49 | ### 결과 50 | 51 | - Hello world! 텍스트 뷰를 꾸욱 누르면 해당 컨텍스트 메뉴가 팝업 된다. 52 | 53 |
54 | 55 |

스크린샷 2023-05-03 오후 5 07 09

56 | 57 |
58 | Context Menu의 버튼 항목은 title(Text)와 icon만 넣을 수 있게 되어있음
59 | 그래서 menu 자체를 customize 하는 건 불가함. -------------------------------------------------------------------------------- /SwiftUI/Summary/Custom Keyboard Submit Button/README.md: -------------------------------------------------------------------------------- 1 | # Custom Keyboard Submit Button 2 | 3 | ## 설명 4 | 5 | 1. .onSubmit 6 | - 클로저를 통해 return을 누르는 순간을 알 수 있다. 7 | 2. .submitLabel 8 | - reutrn key를 해당 속성 값으로 바꿀 수 있다. 9 | - 속성 종류 10 | 스크린샷 2023-05-11 오전 8 53 03 11 | 12 | ```swift 13 | import SwiftUI 14 | 15 | struct ContentView: View { 16 | 17 | 18 | 19 | @State var text1:String = "" 20 | @State var text2:String = "" 21 | 22 | 23 | var body: some View { 24 | VStack{ 25 | TextField("Name1",text:$text1) 26 | .submitLabel(.return) 27 | .padding(.leading) 28 | .frame(height: 55) 29 | .frame(maxWidth: .infinity) 30 | .background(Color.gray.opacity(0.5)) 31 | .cornerRadius(10) 32 | .onSubmit { 33 | print("Name1") 34 | } 35 | 36 | TextField("Name2",text:$text2) 37 | .submitLabel(.search) 38 | .padding(.leading) 39 | .frame(height: 55) 40 | .frame(maxWidth: .infinity) 41 | .background(Color.gray.opacity(0.5)) 42 | .cornerRadius(10) 43 | .onSubmit { 44 | print("Name2") 45 | } 46 | 47 | 48 | 49 | } 50 | .padding(40) 51 | 52 | } 53 | 54 | } 55 | ``` 56 | 57 | ### 포커싱 되는 텍스트 필드마다 리턴 버튼이 return <-> search로 변한다. 58 | 59 |

-------------------------------------------------------------------------------- /SwiftUI/Summary/Custom Shape Animation/README.md: -------------------------------------------------------------------------------- 1 | # Custom Shape Animation 2 | 3 | 4 | ## animatableData 적용 전 5 | 6 | 7 | ### 0. Content View 8 | ```swift 9 | struct ContentView: View { 10 | 11 | @State var animate: Bool = false 12 | 13 | 14 | var body: some View { 15 | 16 | ZStack{ 17 | //애니메이션 토글 시 cornerRadius 변경 18 | RectangleWithSingleCornerAnimation(cornerRadius: animate ? 60 :0) 19 | .frame(width:250,height: 250) 20 | } 21 | .onAppear{ 22 | // 평생 애니메이션 토글 23 | withAnimation(Animation.linear(duration: 2.0).repeatForever()) { 24 | animate.toggle() 25 | } 26 | 27 | } 28 | 29 | 30 | 31 | } 32 | 33 | } 34 | 35 | 36 | ``` 37 | 38 | ### 1. Custom Shape 39 | ```swift 40 | struct RectangleWithSingleCornerAnimation : Shape { 41 | 42 | var cornerRadius: CGFloat 43 | 44 | func path(in rect: CGRect) -> Path { 45 | 46 | Path{ path in 47 | 48 | path.move(to: .zero) 49 | path.addLine(to: CGPoint(x:rect.maxX,y:rect.minY)) 50 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) 51 | 52 | path.addArc(center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), radius: cornerRadius, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: false) 53 | 54 | path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY)) 55 | 56 | path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) 57 | 58 | } 59 | 60 | } 61 | 62 | } 63 | 64 | ``` 65 | 66 |
67 | 68 | ### 결과 69 | - 전혀 변화가 없음 70 | - 이유: cornerRadius변수는 뷰가 변화를 알아챌 수 있는 프로퍼티 래퍼가 없기 때문에 71 | 72 | 73 | ## 개선 74 | - Shape 프로토콜을 채택하면 animatableData 변수가 존재함 75 | - animatableData변수는 뷰가 알아차릴 수 있는 변수 76 | 77 | 스크린샷 2023-07-13 오후 12 30 23 78 | 79 | 80 |
81 | 82 | ### 1. Custom Shape 83 | ```swift 84 | 85 | struct RectangleWithSingleCornerAnimation : Shape { 86 | 87 | var cornerRadius: CGFloat 88 | 89 | var animatableData: CGFloat { 90 | 91 | get { 92 | cornerRadius 93 | } 94 | 95 | set { 96 | cornerRadius = newValue 97 | } 98 | 99 | } 100 | 101 | 102 | func path(in rect: CGRect) -> Path { 103 | 104 | Path{ path in 105 | 106 | path.move(to: .zero) 107 | path.addLine(to: CGPoint(x:rect.maxX,y:rect.minY)) 108 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) 109 | path.addArc(center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), radius: cornerRadius, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: false) 110 | 111 | path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY)) 112 | 113 | path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) 114 | 115 | } 116 | 117 | } 118 | 119 | } 120 | 121 | ``` 122 | 123 | 스크린샷 2023-07-13 오후 12 30 23 124 | 125 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Dark mode/README.md: -------------------------------------------------------------------------------- 1 | # **Dark Mode** 2 | ```swift 3 | var body: some View { 4 | NavigationView { 5 | ScrollView{ 6 | VStack(spacing: 20) { 7 | Text("This color is PRIMARY") 8 | .foregroundColor(.primary) //라이트모드에서는 black, 다크모드에서는 white로 변함 9 | Text("This color is SECONDARY") 10 | .foregroundColor(.secondary) //라이트모드와 다크모드 둘 다에서 연한 회색 11 | Text("This color is BLACK") 12 | .foregroundColor(.black) 13 | Text("This color is WHITE") 14 | .foregroundColor(.white) 15 | } 16 | } 17 | .navigationTitle(Text("Dark Mode Bootcamp")) 18 | } 19 | } 20 | ``` 21 | - primary와 secondary 컬러는 라이트 모드와 다크모드에서 자동으로 조정됨. 22 | ![화면 기록 2023-05-09 오전 5 46 33](https://user-images.githubusercontent.com/87987002/236931563-94304a79-7649-41d8-819c-7466e2a885f1.gif) 23 | 24 |
25 |
26 | 27 | ## Globally adapted Color 28 | 29 | asset → New Color Set → 이름 설정 → Appearances → Any, Dark 로 설정 → 색상 설정 30 | 31 | ```swift 32 | Text("This color is globally adapted!") 33 | .foregroundColor(Color("AdaptiveColor")) 34 | ``` 35 | 스크린샷 2023-05-09 오전 6 02 59 36 | 스크린샷 2023-05-09 오전 5 58 23 37 | 38 |
39 |
40 | 41 | ## 환경변수 추가 42 | - colorScheme은 swiftUI에서 기본으로 제공하는 환경변수임 - light/ Dark 43 | 44 | ```swift 45 | @Environment(\.colorScheme) var colorScheme 46 | 47 | var body: some View { 48 | NavigationView { 49 | ScrollView{ 50 | VStack(spacing: 20) { 51 | Text("This color is locally adaptive!") 52 | .foregroundColor(colorScheme == .light ? .green : .orange) 53 | //삼항연산자로 colorScheme이 dark인지 light인지 확인 54 | } 55 | } 56 | .navigationTitle(Text("Dark Mode Bootcamp")) 57 | } 58 | } 59 | ``` 60 | 스크린샷 2023-05-09 오전 6 17 10 61 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Extracting functions and subviews/README.md: -------------------------------------------------------------------------------- 1 | # Extracting functions and subviews 2 | 3 | 한 뷰에 기능과 뷰를 모두 때려 박으면 선언형 UI 특성상 무지하게 길어진다. 4 | 5 | 그렇기 때문에 적절하게 분리하여 보기도 좋고 관리하기도 편하다.\ 6 | 7 | 8 |
9 | 10 | ## Befre Extracting 11 | 12 | - 버튼의 기능과 서브 뷰를 나누기 전 입니다. 13 | 14 | ```swift 15 | 16 | struct SwiftUIView: View { 17 | 18 | @State var backgroundColor: Color = .pink 19 | 20 | var body: some View { 21 | ZStack{ 22 | backgroundColor 23 | .edgesIgnoringSafeArea(.all) 24 | 25 | VStack(spacing:20){ 26 | Text("Title") 27 | 28 | Button { 29 | backgroundColor = .yellow 30 | } label: { 31 | Text("Press Me") 32 | .font(.headline) 33 | } 34 | 35 | 36 | 37 | } 38 | } 39 | } 40 | } 41 | 42 | ``` 43 | 44 |
45 | 46 | ## After Extracting 47 | 48 | ```swift 49 | 50 | struct SwiftUIView: View { 51 | 52 | @State var backgroundColor: Color = .pink 53 | 54 | var body: some View { 55 | ZStack{ 56 | backgroundColor 57 | .edgesIgnoringSafeArea(.all) 58 | 59 | contentLayer // 본문이 굉장히 짤아졌음 60 | 61 | } 62 | } 63 | 64 | 65 | //서브뷰를 변수로 66 | var contentLayer: some View { 67 | VStack(spacing:20){ 68 | Text("Title") 69 | 70 | Button { 71 | buttonPress() 72 | } label: { 73 | Text("Press Me") 74 | .font(.headline) 75 | } 76 | 77 | } 78 | } 79 | 80 | //버튼 기능 81 | func buttonPress() { 82 | backgroundColor = .yellow 83 | } 84 | } 85 | 86 | ``` 87 | 88 | ## xCode 기능을 이용하여 SubView 만들기 89 | 90 | 1. 추출하고 싶은 가장 바깥뷰를 클릭한 후, command + 클릭을 한다. 91 | 92 | 93 | 스크린샷 2023-04-26 오후 11 02 03 94 | 95 | 2. 아래와 같이 옵션창이 뜰 때 Extract Subview를 눌러 추출한다. 96 | 97 | 스크린샷 2023-04-26 오후 11 02 14 98 | 99 | 3. command + shift + A 를 누른 후 rename 을 하게되면 한번에 이름이 바뀐다. -------------------------------------------------------------------------------- /SwiftUI/Summary/FocusState/README.md: -------------------------------------------------------------------------------- 1 | # @FocusState 2 | 3 | ## 사용 이유 4 | - 텍스트 필드와 같이 포커싱 여부에 따른 키보드 팝업 등 관리를 코드단에서 관리하기 위해서 사용합니다. 5 | 6 |
7 | 8 | ## 사용 방법 9 | 10 | 스크린샷 2023-05-10 오후 11 21 14 11 | 12 |
13 | 14 | ### 1. focused(_ condition) 15 | - Boolean 타입의 @FocusState를 바인딩하여 포커스를 감지할 수 있다. 16 | 17 | ### 2. focused(_ bindnig:equals:) 18 | - Enum을 이용한 @FocusState를 바인딩하여 , equals에 지정된 특정 case가 될 때 focus를 준다. 19 | **여기서 중요한 것은 해당 Enum은 **Hashable**을 채택해야한다.** 20 | 21 |

22 | 23 | ### 1. focused(_ condition) 24 | 25 | ```swift 26 | struct ContentView: View { 27 | 28 | @State var text:String = "" 29 | @FocusState private var nameIsFocus: Bool 30 | 31 | var body: some View { 32 | VStack{ 33 | TextField("Name",text:$text) 34 | .focused($nameIsFocus) // 자동으로 텍스트 필드 focus를 감지함 35 | .padding(.leading) 36 | .frame(height: 55) 37 | .frame(maxWidth: .infinity) 38 | .background(Color.gray.opacity(0.5)) 39 | .cornerRadius(10) 40 | 41 | 42 | Button { 43 | nameIsFocus = false // 버튼을 통해 포커싱 해제 44 | } label: { 45 | Text("Hide Keyboard") 46 | } 47 | 48 | } 49 | .padding(40) 50 | 51 | } 52 | 53 | } 54 | ``` 55 | 56 |

57 | 58 |

59 | 60 | 61 |

62 | 63 | ### 2. focused(_ condition) 64 | ```swift 65 | struct ContentView: View { 66 | 67 | enum FocusField:Hashable { 68 | case textField1 69 | case textField2 70 | } 71 | 72 | 73 | @State var text:String = "" 74 | @State var text2:String = "" 75 | @FocusState private var focusField: FocusField? 76 | 77 | var body: some View { 78 | VStack{ 79 | TextField("Name",text:$text) 80 | .focused($focusField, equals: .textField1) // 현재 focusField 값이 , .textField1 이면 포커싱 81 | .padding(.leading) 82 | .frame(height: 55) 83 | .frame(maxWidth: .infinity) 84 | .background(Color.gray.opacity(0.5)) 85 | .cornerRadius(10) 86 | 87 | TextField("Name2",text:$text2) 88 | .focused($focusField, equals: .textField2)// 현재 focusField 값이 , .textField2 이면 포커싱 89 | .padding(.leading) 90 | .frame(height: 55) 91 | .frame(maxWidth: .infinity) 92 | .background(Color.gray.opacity(0.5)) 93 | .cornerRadius(10) 94 | 95 | 96 | Button { 97 | focusField = .textField2 98 | } label: { 99 | Text("Change focus to textField2") 100 | } 101 | 102 | } 103 | .padding(40) 104 | 105 | } 106 | 107 | } 108 | ``` 109 | 110 |

111 | 112 |

113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /SwiftUI/Summary/ForEach/README.md: -------------------------------------------------------------------------------- 1 | # **#14 ForEach** 2 | 3 | 동일한 View에 다른 데이터를 반복해서 만들고 싶을 때 사용합니다. 4 | 5 | ```swift 6 | ForEach(data:,id:,content) 7 | ``` 8 | 기본 형태는 이렇습니다. 9 | 10 | ### 매개변수 11 | - data: 배열과 같은 반복가능한 객체입니다. 12 | - id: data의 요소 중 , 절대 겹치지않는 Hashable 값을 지정해 줍니다. 13 | - content: 반복될 뷰를 넣어줍니다. 14 | 15 | 사용 예) 16 | ```swift 17 | import SwiftUI 18 | 19 | 20 | 21 | struct Student : Hashable{ 22 | //절대 겹치지 않는 값을 갖고 있다는 Hashable 프로토콜을 채택해준다 23 | static func == (lhs: Student, rhs: Student) -> Bool { 24 | lhs.id == rhs.id // 아이디가 같으면 같은 객체로 인식 25 | } 26 | 27 | var id:Int = UUID().hashValue // 절대 겹치지 않을 값은 id로 28 | var name:String //학생 이름 (겹칠 수 있음) 29 | var grade:Int //학생 학년(겹칠 수 있음) 30 | 31 | 32 | } 33 | 34 | 35 | struct SwiftUIView: View { 36 | var data:[Student] = [Student(name: "해나", grade: 1),Student(name: "스낵", grade: 2),Student(name: "클레어", grade: 1),Student(name: "코비", grade: 1)] 37 | 38 | var body: some View { 39 | VStack{ 40 | //\.id는 -> Student 구조체 안의 id를 으미 41 | //info는 Student 객체를 의미함 42 | 43 | ForEach(data,id:\.id){ info in 44 | VStack{ 45 | Text("\(info.id)") //해쉬한 id 값과 46 | Text("\(info.name)") // 학생이름을 보여준다. 47 | } 48 | 49 | } 50 | } 51 | 52 | 53 | } 54 | } 55 | ``` 56 | 57 | ### 결과 58 | 스크린샷 2023-04-29 오전 11 32 15 59 | 60 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Frames & Alignments/README.md: -------------------------------------------------------------------------------- 1 | # Frames 2 | - 한 텍스트 뒤에 여러 frame을 넣을 수 있다. 3 | - background와 frame 여러 개를 한 번에 겹겹이 쌓을 수 있다. 4 | 5 |
6 | 7 | ## Text의 기본 frame 8 | 9 | - frame의 크기를 정해주지 않는다면 text의 기본 영역 자체가 frame이 된다. 10 | 11 | 12 | 13 | 14 | ```swift 15 | Text("Hello, World!") 16 | .background(Color.green) 17 | ``` 18 |
19 | 20 | ## 여러 겹으로 쌓을 수 있는 frame 21 | 22 | - frame은 밑에 쓴 코드일수록 화면상 제일 밑에 깔린다. (위의 요소들을 전부 담는 쟁반이라고 생각하면 편함) 23 | - **.infinity -** 화면 내에서의 최대 길이 24 | 25 | 26 | 27 | 28 | ```swift 29 | Text("Hello, World!") 30 | .background(Color.green) 31 | 32 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) 33 | .background(Color.red) 34 | // "Hello, World!"와 초록 배경은 빨간 frame 안의 content(요소)들이다 35 | ``` 36 |
37 | 38 | ## 여러 frame의 활용과 정렬 39 | 40 | ![image.jpg1](https://user-images.githubusercontent.com/126866283/233776382-7419ab70-e0d3-42cd-ab6a-793e5728d6df.png) |![image.jpg2](https://user-images.githubusercontent.com/126866283/233776415-bd708024-212a-4a4a-a15d-d45443b20886.png) |![image.jpg3](https://user-images.githubusercontent.com/126866283/233776622-16a8e08d-e68f-44e2-9d16-d7a2905182ee.png) |![image.jpg4](https://user-images.githubusercontent.com/126866283/233777053-9af3b98d-0444-4f2b-aaef-093370aa6865.png) |![image.jpg5](https://user-images.githubusercontent.com/126866283/233777096-bfc92ff9-3205-4ec8-9f6d-38abf289dcc5.png) 41 | --- | --- | --- | --- | --- | 42 | 43 | 44 | ```swift 45 | Text("Hello, World!") 46 | .background(Color.green) 47 | 48 | // (1) 텍스트상자를 상단 정렬로 포함하는, 높이가 100인 orange frame 49 | .frame(height: 100, alignment: .top) 50 | .background(Color.orange) 51 | 52 | // (2) 위의 모든 요소를 (가운데 정렬로) 포함하는, 너비가 150인 purple frame 53 | .frame(width: 150) 54 | .background(Color.purple) 55 | 56 | // (3) 위의 모든 요소들을 왼쪽 정렬로 포함하는, 너비가 화면 너비만한 pink frame 57 | .frame(maxWidth: .infinity, alignment: .leading) 58 | .background(Color.pink) 59 | 60 | // (4) 위의 모든 요소들을 (가운데 정렬로) 포함하는, 높이가 400인 green frame 61 | .frame(height: 400) 62 | .background(Color.green) 63 | 64 | // (5) 위의 모든 요소들을 상단 정렬로 포함하는, 높이가 화면 높이만한 yellow frame 65 | .frame(maxHeight: .infinity, alignment: .top) 66 | .background(Color.yellow) 67 | ``` 68 | 69 | 70 | ### Frame 크기 71 | - frame의 크기를 유동적으로 지정할 수 있다. 72 | ```swift 73 | Text("Hello, World!") 74 | .background(Color.red) 75 | .frame( 76 | minWidth: 10, // 최소 너비 지정 77 | idealWidth: 300, // 최적 너비 지정 78 | maxWidth: .infinity, // 최대 너비 지정 79 | minHeight: 10, // 최소 높이 지정 80 | idealHeight: 300, // 최적 높이 지정 81 | maxHeight: .infinity, // 최대 높이 지정 82 | alignment: .center 83 | ) 84 | ``` 85 | - fixedSize modifier를 통해 maxWidth와 maxHeight에 상관없이 영역을 고정할 수 있다. 86 | - idealWidth와 idealHeight이 지정되어 있을 경우 그 크기로 고정되며, 지정되어 있지 않을 경우 내부 콘텐츠의 크기로 고정된다. 87 | ```swift 88 | .fixedSize(horizontal: true, vertical: true) 89 | ``` 90 | -------------------------------------------------------------------------------- /SwiftUI/Summary/GeometryReader/README.md: -------------------------------------------------------------------------------- 1 | # GeometryReader 2 | - 상위 뷰의 크기 및 좌표 시스템을 활용하여 하위 뷰의 레이아웃과 위치를 제어할 수 있는 컨테이너 뷰 3 | 4 | ### UIScreen 5 | - `UIScreen`을 활용하여 화면의 너비에 따라 레이아웃이나 위치를 지정할 수 있다. 6 | - 모든 상황에서 화면의 너비를 기준으로 하기 때문에 `Landscape`모드 등 유동적으로 적용되지 않는다. 7 | ```swift 8 | HStack(spacing: 0) { 9 | Rectangle() 10 | .fill(.red) 11 | // 화면 너비의 3/2 크기 12 | .frame(width: UIScreen.main.bounds.width * 0.666) 13 | Rectangle() 14 | .fill(.blue) 15 | } 16 | .ignoresSafeArea() 17 | ``` 18 | ![](https://velog.velcdn.com/images/snack/post/c42a04bc-6ee1-40ac-8a22-5aeb3a81c339/image.png) 19 | 20 | ### GeometryReader 21 | - 상위 뷰의 주어진 공간 내에서 하위 뷰의 위치와 크기를 유동적으로 결정할 수 있다. 22 | - 클로저 내에서 `GeometryProxy`를 통해 상위 뷰 정보에 접근할 수 있다. 23 | - `GeometryReader`는 리소스를 많이 차지하기 때문에 남용하지 말고 꼭 필요한 상황에서만 사용해야한다. 24 | ```swift 25 | GeometryReader { geometry in 26 | HStack(spacing: 0) { 27 | Rectangle() 28 | .fill(.red) 29 | // 상위 뷰 너비의 3/2 크기 30 | .frame(width: geometry.size.width * 0.666) 31 | Rectangle() 32 | .fill(.blue) 33 | } 34 | .ignoresSafeArea() 35 | } 36 | ``` 37 | ![](https://velog.velcdn.com/images/snack/post/84b9d71d-43b1-4f34-a117-bd0d8454a86b/image.png) 38 | 39 | ### GeometryReader Frame 40 | - `frmae(in:)`을 통해 특정 좌표계를 기준으로 한 프레임 정보를 가저올 수 있다. 41 | ```swift 42 | enum CoordinateSpace { 43 | case global // 화면 전체 영역 기준 44 | case local // GeometryReader 기준 45 | case named(AnyHashable) // 명시적으로 이름을 할당한 공간 기준 46 | } 47 | ``` 48 | ![](https://velog.velcdn.com/images/snack/post/132a23f6-65e3-4e1b-ae06-ab1f1986396d/image.png) 49 | 50 | - `GeometryReader`를 활용하여 다양한 특수효과를 표현할 수 있다. 51 | ```swift 52 | struct GeometryReaderStudy: View { 53 | var body: some View { 54 | ScrollView(.horizontal, showsIndicators: false) { 55 | HStack { 56 | ForEach(0..<20) { index in 57 | GeometryReader { geometry in 58 | RoundedRectangle(cornerRadius: 20) 59 | .rotation3DEffect( 60 | // 카드의 현재 위치에 따라 각도를 다르게 조절 61 | Angle(degrees: getPercentage(geo: geometry)) * 20, 62 | axis: (x: 0.0, y: 1.0, z: 0.0)) 63 | } 64 | .frame(width: 300, height: 250) 65 | .padding() 66 | } 67 | } 68 | } 69 | } 70 | func getPercentage(geo: GeometryProxy) -> Double { 71 | // 화면의 중앙 위치 72 | let maxDistance = UIScreen.main.bounds.width / 2 73 | // 화면 전체 영역 기준 카드의 현재 중앙 좌표 74 | let currentX = geo.frame(in: .global).midX 75 | // 두 위치에 대한 비율 계산 76 | return Double(1 - (currentX / maxDistance)) 77 | } 78 | } 79 | ``` 80 | ![](https://velog.velcdn.com/images/snack/post/51747588-10f1-49fd-85ea-718b7f438933/image.gif) 81 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Gradients/README.md: -------------------------------------------------------------------------------- 1 | # #5 Gradients 2 | ## LinearGradient 3 | 4 | ```swift 5 | RoundedRectangle(cornerRadius: 25) 6 | .fill( 7 | LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), 8 | startPoint: .leading, 9 | endPoint: .trailing) 10 | ) 11 | 12 | // startPoint: .top, endPoint: .bottom 13 | // startPoint: .topLeading, endPoint: .bottomtrailing 14 | // startPoint: .top, endPoint: .bottom 15 | // startPoint: .topLeading, enPoint: .bottom - 이게 자주 사용됨 16 | ``` 17 | 그라디언트 매개변수 내의 색상은 배열임. 18 | 19 |
20 | 21 | 22 | ```swift 23 | RoundedRectangle(cornerRadius: 25) 24 | .fill( 25 | LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue, Color.orange, Color.purple]), 26 | startPoint: .leading, 27 | endPoint: .trailing) 28 | ) 29 | ``` 30 | 색상은 이렇게 계속 추가할 수 있음. 31 | 32 |
33 |
34 | 35 | ## RadialGradient 36 | 37 | ```swift 38 | RoundedRectangle(cornerRadius: 25) 39 | .fill( 40 | RadialGradient( 41 | gradient: Gradient(colors: [Color.red, Color.blue]), 42 | center: .topLeading, 43 | startRadius: 5, 44 | endRadius: 400) 45 | ) 46 | ``` 47 | 스크린샷 2023-04-20 오후 6 18 23 48 | 49 |
50 |
51 | 52 | ## AngularGradient 53 | ```swift 54 | RoundedRectangle(cornerRadius: 25) 55 | .fill( 56 | AngularGradient(gradient: Gradient(colors: [Color.red, Color.blue]), 57 | center: .center, 58 | angle: .degrees(180 + 45)) //오른쪽은 0도, 왼쪽은 180도 59 | ) 60 | ``` 61 | 62 | 스크린샷 2023-04-20 오후 6 28 47 63 | 64 | 65 | 중심점의 위치를 좌상단으로 옮기면 66 | 67 | ```swift 68 | RoundedRectangle(cornerRadius: 25) 69 | .fill( 70 | AngularGradient(gradient: Gradient(colors: [Color.red, Color.blue]), 71 | center: .topLeading, 72 | angle: .degrees(180 + 45)) 73 | ) 74 | ``` 75 | 스크린샷 2023-04-20 오후 6 30 00 76 | 77 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Group/README.md: -------------------------------------------------------------------------------- 1 | # Group 2 | 3 | ## 사용 이유 4 | - 별도의 뷰를 생성하지 않고 Modifier를 적용하고 싶을 때 사용합니다. 5 | 6 | 7 |
8 | 9 | #### 1. 가장 바깥 뷰에 Modifier를 적용하면 자식 뷰도 해당 속성을 따라간다. 10 | #### 2. 별도의 Modifier를 적용하기 위해서는 다시한번 뷰로 감싸야 한다. 11 | #### 3. Group을 사용하면 불필요한 뷰를 만들어 랜더링 할 필요가 없다. 12 | 13 | ```swift 14 | VStack{ 15 | 16 | Text("Hello") 17 | Text("Hello") 18 | Text("Hello") 19 | 20 | } 21 | .foregroundColor(.red) 22 | .font(.largeTitle) 23 | ``` 24 | 25 | ```swift 26 | VStack{ 27 | 28 | Text("Hello") 29 | VStack{ 30 | Text("Hello") 31 | Text("Hello") 32 | } 33 | .foregroundColor(.blue) 34 | .font(.caption) 35 | } 36 | .foregroundColor(.red) 37 | .font(.largeTitle) 38 | ``` 39 | 40 | ```swift 41 | VStack{ 42 | 43 | Text("Hello") 44 | Group{ 45 | Text("Hello") 46 | Text("Hello") 47 | } 48 | .foregroundColor(.blue) 49 | .font(.caption) 50 | } 51 | .foregroundColor(.red) 52 | .font(.largeTitle) 53 | ``` 54 | 55 |

56 | 57 | 58 | 59 | 60 | 61 | 62 |

63 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Haptic/README.md: -------------------------------------------------------------------------------- 1 | # Haptic 2 | 3 | 4 | ## 목차 5 | 6 | ### 1. 해당 영상에서는 2가지 방식으로 진동을 만들어 낸다. 7 | 8 | - UINotificationFeedbackGenerator 9 | - UIImpactFeedbackGenerator 10 | 11 |
12 | 13 | ## HapticManager 14 | ```swift 15 | class HapticManager { 16 | 17 | static let instance = HapticManager() // 싱글톤 18 | 19 | 20 | func notification(type:UINotificationFeedbackGenerator.FeedbackType){ 21 | 22 | 23 | let generator = UINotificationFeedbackGenerator() 24 | generator.notificationOccurred(type) //진동 25 | } 26 | 27 | func impact(style:UIImpactFeedbackGenerator.FeedbackStyle){ 28 | let generator = UIImpactFeedbackGenerator(style: style) 29 | generator.impactOccurred() // 진동 30 | } 31 | 32 | } 33 | ``` 34 | 35 | ## View 36 | ```swift 37 | VStack(spacing: 20){ 38 | 39 | Button("success") {HapticManager.instance.notification(type:.success)} 40 | Button("warning") {HapticManager.instance.notification(type:.warning)} 41 | Button("error") {HapticManager.instance.notification(type:.error)} 42 | 43 | 44 | 45 | Divider() 46 | 47 | Button("soft") {HapticManager.instance.impact(style: .soft)} 48 | Button("light") {HapticManager.instance.impact(style: .light)} 49 | Button("medium") {HapticManager.instance.impact(style: .medium)} 50 | Button("rigid") {HapticManager.instance.impact(style: .rigid)} 51 | Button("heavy") {HapticManager.instance.impact(style: .heavy)} 52 | 53 | 54 | } 55 | ``` 56 | 57 | 58 |
59 | 60 | ### 1. UINotificationFeedbackGenerator.FeedbackType 61 | 62 | #### 단순 진동이 아닌 약간 뒤끝이 있음 63 | - .success : 완료를 알리는 알림 진동 , 약간 띠딩 느낌 64 | - .warning : 경고 알림 진동, 띠..딩 느낌 65 | - .error : 에러를 알림 , 가장 독특한 진동, 또로로롱 느낌 뭔가 굴러가는 느낌 66 | 67 | [진 관련 HIG 자료](https://developer.apple.com/design/human-interface-guidelines/playing-haptics) 68 | 69 |
70 | 71 | ### 2. UIImpactFeedbackGenerator.FeedbackStyle 72 | 73 | #### 단순 진동 느낌 , 강도만 달라짐 74 | - .soft 75 | - .light 76 | - .medium 77 | - .rigid 78 | - .heavy 79 | -------------------------------------------------------------------------------- /SwiftUI/Summary/High Order Function/README.md: -------------------------------------------------------------------------------- 1 | # High Order Function 2 | 3 | 고차함수는 다른 함수를 인자로 받거나, 함수의 결과로 함수를 반환하는 함수를 의미합니다. 4 | 5 | 스위프트에서는 map, filter, reduce 가 콜렉션 타입 내에 정의되어 있습니다. 6 | 7 |
8 | 9 | ### Test Model 10 | 11 | ```swift 12 | struct UserModel: Identifiable { 13 | let id = UUID().uuidString 14 | let name: String 15 | let points: Int 16 | let isVerified: Bool 17 | } 18 | ``` 19 | 20 | ### 1. sorted 21 | ```swift 22 | // 내림차순 정렬 23 | 24 | filteredArray = dataArray.sorted(by: { u1, u2 in 25 | return u1.points > u2.points 26 | }) 27 | 28 | filteredArray = dataArray.sorted(by: {$0.points > $1.points}) 29 | ``` 30 | 31 | ### 2. filter 32 | ```swift 33 | //인증된 인원 34 | 35 | filteredArray = dataArray.filter({ user in 36 | user.isVerified 37 | }) 38 | 39 | filteredArray = dataArray.filter({$0.isVerified}) 40 | 41 | ``` 42 | 43 | ### 3. map 44 | ```swift 45 | //유저의 이름만 필요 46 | 47 | mappedArray = dataArray.map({ user -> String in 48 | return user.name 49 | }) 50 | ``` 51 | 52 | ### 4. compactMap 53 | ``` swift 54 | // 유저 이름 중 nil이 있는 것은 알아서 필터링 55 | 56 | mappedArray = dataArray.compactMap({$0.name}) 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Icons/README.md: -------------------------------------------------------------------------------- 1 | # Icons 2 | - Image 뷰를 이용해 원하는 아이콘 생성 3 | 4 | ### System Icons 5 | - Apple의 SF Symbols를 활용해 기본 시스템 아이콘을 사용할 수 있다. 6 | - SF Symbols의 아이콘 이름을 systemName으로 입력하여 사용할 수 있다. 7 | - SF Symbols (https://developer.apple.com/sf-symbols/) 8 | ```swift 9 | Image(systemName: "heart.fill") // SF Symbols의 시스템 아이콘 사용 10 | ``` 11 | ![](https://velog.velcdn.com/images/snack/post/09e2d92d-0a7a-4c64-b147-b29d92277235/image.png) 12 | 13 | ### Icons 속성 14 | - modifiers를 통해 Icon에 다양한 속성을 추가할 수 있다. 15 | ```swift 16 | Image(systemName: "heart.fill") 17 | .font(.title) // 폰트 시스템에 따라 아이콘 크기 변경 18 | .font(.system(size: 200)) // 폰트 사이즈 200으로 아이콘 사이즈 변경 19 | .foregroundColor(.green) // 초록색으로 색상 변경 20 | .resizable() // 크기 변경 가능 21 | .aspectRatio(contentMode: .fit) // 프레임에 맞게 비율 조정 22 | .scaledToFill() // 프레임에 맞게 비율 조정 23 | .frame(width: 200, height: 200) // 가로 200, 세로 200으로 크기 변경 24 | .clipped() // 프레임 클립핑 마스크 적용 25 | ``` 26 | 27 | ### Multi-Color Icons 28 | - SF Symbols의 Multi-Color Icons를 사용할 수 있다. 29 | - 초록색과 빨간색 같은 시스템 강조색은 변경할 수 없다. 30 | ```swift 31 | Image(systemName: "person.fill.badge.plus") 32 | .font(.largeTitle) 33 | .symbolRenderingMode(.multicolor) // multicolor로 렌더링 모드 변경 34 | ``` 35 | -------------------------------------------------------------------------------- /SwiftUI/Summary/If Let, Guard Let/README.md: -------------------------------------------------------------------------------- 1 | # If Let, Guard Let 2 | - 옵셔널 값을 안전하게 추출하는 옵셔널 바인딩 방법 3 | 4 | ### if let 5 | - 조건문 안의 옵셔널 값이 `nil`인지 체크하고, 각 경우에 대한 처리를 안전하게 수행한다. 6 | - 보통 각 조건에 따른 처리를 위해 사용되지만, `else`문을 생략하고 사용할 수 있다. 7 | - 옵셔널 바인딩 된 상수 `if`문 안에서만 사용할 수 있다. 8 | ```swift 9 | struct UnwrapStudy: View { 10 | 11 | @State var currentUserID: String? = "Snack" 12 | @State var displayText: String? = nil 13 | @State var isLoading: Bool = false 14 | 15 | var body: some View { 16 | VStack { 17 | // displayText가 nil이 아닌 경우 18 | if let text = displayText { 19 | Text(text) 20 | .font(.title) 21 | } 22 | if isLoading { 23 | ProgressView() 24 | } 25 | } 26 | .onAppear { 27 | loadData() 28 | } 29 | } 30 | 31 | func loadData() { 32 | // currentUserID가 nil이 아닌 경우 33 | if let userID = currentUserID { 34 | isLoading = true 35 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 36 | displayText = "The username is \(userID)." 37 | isLoading = false 38 | } 39 | // currentUserID가 nil인 경우 40 | } else { 41 | displayText = "There is no user data." 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ### guard let 48 | - 블록 내부에서만 사용되며, 옵셔널 값을 체크하여 `nil`인 경우에 대한 처리를 `guard` 절 안에서 바로 수행한다. 49 | - `return`, `break`, `continue`, `throw` 등의 제어문 전환 명령어를 필수로 입력한다. 50 | - 옵셔널 바인딩 된 상수를 `else`문을 제외한 블록 내에서 사용할 수 있다. 51 | - 예외사항 처리를 먼저 수행하여 코드의 안정성과 가독성이 향상된다. 52 | ```swift 53 | struct UnwrapStudy: View { 54 | 55 | @State var currentUserID: String? = "Snack" 56 | @State var displayText: String? = nil 57 | @State var isLoading: Bool = false 58 | 59 | var body: some View { 60 | VStack { 61 | // displayText가 nil이 아닌 경우 62 | if let text = displayText { 63 | Text(text) 64 | .font(.title) 65 | } 66 | if isLoading { 67 | ProgressView() 68 | } 69 | } 70 | .onAppear { 71 | loadData() 72 | } 73 | } 74 | 75 | func loadData() { 76 | guard let userID = currentUserID else { 77 | // currentUserID가 nil인 경우 78 | displayText = "There is no user data." 79 | return // 함수 종료 80 | } 81 | // currentUserID가 nil이 아닌 경우 82 | isLoading = true 83 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 84 | displayText = "The username is \(userID)." 85 | isLoading = false 86 | } 87 | } 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Image/README.md: -------------------------------------------------------------------------------- 1 | # Image 2 | - 이미지를 추가할 수 있는 뷰 3 | 4 | ### Image 사이즈 5 | - 이미지를 frame을 기준으로 사이즈를 조정할 수 있다. 6 | ```swift 7 | Image("testImage") 8 | .resizable() // 사이즈 조정 가능 9 | .frame(width: 300, height: 250) // 이미지 뷰 frame 사이즈 조정 10 | ``` 11 | 12 | ### ContentMode Fill 13 | - 원본 이미지 비율을 유지하면서 프레임에 사이즈에 맞게 이미지 사이즈를 조정할 수 있다. 14 | - 이미지를 조정할 때 프레임 사이즈를 벗어날 수 잇다. 15 | ```swift 16 | Image("testImage") 17 | .resizable() 18 | .aspectRatio(contentMode: .fill) // 프레임 사이즈에 맞게 이미지 사이즈 조정 19 | .scaledToFill() // 위와 동일 효과 20 | .frame(width: 300, height: 250) 21 | ``` 22 | ![](https://velog.velcdn.com/images/snack/post/ae39a2c4-a34f-4f4a-847b-0468ef92448d/image.png) 23 | - `aspectRatio` 파라미터를 추가해 임의의 이미지 비율을 지정할 수 있다. 24 | - 이미지 비율은 `가로/세로`의 값을 입력한다. 25 | ```swift 26 | Image("testImage") 27 | .resizable() 28 | .aspectRatio(1.5, contentMode: .fill) // 임의의 이미지 비율 지정 29 | .frame(width: 300, height: 250) 30 | ``` 31 | ![](https://velog.velcdn.com/images/snack/post/9cf0133f-2d9d-4afd-bb14-2ded3038b0a0/image.png) 32 | 33 | ### ContentMode Fit 34 | - 원본 이미지 비율을 유지하면서 프레임에 사이즈에 맞게 이미지 사이즈를 조정할 수 있다. 35 | - 이미지를 조정할 때 프레임 사이즈를 벗어나지 않는다. 36 | ```swift 37 | Image("testImage") 38 | .resizable() 39 | .aspectRatio(contentMode: .fit) // 프레임 사이즈에 맞게 이미지 사이즈 조정 40 | .scaledToFit() // 위와 동일 효과 41 | .frame(width: 300, height: 250) 42 | ``` 43 | ![](https://velog.velcdn.com/images/snack/post/47cb6d96-057e-4097-82a1-60f4321c8d17/image.png) 44 | - `aspectRatio` 파라미터를 추가해 임의의 이미지 비율을 지정할 수 있다. 45 | - 이미지 비율은 `가로/세로`의 값을 입력한다. 46 | ```swift 47 | Image("testImage") 48 | .resizable() 49 | .aspectRatio(1.5, contentMode: .fit) // 임의의 이미지 비율 지정 50 | .frame(width: 300, height: 250) 51 | ``` 52 | ![](https://velog.velcdn.com/images/snack/post/36b34f67-f911-467d-a061-d95360a0421a/image.png) 53 | 54 | ### Image 클리핑 마스크 55 | - 이미지를 원하는 모양으로 클리핑 마스크를 씌울 수 있다. 56 | ```swift 57 | Image("testImage") 58 | .resizable() 59 | .aspectRatio(contentMode: .fill) 60 | .scaledToFill() 61 | .frame(width: 300, height: 250) 62 | .clipped() // 프레임을 초과한 부분 자르기 63 | .cornerRadius(24) // 프레임의 둥근 모서리 모양으로 초과한 부분 자르기 64 | .clipShape(Circle()) // Shape 모양으로 자르기 65 | ``` 66 | 67 | ### RenderingMode 68 | - 이미지의 불투명 영역의 색상을 임의로 변경할 수 있다. 69 | ```swift 70 | Image("apple") 71 | .renderingMode(.template) // 이미지의 렌더링 모드를 template 모드로 변경 72 | .resizable() 73 | .aspectRatio(contentMode: .fit) 74 | .frame(width: 300, height: 200) 75 | .foregroundColor(.green) // template 모드 이미지의 불투명 영역 색상을 변경 76 | ``` 77 | - 이미지 에셋의 Attributes inspector에서 이미지의 렌더링 모드를 사전에 설정할 수 있다. 78 | - 렌더링 모드를 사전에 설정하면 `.renderingMode(.template)`을 입력하지 않아도 template 모드가 적용된다. 79 | 80 | ![](https://velog.velcdn.com/images/snack/post/75685878-60c1-4d07-9904-af191300c8cc/image.png) 81 | -------------------------------------------------------------------------------- /SwiftUI/Summary/LazyStack/README.md: -------------------------------------------------------------------------------- 1 | # LazyStack 2 | - 스택 내의 아이템들이 화면에 렌더링 될 때 생성되는 뷰 3 | 4 | ### LazyV(H)Stack 5 | - 일반 Stack의 경우 화면에 나타나기 전 모든 데이터를 로드하지만, LazyStack의 경우 화면에 나타난 후에 데이터를 로드한다. 6 | ```swift 7 | ScrollView { 8 | LazyVStack(alignment: .leading) { 9 | ForEach(0..<100) { index in 10 | Text("LazyStack \(index)") 11 | } 12 | } 13 | .background(.blue) 14 | } 15 | ``` 16 | - LazyStack은 유연하게 너비를 갖기 때문에 일반 스택과 다르게 자동으로 여유 공간을 차지한다. 17 | 18 | ![](https://velog.velcdn.com/images/snack/post/944703c3-964d-4b7f-aefd-a77508089ad0/image.png) 19 | -------------------------------------------------------------------------------- /SwiftUI/Summary/List Style/README.md: -------------------------------------------------------------------------------- 1 | # ListStyle 2 | 3 | ## iOS 4 | 5 | ### 종류 6 | 7 | - PlainListStyle 8 | - DefaultListStyle 9 | - InsetListStyle 10 | - GroupedListStyle 11 | - InsetGroupedListStyle 12 | - SidebarListStyle 13 | 14 | 15 | ## WatchOS 16 | - PlainListStyle 17 | - DefaultListStyle 18 | - CarouseListStyle 19 | - EllipticicalListStyle 20 | 21 |
22 | 23 | 여기서 알 수 있는 점은..각 OS별로 사용할 수 있는 ListStyle이 있는거구나..! 입니다 24 | 25 | 스크린샷 2023-05-01 오후 1 03 50 26 | 27 |
28 | 29 | 스크린샷 2023-05-01 오후 1 03 59 30 | 31 |

32 | 33 | ### Default와 Plain의 차이 34 | 35 | - Default는 플랫폼의 기본적인 동작/appearance인 list style. 36 | 37 | - Plain은 그냥 plain list의 동작/appearance인 list style. 38 | 39 | 아 뭔가 Default로 지정하면 각 플랫폼에 맞게 뭔가 좀 변하는데... 40 | 41 | Plain으로 가면 그냥 그런게 없나보네요. 42 | 43 | **그리고 insetListStyle도 똑같아 보이지만 Default/Plain에 비해 Leading space가 큽니다.** 44 | 45 |

46 | 47 | ### DefaultListStyle 48 | 49 | 50 | 51 | ### CarouselListStyle 52 | 53 | 54 | 위로 사라지는 느낌을 주의깊게 봐야합니다. 55 | 56 | Carousel라는 뜻은 "회전목마" 느낌 -------------------------------------------------------------------------------- /SwiftUI/Summary/List Swipe Actions/README.md: -------------------------------------------------------------------------------- 1 | # **#58 List Swipe Actions** 2 | 3 | https://github.com/yongbeomkwak/SwiftUI-Study/tree/main/SwiftUI/Summary/List 4 |
List 강좌 참고. 5 | - delete에 사용되는 swipe외에도 list에 적용 가능한 swipeAction들이 있다. 6 | 7 | ## 기존의 delete 할 수 있는 리스트 8 | 9 | ```swift 10 | struct ListSwipeActionsView: View { 11 | 12 | @State var fruits: [String] = [ 13 | "apple", "orange", "banana","peach" 14 | ] 15 | 16 | var body: some View { 17 | List { 18 | ForEach(fruits, id: \.self) { 19 | Text($0.capitalized) 20 | } 21 | .onDelete(perform: delete) 22 | } 23 | } 24 | 25 | func delete(indexSet: IndexSet) { 26 | 27 | } 28 | } 29 | ``` 30 | 스크린샷 2023-05-24 오후 11 36 12 31 | 32 |
33 |
34 |
35 | 36 | 37 | ## 커스텀 스와이프 코드 38 | 39 | ```swift 40 | struct ListSwipeActionsView: View { 41 | 42 | @State var fruits: [String] = [ 43 | "apple", "orange", "banana","peach" 44 | ] 45 | 46 | var body: some View { 47 | List { 48 | ForEach(fruits, id: \.self) { 49 | Text($0.capitalized) 50 | .swipeActions(edge: .trailing, allowsFullSwipe: false) { //오른쪽에서 스와이프 51 | Button("Archive") { } 52 | .tint(.green) 53 | Button("Save") { } 54 | .tint(.blue) 55 | Button("Junk") { } 56 | .tint(.black) 57 | } 58 | .swipeActions(edge: .leading, allowsFullSwipe: true) { //왼쪽에서 스와이프 59 | Button("Share") { } 60 | .tint(.yellow) 61 | } 62 | } 63 | } 64 | } 65 | func delete(indexSet: IndexSet) { 66 | 67 | } 68 | } 69 | ``` 70 | 버튼이 여러개일때는 allowFullSwipe: false로 하기를 추천. 71 | 72 | ![화면 기록 2023-05-24 오후 9 10 08](https://github.com/yongbeomkwak/SwiftUI-Study/assets/87987002/17566fab-3523-42e0-a3df-ba218d5cdc19) 73 | 74 | 75 | -------------------------------------------------------------------------------- /SwiftUI/Summary/MatchedGeometryEffect/README.md: -------------------------------------------------------------------------------- 1 | # MatchedGeometryEffect 2 | - 서로 다른 두 뷰 사이에 애니메이션 효과를 줄 수 있는 기능 3 | 4 | ### MatchedGeometryEffect 5 | - 일반적인 애니메이션 효과는 단일 뷰의 변화를 감지하여 그 변화에 애니메이션을 적용한다. 6 | - `matchedGeometryEffect`를 이용하여 두 뷰 사이의 변화에 애니메이션 효과를 적용할 수 있다. 7 | - `matchedGeometryEffect`를 적용하기 위해선 같은 뷰로 인식할 고유한 `id`값과 이 정보를 기억할 `@Namespace`라는 프로퍼티 래퍼가 필요하다. 8 | ```swift 9 | struct MatchedGeometryEffectStudy: View { 10 | 11 | @State private var isClicked: Bool = false 12 | @Namespace private var namespace 13 | 14 | var body: some View { 15 | VStack { 16 | if !isClicked { 17 | RoundedRectangle(cornerRadius: 12) 18 | // 애니메이션을 적용할 뷰 지정 19 | .matchedGeometryEffect(id: "rectangle", in: namespace) 20 | .frame(width: 100, height: 100) 21 | } 22 | Spacer() 23 | if isClicked { 24 | RoundedRectangle(cornerRadius: 12) 25 | // 애니메이션을 적용할 뷰 지정 26 | .matchedGeometryEffect(id: "rectangle", in: namespace) 27 | .frame(width: 200, height: 200) 28 | } 29 | } 30 | .frame(maxWidth: .infinity, maxHeight: .infinity) 31 | .background(.red) 32 | .onTapGesture { 33 | withAnimation(.easeInOut) { 34 | isClicked.toggle() 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | ![](https://velog.velcdn.com/images/snack/post/b6c2c9ae-509c-47e8-bc11-177aece3fb2d/image.gif) 41 | - 이를 활용하여 SegmentedControl과 같은 효과를 구현할 수 있다. 42 | ```swift 43 | struct MatchedGeometryEffectStudy2: View { 44 | 45 | let categories: [String] = ["Home", "Popular", "Saved"] 46 | @State private var selected: String = "Home" 47 | @Namespace private var namespace2 48 | 49 | var body: some View { 50 | HStack { 51 | ForEach(categories, id: \.self) { category in 52 | ZStack { 53 | if selected == category { 54 | RoundedRectangle(cornerRadius: 12) 55 | .fill(Color.blue).opacity(0.3) 56 | // 애니메이션을 적용할 뷰 지정 57 | .matchedGeometryEffect(id: "category", in: namespace2) 58 | } 59 | Text(category) 60 | } 61 | .frame(maxWidth: .infinity) 62 | .frame(height: 56) 63 | .onTapGesture { 64 | withAnimation(.spring()) { 65 | selected = category 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | ``` 73 | ![](https://velog.velcdn.com/images/snack/post/e7eda39d-92d1-4269-af2f-72f78c8c381b/image.gif) 74 | -------------------------------------------------------------------------------- /SwiftUI/Summary/OnAppear, OnDisappear/README.md: -------------------------------------------------------------------------------- 1 | # OnAppear, OnDisappear 2 | - 뷰가 나타나거나 사라질 때 입력된 코드를 실행 3 | 4 | ### onAppear 5 | - 뷰가 화면 상에 나타날 때 입력된 코드를 실행한다. 6 | ```swift 7 | struct OnAppearStudy: View { 8 | 9 | @State var myText: String = "Star Text" 10 | 11 | var body: some View { 12 | Text(myText) 13 | .font(.largeTitle) 14 | // 뷰가 나타난 직후 해당 코드 실행 15 | .onAppear(perform: { 16 | // 3초 후 해당 코드 실행 17 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 18 | myText = "Next Text" 19 | } 20 | }) 21 | } 22 | } 23 | ``` 24 | - LazyVStack으로 화면에 나타난 후에 데이터를 로드하면, 화면에 나타날 때 `onAppear`내의 코드가 실행된다. 25 | ```swift 26 | struct OnAppearStudy: View { 27 | 28 | @State var count: Int = 0 29 | 30 | var body: some View { 31 | NavigationView { 32 | ScrollView { 33 | // 화면에 나타날 때 데이터 로드 34 | LazyVStack { 35 | ForEach(0..<20) { _ in 36 | RoundedRectangle(cornerRadius: 20) 37 | .frame(height: 200) 38 | .padding() 39 | // 화면에 나타날 때 해당 코드 실행 40 | .onAppear { 41 | count += 1 42 | } 43 | } 44 | } 45 | } 46 | .navigationTitle("On Appear \(count)") 47 | } 48 | } 49 | } 50 | ``` 51 | ![](https://velog.velcdn.com/images/snack/post/94fadc9f-ede4-417c-a211-f3e2a4244ec0/image.png) 52 | 53 | ### onDisappear 54 | - 뷰가 화면 상에서 사라질 때 입력된 코드를 실행한다. 55 | ```swift 56 | struct OnAppearStudy: View { 57 | 58 | @State var myText: String = "Star Text" 59 | 60 | var body: some View { 61 | Text(myText) 62 | .font(.largeTitle) 63 | // 뷰가 사라진 직후 해당 코드 실행 64 | .onDisappear(perform: { 65 | myText = "End Text" 66 | }) 67 | } 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /SwiftUI/Summary/OnTapGesture/README.md: -------------------------------------------------------------------------------- 1 | # OnTapGesture 2 | - 뷰에 탭 제스처를 설정하여 사용자가 화면을 탭 했을 때 설정한 코드 실행 3 | 4 | ### onTapGesture 5 | - 사용자가 특정 뷰를 탭하면 실행할 코드를 지정하여, 제스처가 인식되면 지정된 코드 블럭을 실행한다. 6 | - 일반 버튼과 다르게 뷰가 탭 됐을 때 하이라이트 되지 않는다. 7 | ```swift 8 | struct TapGestureStudy: View { 9 | 10 | @State var isSelected: Bool = false 11 | 12 | var body: some View { 13 | VStack(spacing: 40){ 14 | RoundedRectangle(cornerRadius: 32) 15 | .foregroundColor(isSelected ? .green : .yellow) 16 | .frame(height: 200) 17 | // 일반 버튼 18 | Button { 19 | isSelected.toggle() 20 | } label: { 21 | Text("Button") 22 | .foregroundColor(.white) 23 | .frame(height: 50) 24 | .frame(maxWidth: .infinity) 25 | .background(Color.blue) 26 | .cornerRadius(25) 27 | } 28 | // 버튼 형태의 Text뷰 29 | Text("Tap Gesture") 30 | .foregroundColor(.white) 31 | .frame(height: 50) 32 | .frame(maxWidth: .infinity) 33 | .background(Color.blue) 34 | .cornerRadius(25) 35 | // 탭 제스처 인식 시 해당 코드 실행 36 | .onTapGesture { 37 | isSelected.toggle() 38 | } 39 | } 40 | .padding() 41 | } 42 | } 43 | ``` 44 | 45 | ### Count 46 | - `count` 파라미터를 통해 인식될 탭 횟수를 지정할 수 있다. 사용자는 지정된 횟수만큼 연속으로 탭해야 코드가 실행된다. 47 | ```swift 48 | struct TapGestureStudy: View { 49 | 50 | @State var isSelected: Bool = false 51 | 52 | var body: some View { 53 | VStack(spacing: 40){ 54 | RoundedRectangle(cornerRadius: 32) 55 | .foregroundColor(isSelected ? .green : .yellow) 56 | .frame(height: 200) 57 | Text("Tap Gesture") 58 | .foregroundColor(.white) 59 | .frame(height: 50) 60 | .frame(maxWidth: .infinity) 61 | .background(Color.blue) 62 | .cornerRadius(25) 63 | // 연속을 3번 탭해야 코드 실행 64 | .onTapGesture(count: 3) { 65 | isSelected.toggle() 66 | } 67 | } 68 | .padding() 69 | } 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Padding & Spacer/README.md: -------------------------------------------------------------------------------- 1 | # Padding, Spacer 2 | 뷰 자체 혹은 뷰 간의 여백 추가 3 | 4 | ### Padding 5 | - 뷰 자체의 수직, 수평 방향으로 여백을 추가 할 수 있다. 6 | ```swift 7 | Text("Hello World!") 8 | .background(Color.yellow) 9 | .padding() // 기본 패딩(모든 방향, 여백 10) 10 | .padding(.all, 20) // 모든 방향, 여백 20 11 | .padding(.vertical, 20) // 수직 방향, 여백 20 12 | .padding(.leading, 20) // 왼쪽 방향, 여백 20 13 | .background(Color.yellow) 14 | ``` 15 | 16 | ### Spacer 17 | - Stack뷰 안에 여백(빈 공간)을 추가할 수 있다. 18 | - Spacer는 크기는 유동적으로 조정되며, 가능한 많은 공간을 차지한다. 19 | ```swift 20 | HStack { 21 | Rectangle() 22 | .frame(width: 100, height: 100) 23 | .foregroundColor(Color.blue) 24 | Spacer() // 화면 양쪽 끝으로 두 사각형 배치 25 | Rectangle() 26 | .frame(width: 100, height: 100) 27 | .foregroundColor(Color.yellow) 28 | } 29 | ``` 30 | - minLength 파라미터를 통해 최소 여백을 설정할 수 있다.(기본값 8) 31 | ```swift 32 | HStack { 33 | Rectangle() 34 | .frame(width: 100, height: 100) 35 | .foregroundColor(Color.blue) 36 | Spacer(minLength: 20) // 패딩에 따라 너비가 조정될 때, 최소 너비 20 37 | Rectangle() 38 | .frame(width: 100, height: 100) 39 | .foregroundColor(Color.yellow) 40 | } 41 | .padding(.horizontal, 200) // 수평 방향 패딩 200 42 | ``` 43 | - frame 크기를 지정해 원하는 크기의 여백을 설정할 수 잇다. 44 | ```swift 45 | HStack { 46 | Rectangle() 47 | .frame(width: 100, height: 100) 48 | .foregroundColor(Color.blue) 49 | Spacer() 50 | .frame(width: 50) // 수평 방향 50의 여백 51 | Rectangle() 52 | .frame(width: 100, height: 100) 53 | .foregroundColor(Color.yellow) 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Picker/README.md: -------------------------------------------------------------------------------- 1 | # Picker 2 | 3 | ## 정의 4 | 5 | 선택지가 정해져 있고 거기에서 하나의 요소를 선택할 때 사용한다. 6 | 7 |
8 |
9 | 10 | ## 구성 요소 11 | 12 | Picker(title: StringProtocol, selection: Binding Hashable, content: View) 13 | 14 | - title: Label과 같은 역할 15 | - selection: 현재 선택된 것을 가르킬 Binding 가능 변수 16 | - content: 피커 안에서 보여 줄 요소 17 | 18 | 19 | ## 종류 20 | 21 | ### 1. WheelPickerStyle 22 | - 이름에서 유추할 수 있게 바퀴 처럼 감싸지는 느낌의 피커 스타일 입니다. 23 | 24 | ```swift 25 | 26 | struct ContentView: View { 27 | 28 | @State var selectedNumber:Int = 0 29 | 30 | 31 | 32 | var body: some View { 33 | VStack 34 | { 35 | Text("\(selectedNumber)") 36 | 37 | 38 | 39 | Picker("랜덤 숫자", selection: $selectedNumber) { 40 | ForEach((0..<10)) { i in 41 | Text("\(i)") 42 | .foregroundColor(.red) 43 | .tag(i) // 유니크한 id 값을 부여 44 | } 45 | } 46 | .pickerStyle(WheelPickerStyle()) 47 | .background(Color.gray.opacity(0.5)) 48 | } 49 | } 50 | 51 | } 52 | 53 | ``` 54 | 55 |

56 | 57 |

58 | 59 |

60 | 61 | ### 2. MenuPickerStyle 62 | - 컨텍스트 메뉴와 같은 느낌입니다 63 | - 처음 버튼에 보여지는 요소는 배열의 첫 번째 값입니다. 64 | 65 | ```swift 66 | import SwiftUI 67 | 68 | struct ContentView: View { 69 | 70 | @State var selected:String = "" 71 | 72 | let category:[String] = ["포항","서울","인천"] 73 | 74 | 75 | var body: some View { 76 | VStack 77 | { 78 | Text("\(selected)") 79 | 80 | 81 | 82 | Picker("랜덤 숫자", selection: $selected) { 83 | ForEach(category,id: \.self) { cat in 84 | Text("\(cat)") 85 | .foregroundColor(.red) 86 | .tag(cat) // 유니크한 id 값을 부여 87 | } 88 | } 89 | .pickerStyle(MenuPickerStyle()) 90 | 91 | } 92 | } 93 | 94 | } 95 | ``` 96 | 97 |

98 | 99 |

100 | 101 |

102 | 103 | ### 2. SegmentedPickerStyle 104 | - 탭 타입의 피커 스타일 입니다. 105 | 106 | ```swift 107 | import SwiftUI 108 | 109 | struct ContentView: View { 110 | 111 | @State var selected:String = "" 112 | 113 | let category:[String] = ["포항","서울","인천"] 114 | 115 | 116 | var body: some View { 117 | VStack 118 | { 119 | Text("\(selected)") 120 | 121 | 122 | 123 | Picker("랜덤 숫자", selection: $selected) { 124 | ForEach(category,id: \.self) { cat in 125 | Text("\(cat)") 126 | .foregroundColor(.red) 127 | .tag(cat) // 유니크한 id 값을 부여 128 | } 129 | } 130 | .pickerStyle(SegmentedPickerStyle()) 131 | 132 | } 133 | } 134 | 135 | } 136 | 137 | ``` 138 | 139 |

140 | 141 |

142 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Rotation Gesture/README.md: -------------------------------------------------------------------------------- 1 | # **#3 Rotation Gesture** 2 | 3 | - 두 손가락을 이용해 화면 속 요소를 회전시키는 제스처. 4 | - 두 손가락 터치패드: xcode 시뮬레이터 상에서 option 버튼을 누른 상태로 마우스로 화면을 드래그 5 | 6 | 7 | 8 | ```swift 9 | struct RotationGestureBootcamp: View { 10 | 11 | @State var angle: Angle = Angle(degrees: 0) 12 | // Angle이라는 SwiftUI 타입으로 angle 설정 후, 각도는 0도로 초기화시켜준다. 13 | 14 | var body: some View { 15 | // 파란 사각형 UI 코드 16 | Text("Hello, World!") 17 | .font(.largeTitle) 18 | .fontWeight(.semibold) 19 | .foregroundColor(.white) 20 | .padding(50) 21 | .background(Color.blue.cornerRadius(10)) 22 | 23 | // Rotation 관련 코드 24 | .rotationEffect(angle) // 실제로 입력된 각도에 따라 rotation 인터랙션을 실행시키는 함수 25 | .gesture( 26 | RotationGesture() 27 | .onChanged { value in // 두 손가락으로 돌리면 그 각도를 받아옴 28 | angle = value 29 | } 30 | .onEnded { value in // 손가락을 떼면 튕기는 애니메이션과 함께 각도값 0을 받아옴 31 | withAnimation(.spring()) { 32 | angle = Angle(degrees: 0) 33 | } 34 | } 35 | ) 36 | } 37 | } 38 | ``` 39 |
40 | 41 | ### **rotationEffect()** 42 | 43 | - 실제로 rotation 동작을 실행시키는 함수 44 | - angle: Angle type의 매개 변수, anchor: UnitPoint type의 매개변수를 받아 실행됨 45 | 46 | 47 | 48 |
49 | 50 | ### **RotationGesture()에서의 value 값** 51 | 52 | - 두 손가락으로 요소를 돌렸을 때, 그 각도를 radian 값으로 받아옴. 53 | 54 | 55 | 56 |
57 | 58 | 59 | 60 | *Angle 관련 definition* 61 | 62 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SafeAreaInset/README.md: -------------------------------------------------------------------------------- 1 | # SafeAreaInset 2 | - 디바이스 화면 SafeArea에 콘텐츠 뷰 추가 3 | 4 | ### safeAreaInset 5 | - 디바이스 화면의 가장자리 등 SafeArea에 여백을 추가하여 뷰를 배치할 수 있다. 6 | ```swift 7 | struct SafeAreaInsetStudy: View { 8 | var body: some View { 9 | NavigationStack { 10 | List(0..<10) { _ in 11 | Rectangle() 12 | .frame(height: 200) 13 | } 14 | .navigationTitle("Safe Area") 15 | // 하단 SafeArea에 뷰 추가 16 | .safeAreaInset(edge: .bottom, alignment: .center, spacing: nil) { 17 | Button("test") { 18 | // test button code 19 | } 20 | .frame(maxWidth: .infinity) 21 | .background(Color.green) 22 | } 23 | } 24 | } 25 | } 26 | ``` 27 | ![](https://velog.velcdn.com/images/snack/post/0d34550a-5a68-4db5-9175-413862dc56f8/image.png) 28 | - `navigationTitle`이 존재하는 상황에서 상단에 뷰를 추가할 경우, `navigationTitle` 영역을 포함한다. 29 | ```swift 30 | struct SafeAreaInsetStudy: View { 31 | var body: some View { 32 | NavigationStack { 33 | List(0..<10) { _ in 34 | Rectangle() 35 | .frame(height: 200) 36 | } 37 | .navigationTitle("Safe Area") 38 | // 상단 SafeArea에 뷰 추가 39 | .safeAreaInset(edge: .top, alignment: .center, spacing: nil) { 40 | Button("test") { 41 | 42 | } 43 | .frame(maxWidth: .infinity) 44 | .background(Color.green) 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | ![](https://velog.velcdn.com/images/snack/post/5cfd948e-b816-40f8-9b91-5f227ec0aba4/image.png) 51 | - 그러나 추가하는 뷰 하단의 SafeArea를 무시하는 경우, `navigationTitle` 영역을 포함하지 않는다. 52 | ```swift 53 | struct SafeAreaInsetStudy: View { 54 | var body: some View { 55 | NavigationStack { 56 | List(0..<10) { _ in 57 | Rectangle() 58 | .frame(height: 200) 59 | } 60 | .navigationTitle("Safe Area") 61 | .safeAreaInset(edge: .top, alignment: .center, spacing: nil) { 62 | Button("test") { 63 | // test button code 64 | } 65 | .frame(maxWidth: .infinity) 66 | // 추가하는 뷰 하단의 SafeArea 무시 67 | .background(Color.green.edgesIgnoringSafeArea(.bottom)) 68 | } 69 | } 70 | } 71 | } 72 | ``` 73 | ![](https://velog.velcdn.com/images/snack/post/2fd3435d-e9ac-4e94-8785-f1133305d597/image.png) 74 | -------------------------------------------------------------------------------- /SwiftUI/Summary/ScrollView/README.md: -------------------------------------------------------------------------------- 1 | # **#15 ScrollView** 2 | 3 | 4 | ```swift 5 | var body: some View { 6 | ScrollView { 7 | VStack { 8 | Rectangle() 9 | .frame(height: 300) 10 | Rectangle() 11 | .frame(height: 300) 12 | Rectangle() 13 | .frame(height: 300) 14 | } 15 | } 16 | } 17 | ``` 18 | 스크린샷 2023-04-27 오후 12 53 53 19 | 20 | 스크롤 가능한 뷰가 됨. 21 | 22 |
23 |
24 |
25 | 26 | 27 | 28 | ## ForEach문으로 반복 29 | 30 | 31 | 32 | ```swift 33 | ScrollView { 34 | VStack { 35 | ForEach(0..<50) { index in 36 | Rectangle() 37 | .fill(Color.blue) 38 | .frame(height: 300) 39 | } 40 | 41 | 42 | } 43 | } 44 | ``` 45 | 스크린샷 2023-04-27 오후 1 01 39 46 | 47 | 50개의 사각형을 스크롤 할 수 있음. 48 | 49 |
50 |
51 | 52 | ## 우측 스크롤바 지우고 싶다면 53 | 54 | ```swift 55 | ScrollView(.vertical, showsIndicators: false) { 56 | VStack { 57 | ForEach(0..<50) { index in 58 | Rectangle() 59 | .fill(Color.blue) 60 | .frame(height: 300) 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | scrollView의 매개변수 showIndicators의 값을 false로 하면됨. 67 | 68 |
69 |
70 | 71 | ## 가로로 스크롤 72 | 73 | ```swift 74 | ScrollView(.horizontal, showsIndicators: false) { 75 | HStack { 76 | ForEach(0..<50) { index in 77 | Rectangle() 78 | .fill(Color.blue) 79 | .frame(width: 300,height: 300) 80 | } 81 | } 82 | } 83 | ``` 84 | 스크린샷 2023-04-27 오후 1 10 55 85 | 86 | ScrollView의 축을 horizontal로 바꾸고 Vstack을 Hstack으로 바꿔주면 됨. width값도 추가. 87 | 88 | 89 |
90 |
91 | 92 | ## SccrollView 안에 ScrollView 넣기 93 | 94 | ```swift 95 | ScrollView { 96 | VStack { 97 | ForEach(0..<10) { index in 98 | ScrollView(.horizontal, showsIndicators: false, 99 | content: { 100 | HStack { 101 | ForEach(0..<20) { index in 102 | RoundedRectangle(cornerRadius: 25) 103 | .fill(Color.white) 104 | .frame(width: 200, height: 150) 105 | .shadow(radius: 10) 106 | .padding() 107 | } 108 | } 109 | }) 110 | } 111 | } 112 | } 113 | ```` 114 | 115 | 스크린샷 2023-04-27 오후 1 24 49 116 | 117 | 세로로 10개의 Hstack을 스크롤 할 수 있고, 각 Hstack 마다 20개의 사각형을 가로로 스크롤할 수 있게됨. -------------------------------------------------------------------------------- /SwiftUI/Summary/ScrollViewReader/README.md: -------------------------------------------------------------------------------- 1 | # ScrollViewReader 2 | - 스크롤 뷰의 위치를 제어하고 특정 뷰로 스크롤 할 수 있는 뷰 3 | 4 | ### ScrollView 내부에서 제어 5 | - 일반적으로 `ScrollView` 내부의 포함되는 뷰의 식별자를 사용하여 특정 뷰로 스크롤할 수 있다. 6 | - `id`값을 지정하여 해당 위치의 뷰로 스크롤할 수 있고, `anchor`를 통해 스크롤된 뷰의 위치를 지정할 수 있다. 7 | - `id`값에는 `Int`타입 뿐 아니라 `String`타입도 입력 가능하다. 8 | ```swift 9 | ScrollView { 10 | ScrollViewReader { proxy in 11 | Button("Click here to go to #49") { 12 | withAnimation(.spring()) { 13 | // 특정 id값의 위치로 스크롤 14 | proxy.scrollTo(30, anchor: .center) 15 | } 16 | } 17 | ForEach(0..<50) { index in 18 | Text("This is item #\(index)") 19 | .font(.headline) 20 | .frame(height: 200) 21 | .frame(maxWidth: .infinity) 22 | .background(Color.white) 23 | .cornerRadius(16) 24 | .shadow(radius: 10) 25 | .padding() 26 | .id(index) // id값 생성 27 | } 28 | } 29 | } 30 | ``` 31 | ![](https://velog.velcdn.com/images/snack/post/4db81d9b-1d35-4f10-87f0-a3528da42d30/image.png) 32 | 33 | ### ScrollView 외부에서 제어 34 | - `TextField`와 `onChange`를 활용하여 `ScrollView` 외부에서 스크롤 위치를 제어할 수 있다. 35 | ```swift 36 | struct ScrollViewReaderStudy: View { 37 | 38 | @State var scrollToIndex: Int = 0 39 | @State var textFieldText: String = "" 40 | 41 | var body: some View { 42 | VStack { 43 | // String 타입으로 스크롤할 ScrollView의 특정 위치를 입력 44 | TextField("scroll number", text: $textFieldText) 45 | .frame(height: 56) 46 | .border(.gray) 47 | .padding(.horizontal) 48 | .keyboardType(.numberPad) 49 | Button("Scroll Now") { 50 | // Int 타입으로 입력받은 위치를 변환 51 | if let index = Int(textFieldText) { 52 | scrollToIndex = index 53 | } 54 | } 55 | ScrollView { 56 | ScrollViewReader { proxy in 57 | ForEach(0..<50) { index in 58 | Text("This is item #\(index)") 59 | .font(.headline) 60 | .frame(height: 200) 61 | .frame(maxWidth: .infinity) 62 | .background(Color.white) 63 | .cornerRadius(16) 64 | .shadow(radius: 10) 65 | .padding() 66 | .id(index) 67 | } 68 | // 입력받은 Int 타입의 위치 값이 변할 때 해당 위치로 스크롤 69 | .onChange(of: scrollToIndex) { newValue in 70 | withAnimation { 71 | proxy.scrollTo(newValue, anchor: .center) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | ``` 80 | ![](https://velog.velcdn.com/images/snack/post/1e3dbb79-cc25-4acf-bdfa-8a44c33c3476/image.png) 81 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Shape/README.md: -------------------------------------------------------------------------------- 1 | # Shapes 2 | 3 |
4 | 5 | ## **Fill & Stroke** 6 |
7 | 8 | 9 | 10 | 11 |

12 | 13 | https://youtu.be/RKfkG01x79w?t=570 14 | 15 | ```swift 16 | Circle() 17 | .fill(Color.green) // 색 채우기 1 18 | .foregroundColor(.green) // 색 채우기 2 19 | .stroke() // 검은 테두리 생성 20 | .stroke(Color.blue) // 테두리 색 21 | .stroke(.blue, lineWidth: 5.0) // 테두리 색, 두께 지정 22 | 23 | .stroke(.orange, style: StrokeStyle(lineWidth: 20, lineCap: .butt, dash: [10])) 24 | // StrokeStyle(lineWidth: _, lineCap: _, lineJoin: _, miterLimit: _, dash: _, dashPhase: _) 25 | // 원하는 것 선택 사용 가능 26 | ``` 27 |
28 | 29 | ### **round와 square가 butt보다 길어보이는 이유** 30 | 31 | .butt은 기준선 길이와 막대의 길이가 같고, .round와 .square는 기준선의 **양 끝에도 두께 처리**가 돼서 실제로는 더 길어보인다 32 | 33 | 34 | 35 |
36 |
37 | 38 | # 39 | 40 | ## **Trim** 41 |
42 | 43 | ### **1. 원하는 범위대로 자르기** 44 |
45 | 46 |

47 | 48 |

49 | 50 | https://youtu.be/1dWHjdWgS5M?t=377 51 | 52 | ```swift 53 | Circle() 54 | .trim(from: 0.0, to: 1.0) // 전체 원 그려짐 55 | .trim(from: 0.5, to: 1.0) // 0.5 지점부터 1.0 지점까지 위로 둥근 반원 56 | .trim(from: 0.0, to: 0.5) // 0.0 지점부터 0.5 지점까지 아래로 둥근 반원 57 | ``` 58 |
59 |
60 | 61 | ### **2. Stroke 주기** 62 |
63 | 64 |

65 | 66 |

67 | 68 | https://youtu.be/RKfkG01x79w?t=653 69 | 70 | ```swift 71 | Circle() 72 | .trim(from: 0.2, to: 1.0) // 0.2 부터 1.0 trim 73 | .stroke(.purple, lineWidth: 50) // 테두리 색, 두께 74 | ``` 75 | - 잘린 모양대로 원 테두리를 주고 싶으면 무조건 trim 이후 stroke 설정하기 ! 76 | - 로딩 화면에서 인터랙션 아이콘으로 많이 쓰이는 코드 77 | 78 |
79 |
80 | 81 | # 82 | 83 | ## **그 외 다양한 도형** 84 |
85 | 86 | ### **Capsule()** 87 |
88 |

89 | 90 |

91 | 92 | https://youtu.be/1dWHjdWgS5M?t=535 93 | 94 | ```swift 95 | VStack { 96 | Capsule(style: .circular) // .circular는 사각형에서 모서리만 둥글게 97 | .frame(width: 300, height: 200) 98 | Capsule(style: .continuous) // .continuous는 부드러운 타원 느낌 99 | .frame(width: 300, height: 200) 100 | } 101 | ``` 102 | 103 |
104 |
105 | 106 | ### **RoundedRectangle()** 107 |
108 | 109 | - 자주 쓰이는 도형(버튼) 110 |

111 | 112 |

113 | 114 | https://youtu.be/1dWHjdWgS5M?t=610 115 | 116 | ```swift 117 | RoundedRectangle(cornerRadius: 50). // 모서리 라운드값 설정 118 | .frame(width: 300, height: 200) 119 | ``` -------------------------------------------------------------------------------- /SwiftUI/Summary/Slider/README.md: -------------------------------------------------------------------------------- 1 | # **#42 Slider** 2 | 3 | 4 | ### **`Slider(value: Binding)`** 5 | 6 | - slider의 value만을 받아서 만들 때 사용 7 | - `BinaryFloatingPoint` : 보통 숫자를 의미함 8 | 9 |
10 | 11 | slider의 값으로 설정할 변수를 만들자. 12 | 13 | ```swift 14 | @State var sliderValue: Double = 10 15 | ``` 16 | 17 | 그 후 view에 VStack을 이용해 Slider()를 생성한다. 18 | 19 | ```swift 20 | VStack { 21 | Text("Rating:") 22 | Text("\(sliderValue)") 23 | Slider(value: $sliderValue) 24 | .tint(.red) // Slider 색상 파란색 -> 빨간색 25 | } 26 | ``` 27 | 28 | 29 |
30 |
31 | 32 | 33 | ### **`Slider(value:in)`** 34 | 35 | - slider 조작부의 처음 시작 위치를 지정해두고, 움직일 범위를 따로 설정하고 싶을 때 사용 36 | - `value:` 에는 Binding 값, `in:` 에는 closedRange 값 넣기 37 | 38 | ```swift 39 | Slider(value: $sliderValue, in: 0...100) // slider의 가동범위는 0부터 100까지 40 | .tint(.red) 41 | ``` 42 | 43 | 44 | 45 | sliderValue에 넣어둔 초기값(10)의 위치에 조작부가 멈춰있고, 0부터 100까지 slider를 움직일 수 있다. 46 | 47 |
48 |
49 | 50 | ### **`Slider(value:in:step)`** 51 | 52 | - slider가 double 값 모두 선택 가능한 것이 아니라, 전체 범위 중 **특정 구간에만 멈출 수 있게** 할 때 사용 53 | - `value:` 에는 Binding 값, `in:` 에는 closedRange 값, step:에는 구간 값 넣기 54 | 55 | ```swift 56 | Slider(value: $sliderValue, in: 1...10, step: 1.0) // 1.0마다 slider 멈추기 57 | .tint(.red) 58 | ``` 59 | 60 | 61 |
62 |
63 | 64 | ### **`Slider(value:in:step:label:minimumValueLabel:maximumValueLabel:onEditingChanged:)`** 65 | 66 | - slider 주변에 label을 달아 커스텀하고, slider value 값이 변경됐을 때의 action을 주고 싶을 때 사용 67 | 68 | ```swift 69 | @State var sliderValue: Double = 10 70 | @State var color: Color = .red 71 | ``` 72 | 73 | ```swift 74 | VStack { 75 | Text("Rating:") 76 | Text(String(format: "%.2f", sliderValue)) 77 | .foregroundColor(color) 78 | 79 | Slider( 80 | value: $sliderValue, 81 | in: 0...10, 82 | step: 1.0) { 83 | Text("Title") // 'label:'에 해당하는 내용. 나타나지 않음 84 | } minimumValueLabel: { // slider의 왼쪽 끝에 나타낼 label 85 | Text("0") 86 | } maximumValueLabel: { // slider의 오른쪽 끝에 나타낼 label 87 | Text("10") 88 | } onEditingChanged: { (_) in // slider value가 바뀌는 경우 어떤 동작이 일어나는지 89 | // _ 대신 default 값으로는 Bool이 설정되어 있는데, Bool을 쓰지 않는 경우 _로 처리 가능 90 | color = .green // 동작: 숫자 글씨 색 초록색으로 바꾸기 91 | } 92 | } 93 | ``` 94 | 95 | 96 |
97 |
98 |
99 | 100 | ## **숫자의 format 변경하는 법** 101 | 102 | - 나타나는 숫자의 소수점들이 거슬릴 때 → 소수점 몇째 자리까지 나타낼 것인지 설정할 수 있다. 103 | - `String(format:argument)` 104 | - `format:` 소수점 몇째 자리까지 나타낼 것인지, `argument:` 변경할 대상(변수) 넣기 105 | 106 | ```swift 107 | Text( 108 | String(format: "%.2f", sliderValue) // sliderValue의 값을 소수점 두자리까지 표기하기 109 | ) 110 | ``` 111 | 112 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SoundEffect/README.md: -------------------------------------------------------------------------------- 1 | # SoundEffect 2 | 3 | ## 들어가기 전 알아야할 개념 4 | 5 | ### 싱글톤 패턴(Singleton)이란? 6 | 7 | 객체의 인스턴스가 오직 **1개**만 생성되는 패턴을 의미한다. 8 |

9 | 10 | ### 쓰는 이유 11 | 12 | 1. 메모리 측면 13 | 14 | - 최초 한번의 생성자를 통해서 고정된 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다. 15 | 16 | - 뿐만 아니라 이미 생성된 인스턴스를 활용하니 속도 측면에서도 이점이 있다고 볼 수 있다. 17 | 18 | 2. 데이터 공유가 쉽다 19 | 20 | - 싱글톤 인스턴스가 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다. 21 | - 하지만 여러 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 동시에 접근하게 되면 동시성 문제가 발생할 수 있으니 이점을 유의해서 설계하는 것이 좋다. 22 | 23 | ### 단점 24 | 25 | 1. 유지보수 비용이 높아질 수 있다. 26 | - 싱글톤의 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우 다른 클래스와 결합도가 높아져 객체 지향 설계 원칙이 어긋나 수정이 어려워 진다. 27 | 28 | 2. 멀티스레드 환경 29 | - 멀티스레드 환경에서 **동기화** 처리를 안할 경우 인스턴스가 1개보다 많이 생서 될 수 있다. 30 | 31 | --- 32 | 33 |
34 | 35 | ## 내용 36 | 37 | ### 1. 사운드 재생을 위해 AVKit을 사용한다. 38 | ### 2. Bundle을 이용하여 파일의 경로를 읽어온다. 39 | ### 3. 싱글톤 패턴을 이용한 SoundManager를 이용한다. 40 | 41 |
42 | 43 | ## SoundManager 44 | ```swift 45 | 46 | import AVKit 47 | 48 | class SoundManager { 49 | 50 | static let instance = SoundManager() // 싱글톤 51 | var player: AVAudioPlayer? 52 | 53 | enum SoundOption: String { 54 | case tada 55 | case duck 56 | } 57 | 58 | func playSound(sound:SoundOption) { 59 | 60 | guard let url = Bundle.main.url(forResource: sound.rawValue, withExtension: ".mp3") else {return} 61 | //해당 음원파일.mp3 파일의 경로를 읽어 온다 62 | 63 | do { 64 | player = try AVAudioPlayer(contentsOf: url) 65 | player?.play() // 재생 66 | } catch let error { 67 | print("Error playing sound. \(error.localizedDescription)") 68 | } 69 | 70 | } 71 | } 72 | 73 | ``` 74 | 75 |
76 | 77 | ## View 78 | 79 | ```swift 80 | struct ContentView: View { 81 | 82 | 83 | 84 | var body: some View { 85 | VStack{ 86 | 87 | Button("Play sound 1"){ 88 | SoundManager.instance.playSound(sound: .tada) 89 | } 90 | 91 | Button("Play sound 2"){ 92 | SoundManager.instance.playSound(sound: .duck) 93 | } 94 | 95 | } 96 | .padding(40) 97 | 98 | } 99 | 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Stack/README.md: -------------------------------------------------------------------------------- 1 | # Stack 2 | - 한 개 이상의 뷰를 묶어서 배치 3 | - 최대 10개까지 배치 가능 4 | 5 | ### HStack 6 | - 한 개 이상의 뷰를 수평 방향으로 배치할 수 있다. 7 | - 수직 방향으로 정렬할 수 있다. 8 | - 뷰간의 간격을 조절할 수 있다.(기본값 8) 9 | ```swift 10 | HStack (alignment: .bottom, spacing: 10) { // 아래쪽 정렬, 간격 10 11 | Circle() // 왼쪽 12 | .fill(Color.red) 13 | .frame(width: 150, height: 150) 14 | Circle() // 가운데 15 | .fill(Color.green) 16 | .frame(width: 100, height: 100) 17 | Circle() // 오른쪽 18 | .fill(Color.blue) 19 | .frame(width: 50, height: 50) 20 | } 21 | ``` 22 | 23 | ### VStack 24 | - 한 개 이상의 뷰를 수직 방향으로 배치할 수 있다. 25 | - 수평 방향으로 정렬할 수 있다. 26 | - 뷰간의 간격을 조절할 수 있다.(기본값 8) 27 | ```swift 28 | VStack (alignment: .leading, spacing: 10) { // 왼쪽 정렬, 간격 10 29 | Circle() // 위쪽 30 | .fill(Color.red) 31 | .frame(width: 150, height: 150) 32 | Circle() // 가운데 33 | .fill(Color.green) 34 | .frame(width: 100, height: 100) 35 | Circle() // 아래쪽 36 | .fill(Color.blue) 37 | .frame(width: 50, height: 50) 38 | } 39 | ``` 40 | 41 | ### ZStack 42 | - 한 개 이상의 뷰를 Z축 방향으로 배치할 수 있다. 43 | - 수직, 수평 방향으로 정렬할 수 있다. 44 | - 밑에 배치 될 수록 앞에 위치한다. 45 | - zIndex(n)를 이용하여 앞으로 배치할 수 있다. defaut 값 = 0 46 | ```swift 47 | ZStack (alignment: .topLeading) { // 왼쪽 위 정렬 48 | Circle() // 뒤쪽 49 | .fill(Color.red) 50 | .frame(width: 150, height: 150) 51 | Circle() // 가운데 52 | .fill(Color.green) 53 | .frame(width: 100, height: 100) 54 | Circle() // 앞쪽 55 | .fill(Color.blue) 56 | .frame(width: 50, height: 50) 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /SwiftUI/Summary/State PropertyWrapper/README.md: -------------------------------------------------------------------------------- 1 | # @State PropertyWrapper 2 | 3 | View는 @State 값을 관찰하고 있다가 값이 변화면 뷰를 새로 그린다. 4 | 5 | ```swift 6 | 7 | struct SwiftUIView: View { 8 | 9 | @State var count:Int = 0 10 | 11 | var body: some View { 12 | ZStack{ 13 | Color.red 14 | .edgesIgnoringSafeArea(.all) 15 | 16 | VStack(spacing:20){ 17 | Text("Title") 18 | Text("Count: \(count)") 19 | 20 | HStack(spacing: 20) { 21 | Button("증가") { 22 | count += 1 23 | } 24 | } 25 | 26 | } 27 | } 28 | } 29 | } 30 | 31 | ``` -------------------------------------------------------------------------------- /SwiftUI/Summary/Stepper/README.md: -------------------------------------------------------------------------------- 1 | # **#41 Stepper** 2 | 3 | 4 | ## **기본 Stepper** 5 | 6 | - `Stepper(title: StringProtocol, value: Binding)` 7 | - **Strideable** 뜻 : continuous and one-dimensional value that can be offset and measured 8 | - **숫자**처럼 값을 올리고 내릴 수 있는 것! 9 | 10 |
11 | 12 | 따라서 Integer 값으로 변수를 설정해주고, 13 | 14 | ```swift 15 | @State var stepperValue : Int = 10 16 | ``` 17 | 18 | View 안에 Stepper()를 사용해준다. 19 | 20 | ```swift 21 | var body: some View { 22 | Stepper("Stepper: \(stepperValue)", value: $stepperValue) 23 | .padding(50) 24 | } 25 | ``` 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 | 34 | ## **Customized Stepper** 35 | 36 | - `Stepper(title:onIncrement:onDecrement)` 37 | - Stepper의 증가와 감소 속성을 커스텀 할 수 있다. 38 | 39 |
40 | 41 | CGFloat Type의 변수를 설정해준 후, 42 | 43 | ```swift 44 | @State var widthIncrement: CGFloat = 0 45 | ``` 46 | 47 | View에서는 VStack을 이용해 RoundedRectangle의 너비를 Customized Stepper를 이용해 조절해보겠다. 48 | 49 | ```swift 50 | VStack { 51 | RoundedRectangle(cornerRadius: 25.0) 52 | .frame(width: 100 + widthIncrement, height: 100) 53 | 54 | Stepper("Stepper 2") { 55 | // increment 56 | widthIncrement += 10 // '+' 누르면 10씩 증가 57 | } onDecrement: { 58 | // decrement 59 | widthIncrement -= 10 // '-' 누르면 10씩 감소 60 | } 61 | } 62 | ``` 63 | 64 | 65 |
66 | 67 | 사각형의 움직임이 너무 딱딱하다 느껴질 땐, animation을 적용하여 움직임을 부드럽게 만들 수 있다. 68 | 69 | `var** body: **some** View { }` 아래에 함수를 하나 만들자. 70 | 71 | ```swift 72 | func incrementWidth(amount: CGFloat) { // CGFloat의 값을 받기 73 | withAnimation(.easeInOut) { // ease in & out animation 적용 74 | widthIncrement += amount // 한 번에 amount 값 만큼 너비 증가 75 | } 76 | } 77 | ``` 78 | 79 | 그리고 아까 `Stepper() {}` 부분을 이 함수로 대체하자. 80 | 81 | ```swift 82 | Stepper("Stepper 2") { 83 | // increment 84 | incrementWidth(amount: 10) // '+' 누르면 10씩 증가 85 | } onDecrement: { 86 | // decrement 87 | incrementWidth(amount: -10) // '-' 누르면 10씩 감소 88 | } 89 | ``` 90 | 91 | 92 | 93 | 움직임이 부드러워졌다! amount의 값이 커질수록 더 smooth 해진다. -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/AppIcon.appiconset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/AppIcon.appiconset/logo.png -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/LaunchColors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/LaunchColors/LaunchAccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFB", 9 | "green" : "0xCF", 10 | "red" : "0xF9" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/LaunchColors/LaunchBackgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0x00", 10 | "red" : "0x00" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/ThemeColors/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFB", 27 | "green" : "0xCF", 28 | "red" : "0xF9" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/ThemeColors/BackgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xFF", 10 | "red" : "0xFF" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x00", 27 | "green" : "0x00", 28 | "red" : "0x00" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/ThemeColors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/ThemeColors/GreenColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0x8E", 10 | "red" : "0x00" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x78", 27 | "green" : "0xFA", 28 | "red" : "0x72" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/ThemeColors/RedColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.319", 9 | "green" : "0.088", 10 | "red" : "0.581" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.573", 27 | "green" : "0.186", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/ThemeColors/SecondaryTextColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.570", 9 | "green" : "0.570", 10 | "red" : "0.570" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.754", 27 | "green" : "0.754", 28 | "red" : "0.754" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/coingecko.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "coingecko.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/coingecko.imageset/coingecko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/coingecko.imageset/coingecko.png -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo-transparent.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "logo-transparent@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "logo-transparent@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/logo-transparent.png -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/logo-transparent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/logo-transparent@2x.png -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/logo-transparent@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo-transparent.imageset/logo-transparent@3x.png -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongbeomkwak/import-SwiftUI/3ff6d48435de1d711f7857b149b23ac67a3f9f4e/SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Assets.xcassets/images/logo.imageset/logo.png -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | ZStack{ 13 | Color.theme.red 14 | } 15 | } 16 | } 17 | 18 | struct ContentView_Previews: PreviewProvider { 19 | static var previews: some View { 20 | ContentView() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/CircleButton/CircleButtonAnimationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircleButtonAnimationView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CircleButtonAnimationView: View { 11 | 12 | @Binding var animate:Bool 13 | 14 | var body: some View { 15 | Circle() 16 | .stroke(lineWidth: 5.0) 17 | .scale(animate ? 1.0 : 0.0) 18 | .opacity(animate ? 0.0 : 1.0) 19 | .animation(animate ? Animation.easeInOut(duration: 1) : .none, value: animate) 20 | 21 | } 22 | } 23 | 24 | struct CircleButtonAnimationView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | CircleButtonAnimationView(animate: .constant(false)) 27 | .foregroundColor(.red) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/CircleButton/CircleButtonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircleButtonView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CircleButtonView: View { 11 | 12 | let iconName:String 13 | 14 | var body: some View { 15 | Image(systemName: iconName) 16 | .font(.headline) 17 | .foregroundColor(.theme.accent) 18 | .frame(width: 50,height: 50) 19 | .background( 20 | Circle() 21 | .foregroundColor(.theme.background) 22 | ) 23 | .shadow(color: .theme.accent.opacity(0.25), radius: 10,x: 0,y: 0) 24 | .padding() 25 | } 26 | } 27 | 28 | struct CircleButtonView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | 31 | Group{ 32 | CircleButtonView(iconName: "info") 33 | .previewLayout(.sizeThatFits) 34 | 35 | CircleButtonView(iconName: "info") 36 | .previewLayout(.sizeThatFits) 37 | .preferredColorScheme(.dark) 38 | } 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/CoinImage/CoinImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinImageView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import SwiftUI 9 | 10 | 11 | 12 | 13 | struct CoinImageView: View { 14 | 15 | @StateObject var vm:CoinImageViewModel 16 | 17 | init(coin:CoinModel){ 18 | _vm = StateObject(wrappedValue: CoinImageViewModel(coin: coin)) 19 | } 20 | 21 | 22 | 23 | var body: some View { 24 | ZStack{ 25 | if let image = vm.image { 26 | Image(uiImage: image) 27 | .resizable() 28 | .scaledToFit() 29 | } else if vm.isLoading { 30 | ProgressView() 31 | } else { 32 | Image(systemName: "questionmark") 33 | .foregroundColor(.theme.secondaryText) 34 | } 35 | } 36 | } 37 | } 38 | 39 | struct CoinImageView_Previews: PreviewProvider { 40 | static var previews: some View { 41 | CoinImageView(coin: dev.coin) 42 | .padding() 43 | .previewLayout(.sizeThatFits) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/CoinImage/CoinImageViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinImageViewModel.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import Combine 11 | 12 | class CoinImageViewModel : ObservableObject { 13 | @Published var image: UIImage? = nil 14 | @Published var isLoading: Bool = false 15 | 16 | 17 | private let coin:CoinModel 18 | private let dataService:CoinImageService 19 | private var cancellables = Set() 20 | 21 | init(coin:CoinModel){ 22 | self.coin = coin 23 | self.dataService = CoinImageService(coin: coin) 24 | self.addSubscribers() 25 | self.isLoading = true 26 | } 27 | 28 | private func addSubscribers(){ 29 | 30 | dataService.$image 31 | .sink(receiveCompletion: { [weak self] _ in 32 | self?.isLoading = false 33 | 34 | }, receiveValue: { [weak self] image in 35 | 36 | self?.image = image 37 | 38 | }).store(in: &cancellables) 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/CoinLogoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinLogoView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoinLogoView: View { 11 | 12 | let coin: CoinModel 13 | 14 | var body: some View { 15 | 16 | VStack{ 17 | CoinImageView(coin: coin) 18 | .frame(width: 50,height: 50) 19 | Text(coin.symbol.uppercased()) 20 | .font(.headline) 21 | .foregroundColor(.theme.accent) 22 | .lineLimit(1) 23 | .minimumScaleFactor(0.5) 24 | Text(coin.name) 25 | .font(.caption) 26 | .foregroundColor(.theme.secondaryText) 27 | .lineLimit(2) 28 | .minimumScaleFactor(0.5) 29 | .multilineTextAlignment(.center) 30 | } 31 | } 32 | } 33 | 34 | struct CoinLogoView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | CoinLogoView(coin: dev.coin) 37 | .previewLayout(.sizeThatFits) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/SearchBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchBarView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SearchBarView: View { 11 | 12 | @Binding var searchText:String 13 | 14 | var body: some View { 15 | HStack{ 16 | Image(systemName: "magnifyingglass") 17 | .foregroundColor(searchText.isEmpty ? .theme.secondaryText : .theme.accent) 18 | 19 | 20 | TextField("Search by name or symbol...",text: $searchText) 21 | .foregroundColor(.theme.accent) 22 | .autocorrectionDisabled(true) 23 | .overlay( 24 | Image(systemName: "xmark.circle.fill") 25 | .padding() 26 | .offset(x:10) 27 | .foregroundColor(.accentColor) 28 | .opacity(searchText.isEmpty ? 0 : 1) 29 | .onTapGesture { 30 | UIApplication.shared.endEditing() 31 | searchText = "" 32 | } 33 | ,alignment: .trailing) 34 | 35 | 36 | } 37 | .font(.headline) 38 | .padding() 39 | .background( 40 | RoundedRectangle(cornerRadius: 25) 41 | .fill(Color.theme.background) 42 | .shadow(color: .theme.accent.opacity(0.5), radius: 10,x: 0,y:0) 43 | ) 44 | .padding() 45 | 46 | 47 | } 48 | } 49 | 50 | struct SearchBarView_Previews: PreviewProvider { 51 | static var previews: some View { 52 | 53 | Group{ 54 | SearchBarView(searchText: .constant("")) 55 | .previewLayout(.sizeThatFits) 56 | .preferredColorScheme(.light) 57 | 58 | SearchBarView(searchText: .constant("")) 59 | .previewLayout(.sizeThatFits) 60 | .preferredColorScheme(.dark) 61 | } 62 | 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/StatisticView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatisticView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct StatisticView: View { 11 | 12 | let stat:StatisticModel 13 | 14 | var body: some View { 15 | VStack(alignment: .leading,spacing: 4){ 16 | 17 | Text(stat.title) 18 | .font(.caption) 19 | .foregroundColor(.theme.secondaryText) 20 | Text(stat.value) 21 | .font(.headline) 22 | .foregroundColor(.theme.accent) 23 | 24 | HStack{ 25 | Image(systemName: "triangle.fill") 26 | .font(.caption2) 27 | .rotationEffect(Angle(degrees: (stat.percentageChange ?? 0) >= 0 ? 0 : 180)) 28 | 29 | Text(stat.percentageChange?.asPercentString() ?? "") 30 | .font(.caption) 31 | .bold() 32 | } 33 | .foregroundColor(stat.percentageChange ?? 0 >= 0 ? .theme.green : .red) 34 | .opacity(stat.percentageChange == nil ? 0 : 1) 35 | 36 | } 37 | 38 | } 39 | } 40 | 41 | struct StatisticView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | StatisticView(stat: dev.stat1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Components/XMarkButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMarkButton.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct XMarkButton: View { 11 | @Environment(\.dismiss) var dismiss 12 | 13 | var body: some View { 14 | Button { 15 | dismiss() 16 | } label: { 17 | Image(systemName: "xmark") 18 | .font(.headline) 19 | } 20 | } 21 | } 22 | 23 | struct XMarkButton_Previews: PreviewProvider { 24 | static var previews: some View { 25 | XMarkButton() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Home/Views/CoinRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinRowView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoinRowView: View { 11 | 12 | let coin:CoinModel 13 | let showHoldingsColumn: Bool 14 | 15 | var body: some View { 16 | 17 | HStack(spacing:0){ 18 | 19 | leftColumn 20 | 21 | if showHoldingsColumn { 22 | centerColumn 23 | } 24 | 25 | rightColumn 26 | 27 | 28 | } 29 | .font(.subheadline) 30 | .background( 31 | Color.theme.background.opacity(0.001) 32 | ) 33 | } 34 | } 35 | 36 | struct CoinRowView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | 39 | Group{ 40 | CoinRowView(coin: dev.coin,showHoldingsColumn: true) 41 | .previewLayout(.sizeThatFits) 42 | CoinRowView(coin: dev.coin,showHoldingsColumn: true) 43 | .previewLayout(.sizeThatFits) 44 | .preferredColorScheme(.dark) 45 | } 46 | 47 | } 48 | } 49 | 50 | 51 | extension CoinRowView { 52 | 53 | private var leftColumn: some View { 54 | HStack(spacing:0){ 55 | Text("\(coin.rank)") 56 | .font(.caption) 57 | .foregroundColor(.theme.secondaryText) 58 | .frame(minWidth: 30) 59 | CoinImageView(coin: coin) 60 | .frame(width: 30,height: 30) 61 | Text(coin.symbol.uppercased()) 62 | .font(.headline) 63 | .padding(.leading,6) 64 | .foregroundColor(.theme.accent) 65 | Spacer() 66 | } 67 | } 68 | 69 | private var centerColumn: some View { 70 | VStack(alignment: .trailing){ 71 | Text(coin.currentHoldingsValue.asCurrencyWith2Decimals()) 72 | .bold() 73 | Text((coin.currentHoldings ?? 0).asNumberString()) 74 | } 75 | .foregroundColor(.theme.accent) 76 | } 77 | 78 | private var rightColumn: some View { 79 | VStack(alignment: .trailing){ 80 | Text("\(coin.currentPrice.asCurrencyWith6Decimals())") 81 | .bold() 82 | .foregroundColor(.theme.accent) 83 | Text(coin.priceChangePercentage24H?.asPercentString() ?? "") 84 | .foregroundColor( 85 | (coin.priceChangePercentage24H ?? 0) >= 0 ? 86 | .theme.green : 87 | .theme.red 88 | ) 89 | }.frame(width: UIScreen.main.bounds.width / 3.5,alignment: .trailing) 90 | } 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Home/Views/HomeStatsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeStatsView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeStatsView: View { 11 | 12 | 13 | @EnvironmentObject private var vm:HomeViewModel 14 | @Binding var showPortfolio:Bool 15 | 16 | 17 | var body: some View { 18 | HStack{ 19 | ForEach(vm.statistics) { stat in 20 | StatisticView(stat: stat) 21 | .frame(width: UIScreen.main.bounds.width / 3) 22 | } 23 | } 24 | .frame(width: UIScreen.main.bounds.width,alignment: showPortfolio ? .trailing : .leading) 25 | } 26 | } 27 | 28 | struct HomeStatsView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | HomeStatsView(showPortfolio: .constant(true)) 31 | .environmentObject(dev.homeVM) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Launch/Views/LaunchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/05/01. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LaunchView: View { 11 | 12 | @State private var loadingText: [String] = "Loading your portfolio".map{String($0)} 13 | @State private var showLoadingText : Bool = false 14 | @State private var counter:Int = 0 15 | @State private var loops: Int = 0 16 | @Binding var showLaunchView: Bool 17 | 18 | private let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() 19 | 20 | var body: some View { 21 | ZStack{ 22 | Color.launch.background 23 | .ignoresSafeArea() 24 | 25 | Image("logo-transparent") 26 | .resizable() 27 | .frame(width: 100,height: 100) 28 | 29 | ZStack{ 30 | if showLoadingText { 31 | HStack(spacing:0){ 32 | ForEach(loadingText.indices){ index in 33 | Text(loadingText[index]) 34 | .font(.headline) 35 | .fontWeight(.heavy) 36 | .foregroundColor(.launch.accent) 37 | .offset(y:counter == index ? -5 :0) 38 | 39 | } 40 | } 41 | .transition(.scale.animation(.easeIn)) 42 | 43 | 44 | } 45 | } 46 | .offset(y:70) 47 | 48 | } 49 | .onAppear{ 50 | showLoadingText.toggle() 51 | } 52 | .onReceive(timer) { _ in 53 | withAnimation(.spring()) { 54 | 55 | let lastIndex = loadingText.count - 1 56 | 57 | if counter == lastIndex { 58 | counter = 0 59 | loops += 1 60 | 61 | if loops >= 2 { 62 | showLaunchView = false 63 | } 64 | } else { 65 | counter += 1 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | struct LaunchView_Previews: PreviewProvider { 73 | static var previews: some View { 74 | LaunchView(showLaunchView: .constant(false)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Core/Setting/Views/SettingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingView.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/05/01. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SettingView: View { 11 | 12 | let defaultURL = URL(string: "https://www.google.com") 13 | let youtubeURL = URL(string: "https://www.youtube.com/c/swiftfulthinking") 14 | 15 | var body: some View { 16 | NavigationStack{ 17 | List{ 18 | Section { 19 | VStack(alignment: .leading){ 20 | Image("logo") 21 | .resizable() 22 | .frame(width: 100,height: 100) 23 | .clipShape(RoundedRectangle(cornerRadius: 20)) 24 | 25 | 26 | Text("BlaBla") 27 | .font(.callout) 28 | .fontWeight(.medium) 29 | .foregroundColor(.theme.accent) 30 | } 31 | 32 | } header: { 33 | Text("Swiftui Thinking") 34 | } footer: { 35 | Text("Footer") 36 | } 37 | 38 | 39 | } 40 | } 41 | .listStyle(GroupedListStyle()) 42 | .navigationTitle("Setting") 43 | .toolbar { 44 | ToolbarItem(placement: .navigationBarLeading) { 45 | XMarkButton() 46 | } 47 | } 48 | } 49 | } 50 | 51 | struct SettingView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | NavigationStack{ 54 | SettingView() 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Extensions/Extension+Color.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | extension Color { 5 | 6 | static let theme = ColorTheme() 7 | static let launch = LaunchTheme() 8 | } 9 | 10 | struct ColorTheme { 11 | 12 | let accent = Color("AccentColor") 13 | let background = Color("BackgroundColor") 14 | let green = Color("GreenColor") 15 | let red = Color("RedColor") 16 | let secondaryText = Color("SecondaryTextColor") 17 | } 18 | 19 | struct LaunchTheme { 20 | 21 | let accent = Color("LaunchAccentColor") 22 | let background = Color("LaunchBackgroundColor") 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Extensions/Extension+Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension+Date.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/30. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | 12 | // "2021-03-13T20:49:26.606Z" 13 | init(coinGeckoString: String) { 14 | let formatter = DateFormatter() 15 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 16 | let date = formatter.date(from: coinGeckoString) ?? Date() 17 | self.init(timeInterval: 0, since: date) 18 | } 19 | 20 | private var shortFormatter: DateFormatter { 21 | let formatter = DateFormatter() 22 | formatter.dateStyle = .short 23 | return formatter 24 | } 25 | 26 | func asShortDateString() -> String { 27 | return shortFormatter.string(from: self) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Extensions/Extension+String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension+String.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/05/01. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | extension String { 12 | 13 | 14 | var removingHTMLOccurances: String { 15 | return self.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Extensions/Extension+UIApplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension+UIApplication.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UIApplication { 12 | func endEditing() { 13 | sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) // 키보드 없애기 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Models/PortfolioContainer.xcdatamodeld/PortfolioContainer.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Models/StatisticModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stat.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import Foundation 9 | 10 | struct StatisticModel: Identifiable { 11 | let id = UUID().uuidString 12 | let title: String 13 | let value: String 14 | let percentageChange: Double? 15 | 16 | init(title: String, value: String, percentageChange: Double? = nil) { 17 | self.title = title 18 | self.value = value 19 | self.percentageChange = percentageChange 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Services/CoinDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinDataService.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/25. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class CoinDataService { 12 | 13 | @Published var allCoins: [CoinModel] = [] 14 | var coinSubscription: AnyCancellable? 15 | 16 | 17 | init(){ 18 | getCoins() 19 | } 20 | 21 | 22 | func getCoins(){ 23 | 24 | guard let url = URL(string: "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=true&price_change_percentage=24h") else {return} 25 | 26 | 27 | coinSubscription = NetworkingManager.download(url: url) 28 | .decode(type: [CoinModel].self, decoder: JSONDecoder()) 29 | .receive(on:DispatchQueue.main) 30 | .sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] coins in 31 | guard let self else {return} 32 | self.allCoins = coins 33 | self.coinSubscription?.cancel() 34 | 35 | }) 36 | 37 | 38 | 39 | } 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Services/CoinDetailDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinDataService.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/25. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class CoinDetailDataService { 12 | 13 | @Published var coinDetails : CoinDetailModel? = nil 14 | var coinDetailSubscription: AnyCancellable? 15 | let coin:CoinModel 16 | 17 | 18 | init(coin:CoinModel){ 19 | self.coin = coin 20 | getCoinDetails() 21 | } 22 | 23 | 24 | func getCoinDetails(){ 25 | 26 | guard let url = URL(string: "https://api.coingecko.com/api/v3/coins/\(coin.id)?localization=false&tickers=false&market_data=false&community_data=false&developer_data=false&sparkline=false") else {return} 27 | 28 | 29 | coinDetailSubscription = NetworkingManager.download(url: url) 30 | .decode(type: CoinDetailModel.self, decoder: JSONDecoder()) 31 | .receive(on:DispatchQueue.main) 32 | .sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] coins in 33 | guard let self else {return} 34 | self.coinDetails = coins 35 | self.coinDetailSubscription?.cancel() 36 | 37 | }) 38 | 39 | 40 | 41 | } 42 | 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Services/CoinImageService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinImageService.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import Combine 11 | 12 | class CoinImageService { 13 | 14 | @Published var image:UIImage? = nil 15 | 16 | private var imageSubscription: AnyCancellable? 17 | private var coin:CoinModel 18 | private let fileManager = LocalFileManager.shared 19 | private let folderName = "coin_image" 20 | private let imageName:String 21 | 22 | 23 | init(coin:CoinModel){ 24 | self.coin = coin 25 | self.imageName = coin.id 26 | getCoinImage() 27 | } 28 | 29 | private func getCoinImage() { 30 | if let savedImage = fileManager.getImage(imageName: imageName, folderName: folderName){ //캐싱 되어 있으면 바로 꺼내고 31 | image = savedImage 32 | // print("Retrieved image from File Manager") 33 | } else { //아니면 다운 34 | downloadCoinImage() 35 | // print("Downloading image now") 36 | } 37 | } 38 | 39 | private func downloadCoinImage(){ 40 | guard let url = URL(string:coin.image) else {return} 41 | 42 | imageSubscription = NetworkingManager.download(url: url) 43 | .tryMap({ (data) -> UIImage? in 44 | return UIImage(data: data) 45 | }) 46 | .receive(on:DispatchQueue.main) 47 | .sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] image in 48 | 49 | guard let self, let downloadedImage = image else {return} 50 | self.image = downloadedImage 51 | self.imageSubscription?.cancel() 52 | self.fileManager.saveImage(image: downloadedImage, imageName: self.imageName, folderName: self.folderName) 53 | 54 | }) 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Services/MarketDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoinDataService.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/25. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class MarketDataService { 12 | 13 | @Published var marketData: MarketDataModel? = nil 14 | var marketDataSubscription: AnyCancellable? 15 | 16 | 17 | init(){ 18 | getCoins() 19 | } 20 | 21 | 22 | func getCoins(){ 23 | 24 | guard let url = URL(string: "https://api.coingecko.com/api/v3/global") else {return} 25 | 26 | 27 | marketDataSubscription = NetworkingManager.download(url: url) 28 | .decode(type: GlobalData.self, decoder: JSONDecoder()) 29 | .receive(on:DispatchQueue.main) 30 | .sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] globalData in 31 | guard let self else {return} 32 | self.marketData = globalData.data 33 | self.marketDataSubscription?.cancel() 34 | 35 | }) 36 | 37 | 38 | 39 | } 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Services/PortfolioDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PortfolioDataService.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | class PortfolioDataService { 12 | private let container: NSPersistentContainer 13 | private let containerName : String = "PortfolioContainer" // DataModel 파일명과 같게 설정 14 | private let entityName:String = "PortfolioEntity" 15 | 16 | @Published var savedEntities: [PortfolioEntity] = [] 17 | 18 | init(){ 19 | container = NSPersistentContainer(name: containerName) 20 | container.loadPersistentStores(completionHandler: { (_,error) in 21 | 22 | if let error = error { 23 | print("Error loading Core Data! \(error)") 24 | } 25 | }) 26 | 27 | self.getPortfolio() 28 | } 29 | 30 | private func getPortfolio() { 31 | let request = NSFetchRequest(entityName: entityName) 32 | 33 | do { 34 | savedEntities = try container.viewContext.fetch(request) 35 | } catch let error { 36 | print("Error fetching Portfolio Entities. \(error)") 37 | } 38 | 39 | 40 | } 41 | 42 | // MARK: PUBLIC 43 | 44 | 45 | func updatePortfolio(coin:CoinModel, amount:Double){ 46 | 47 | if let entity = savedEntities.first(where: {$0.coinID == coin.id}) { 48 | 49 | if amount > 0 { 50 | update(entity: entity, amount: amount) 51 | } else { 52 | delete(entity: entity) 53 | } 54 | } else { 55 | add(coin: coin, amount: amount) 56 | } 57 | } 58 | 59 | 60 | 61 | // MARK: PRIVATE 62 | 63 | private func add(coin:CoinModel,amount: Double){ 64 | let entity = PortfolioEntity(context: container.viewContext) 65 | 66 | entity.coinID = coin.id 67 | entity.amount = amount 68 | 69 | applyChange() 70 | } 71 | 72 | private func update(entity: PortfolioEntity, amount:Double) { 73 | print("Run update") 74 | entity.amount = amount 75 | applyChange() 76 | } 77 | 78 | private func delete(entity: PortfolioEntity){ 79 | container.viewContext.delete(entity) 80 | applyChange() 81 | } 82 | 83 | private func save(){ 84 | 85 | do { 86 | try container.viewContext.save() 87 | } catch let error { 88 | print("Error saving to Core Data \(error)") 89 | } 90 | 91 | } 92 | 93 | private func applyChange(){ 94 | save() 95 | getPortfolio() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/SwiftuiCryptoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftuiCryptoApp.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct SwiftuiCryptoApp: App { 12 | 13 | @StateObject private var vm = HomeViewModel() 14 | @State private var showLaunchView:Bool = true 15 | 16 | init() { 17 | UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor:UIColor(Color.theme.accent)] 18 | UINavigationBar.appearance().titleTextAttributes = [.foregroundColor:UIColor(Color.theme.accent)] 19 | } 20 | 21 | var body: some Scene { 22 | WindowGroup { 23 | ZStack{ 24 | NavigationStack{ 25 | HomeView() 26 | .toolbar(.hidden) 27 | } 28 | .environmentObject(vm) 29 | 30 | ZStack{ 31 | if showLaunchView { 32 | LaunchView(showLaunchView: $showLaunchView) 33 | .transition(.move(edge: .leading)) 34 | } 35 | } 36 | .zIndex(2.0) 37 | 38 | 39 | } 40 | 41 | 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Utilities/HapticManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HapticManager.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/29. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | class HapticManager { 12 | 13 | static private let generator = UINotificationFeedbackGenerator() 14 | 15 | static func notification(type:UINotificationFeedbackGenerator.FeedbackType){ 16 | generator.notificationOccurred(type) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Utilities/LocalFileManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalFileManager.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class LocalFileManager { 12 | 13 | static let shared = LocalFileManager() 14 | 15 | private init() {} 16 | 17 | 18 | func saveImage(image:UIImage,imageName:String,folderName:String) { 19 | 20 | //create folder 21 | createFolderIfNeeded(folderName: folderName) 22 | 23 | 24 | //get path for image 25 | guard let data = image.pngData(),let url = getURLForImage(imageName: imageName, folderName: folderName) // PNG Data 포맷 26 | else {return} 27 | 28 | // save image to path 29 | do{ 30 | try data.write(to: url) 31 | } catch let error { 32 | print("Error saving image. \(error)") 33 | } 34 | 35 | 36 | } 37 | 38 | func getImage(imageName: String, folderName:String) -> UIImage? { 39 | 40 | guard let url = getURLForImage(imageName: imageName, folderName: folderName), FileManager.default.fileExists(atPath: url.path()) else { 41 | return nil 42 | } 43 | 44 | return UIImage(contentsOfFile: url.path()) 45 | 46 | } 47 | 48 | private func createFolderIfNeeded(folderName:String){ 49 | 50 | guard let url = getURLForFolder(folderName: folderName) else {return} 51 | 52 | if !FileManager.default.fileExists(atPath: url.path()) { //폴더 경로 없으면 53 | do{ 54 | /* 55 | at : 경로 및 폴더명, 위에서 만든 URL 사용 56 | withIntermediateDirectories : “중간 디렉토리들도 만들꺼야?” 이런 의미. 57 | attributes : 파일 접근 권한, 그룹 등등 폴더 속성 정의 58 | 59 | */ 60 | //폴더 생성 61 | try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true,attributes: nil) 62 | 63 | } catch let error { 64 | print("Error creating directory. FolderName: \(folderName). \(error)") 65 | } 66 | 67 | } 68 | 69 | } 70 | 71 | private func getURLForFolder(folderName:String) -> URL? { 72 | //먼저 FileManager인스턴스를 생성해야겠죠? default는 FileManager의 싱글톤 인스턴스를 만들어준답니다. 73 | /* 74 | 저 urls라는 메소드는 요청된 도메인에서 지정된 공통 디렉토리에 대한 URL배열을 리턴해주는 메소드에요. 75 | 76 | 첫번째 파라미터는 검색 경로 디렉토리에요. 들어간 값을 보니, enum인 것 같죠? 77 | 78 | 무슨 값들이 있는지는 여기에 나와있어요. 그리고 두번째는 Domain을 나타내는 파라미터로, 다른 값들은 여기에 나와있어요. 79 | 80 | */ 81 | guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { 82 | return nil 83 | } 84 | 85 | return url.appendingPathComponent(folderName) // return cacheDirectory경로/folderName 86 | } 87 | 88 | private func getURLForImage(imageName:String, folderName:String) -> URL? { 89 | guard let folderURL = getURLForFolder(folderName: folderName) else { 90 | return nil 91 | } 92 | 93 | return folderURL.appendingPathComponent(imageName + ".png") // cacheDirectory경로/folderName/imageName 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /SwiftUI/Summary/SwiftuiCrypto/SwiftuiCrypto/Utilities/NetworkingManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkingManager.swift 3 | // SwiftuiCrypto 4 | // 5 | // Created by yongbeomkwak on 2023/04/28. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class NetworkingManager { 12 | 13 | enum NetworkingError: LocalizedError { 14 | case badURLResponse(url:URL) 15 | case unknown 16 | 17 | var errorDescription: String? { 18 | switch self { 19 | 20 | case .badURLResponse(url: let url): 21 | return "❌ Bad Response from URL: \(url)" 22 | case .unknown: 23 | return "[⚠️] Unknown error occured" 24 | } 25 | } 26 | 27 | } 28 | 29 | 30 | static func download(url:URL) -> AnyPublisher { 31 | return URLSession.shared.dataTaskPublisher(for: url) 32 | .tryMap({ try handleURLResponse(output: $0,url: url) }) 33 | .retry(3) 34 | .eraseToAnyPublisher() 35 | 36 | } 37 | 38 | 39 | static func handleURLResponse(output: URLSession.DataTaskPublisher.Output,url:URL) throws -> Data { 40 | guard let response = output.response as? HTTPURLResponse, response.statusCode >= 200 && 41 | response.statusCode < 300 else { 42 | throw NetworkingError.badURLResponse(url: url) 43 | } 44 | 45 | return output.data 46 | 47 | } 48 | 49 | 50 | static func handleCompletion(completion: Subscribers.Completion) { 51 | 52 | switch completion { 53 | case .finished: 54 | break 55 | case .failure(let error): 56 | print(error.localizedDescription) 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Text/README.md: -------------------------------------------------------------------------------- 1 | # Text 2 | - 텍스트 입력하고 표시할 수 있는 뷰 3 | 4 | ### Modifiers 5 | - modifiers를 통해 뷰의 여러 속성들을 추가 및 변경할 수 있다. 6 | ```swift 7 | Text("Hello, World!") 8 | .font(.title) 9 | ``` 10 | 11 | ### Text 속성 12 | - modifiers를 통해 text의 다양한 속성을 추가할 수 있다. 13 | ```swift 14 | Text("Hello, World!") 15 | .font(.body) // body형식의 텍스트 16 | .fontWeight(.bold) // bold체 17 | .bold() // bold형식의 텍스트 18 | .foregroundColor(.blue) // 색상 변경 19 | .underline() // 텍스트 밑줄 20 | .underline(true, color: Color.red) // 빨간색의 텍스트 밑줄 21 | .strikethrough(true, color: Color.green) // 초록색의 텍스트 취소선 22 | .italic() // italic 형식의 텍스트 23 | ``` 24 | 25 | ### System Text 26 | - .font modifier를 통해 시스템 폰트를 활용할 수 있다. 27 | ```swift 28 | Text("Hello, World!") 29 | .font(.system(size: 24, weight: .semibold, design: .serif)) 30 | // sysetm 폰트의 크기 24, semibold체, serif 디자인 31 | ``` 32 | - 크기 24를 정해두면 dynamic type이 적용되지 않는다. 반면, .font(.body)처럼 기본 font style을 적용하면 dynamic type이 지원된다. 33 | 34 | ### Formatting Text 35 | - 텍스트의 정렬 및 간격을 조정할 수 있다. 36 | ```swift 37 | Text("Hello, World!") 38 | .multilineTextAlignment(.leading) // 왼쪽 정렬 39 | .multilineTextAlignment(.center) // 중앙 정렬 40 | .multilineTextAlignment(.trailing) // 오른쪽 정렬 41 | .baselineOffset(8.0) // 텍스트 baseline 기준 아래쪽 간격 추가 42 | .baselineOffset(-8.0) // 음수일 경우 위쪽 간격 추가 43 | .lineSpacing(8) // 행간 조정 44 | .kerning(8) // 자간 조정 45 | ``` 46 | 47 | ### Frame 48 | - 텍스트의 너비 및 높이를 조정한다. 49 | - 따로 조정해주지 않으면 텍스트 내용에 따라 사이즈가 정해진다. 50 | ```swift 51 | Text("Hello, World!") 52 | .frame(width: 200, height: 100, alignment: .center) // 너비 및 높이 고정, 중앙 정렬 53 | .minimumScaleFactor(0.1) // frame에 텍스트를 모두 입력하기 위해 자동으로 텍스트 크기를 줄일 최소 비율 지정 54 | ``` 55 | 56 | ### Text 형식 57 | - 입력된 text와 상관없이 text의 형식을 변경할 수 있다. 58 | ```swift 59 | Text("Hello, World!".lowercased()) // 소문자로 변경 60 | Text("Hello, World!".uppercased()) // 대문자로 변경 61 | Text("Hello, World!".capitalized()) // 첫번째 문자를 대문자, 나머지 문자를 소문자로 변경 62 | ``` 63 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TextField/README.md: -------------------------------------------------------------------------------- 1 | # TextField & TextEditor 2 | 3 | ## TextField 4 | 5 | ### 특징 6 | - 텍스트를 입력 받을 수 있는 필드 7 | - 1줄 입력만 가능 8 | 9 | ### 종류 10 | 1. 가장 기본적인 셋팅 11 | ```swift 12 | 13 | titleKey: 플레이스 홀더(입력이 없을 때 보여줄 텍스트) 14 | text: 현재 텍스트필드의 입력된 문자들 15 | 16 | TextField(_ titleKey:String,text:Binding) 17 | ``` 18 | 19 | 2. 축과 함께 20 | ```swift 21 | 22 | titleKey: 플레이스 홀더(입력이 없을 때 보여줄 텍스트) 23 | text: 현재 텍스트필드의 입력된 문자들 24 | axis: 텍스트 필드 width를 넘었을 때 쌓일 방향 (기본 .horizontal) 25 | 26 | TextField(_ titleKey:String,text:Binding,axis:Axis) 27 | 28 | 왼쪽.vertical , 오른쪽 .horizontal 29 | 30 | 31 | ``` 32 | 33 |

34 | 스크린샷 2023-05-03 오후 5 29 02 35 |

36 |

37 | 스크린샷 2023-05-03 오후 5 28 34 38 |

39 | 40 | ### 속성 41 | #### 1. .foregroundColor(Color) : 글자색 42 | #### 2. .font : 폰트지정 43 | #### 3. .keyboardType(_ type:UIKeyboardType): 텍스트 필드를 누를 시 나올 키보드 종류 설정 44 | #### 4. .onSubmit : 완료 버튼 누를 때 동작할 함수 45 | #### 5. .textInputAutocapitalization(.never) : 첫 글자를 자동으로 대문자로 설정하는 기능 비활성화 46 | #### 6. .disableAutocorrection(true): 자동 맞춤법 수정 비활성화 47 | #### 7. .textFieldStyle(.roundedBorder): 텍스트 필드 rounded 윤곽선 표출 48 | 49 |

50 | 51 | --- 52 | 53 | ## TextEditor 54 | 55 | ### 특징 56 | - 여러줄의 많은 텍스트를 받을 때 57 | - 내장 스크롤이 있음 58 | 59 | ```swift 60 | TextEditor(text: $text) 61 | .colorMultiply(.red) //텍스트 에디터 색깔을 변경하기위함 62 | .background() ,.forgroundColor 모두 안먹음 63 | .foregroundColor(.orange) //글자 색 변경 64 | /* 글자의 색깔이 바뀌지만 위 65 | .colorMultiply 색상의 영향으로 색깔이 변질될 수 있다. 66 | */ 67 | ``` -------------------------------------------------------------------------------- /SwiftUI/Summary/TextSelection/README.md: -------------------------------------------------------------------------------- 1 | # **#56 TextSelection** 2 | 3 | 4 | ### **`TextSelection`** 5 | 6 | ### 아래의 이미지와 같이 텍스트를 길게 눌렀을 때 Copy, Share 와 같은 기능을 사용할 수 있도록 해주는 유용한 기능입니다 7 |
8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | `아래와 같은 간단한 코드로 적용이 가능합니다.` 17 |
18 | 19 | ```swift 20 | // 21 | // TextSelectionBootcamp.swift 22 | // SwiftUIBootcamp 23 | // 24 | // Created by David Goggins on 2023/05/25. 25 | // 26 | 27 | Text("Hello, World!") 28 | .textSelection(.enabled) //enabled: 활성화 //disable: 비활성화 29 | ``` 30 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/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 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Models/ItemModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemModel.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ItemModel:Identifiable,Codable { 11 | 12 | let id:String 13 | let title: String 14 | let isCompleted: Bool 15 | 16 | init(id: String = UUID().uuidString, title: String, isCompleted: Bool) { 17 | self.id = id 18 | self.title = title 19 | self.isCompleted = isCompleted 20 | } 21 | 22 | 23 | func updateCompletion() -> ItemModel { 24 | return ItemModel(id:id,title: title, isCompleted: !isCompleted) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/TodoListApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoListApp.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TodoListApp: App { 12 | @StateObject var listViewModel = ListViewModel() 13 | var body: some Scene { 14 | 15 | WindowGroup { 16 | NavigationStack{ 17 | ListView() 18 | } 19 | 20 | .environmentObject(listViewModel) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/ViewModels/ListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewModel.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | class ListViewModel:ObservableObject { 11 | 12 | @Published var items: [ItemModel] = [] { 13 | didSet{ 14 | saveItems() 15 | } 16 | } 17 | let itemsKey:String = "items_list" 18 | 19 | 20 | init(){ 21 | getItems() 22 | } 23 | 24 | func getItems(){ 25 | 26 | guard let data = UserDefaults.standard.data(forKey: itemsKey), 27 | let saveItems = try? JSONDecoder().decode([ItemModel].self, from: data) else {return} 28 | 29 | self.items = saveItems 30 | } 31 | 32 | func deleteItem(indexSet: IndexSet) { 33 | items.remove(atOffsets: indexSet) 34 | } 35 | 36 | func moveItem(from:IndexSet,to : Int){ 37 | items.move(fromOffsets: from, toOffset: to) 38 | } 39 | 40 | func addItem(title:String) { 41 | let newItem = ItemModel(title: title, isCompleted: false) 42 | items.append(newItem) 43 | 44 | } 45 | 46 | func updateItem(item:ItemModel) { 47 | 48 | if let index = items.firstIndex(where: {$0.id == item.id}) { 49 | items[index] = item.updateCompletion() 50 | } 51 | } 52 | 53 | func saveItems() { 54 | if let encodeData = try? JSONEncoder().encode(items) { 55 | UserDefaults.standard.set(encodeData, forKey: itemsKey) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Views/AddView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddView.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AddView: View { 11 | 12 | 13 | @EnvironmentObject var listViewModel:ListViewModel 14 | @Environment(\.dismiss) private var dismiss 15 | 16 | @State var text:String = "" 17 | @State var alertTitle:String = "" 18 | @State var showAlert:Bool = false 19 | 20 | var body: some View { 21 | 22 | 23 | ScrollView { 24 | 25 | VStack{ 26 | TextField("Type Something", text: $text) 27 | .padding(.horizontal) 28 | .frame(height: 55) 29 | .background(.gray.opacity(0.5)) 30 | .cornerRadius(15) 31 | 32 | Button { 33 | saveButtonPressed() 34 | } label: { 35 | Text("Save".uppercased()) 36 | .foregroundColor(.white) 37 | .font(.headline) 38 | .frame(height: 55) 39 | .frame(maxWidth: .infinity) 40 | .background(.primary) 41 | .cornerRadius(10) 42 | } 43 | 44 | } 45 | .padding(14) 46 | 47 | 48 | } 49 | .navigationTitle("Add an Item ✏️") 50 | .alert(alertTitle, isPresented: $showAlert) { 51 | Button("OK", role: .cancel) {} 52 | } 53 | 54 | 55 | } 56 | 57 | 58 | func saveButtonPressed() { 59 | if textIsAppropriate() { 60 | listViewModel.addItem(title: text) 61 | dismiss() 62 | } 63 | 64 | 65 | } 66 | 67 | func textIsAppropriate() -> Bool { 68 | if text.count < 3 { 69 | 70 | alertTitle = "최소글자를 어겼습니다. 3글자 이상" 71 | showAlert.toggle() 72 | 73 | return false 74 | } 75 | 76 | return true 77 | } 78 | 79 | 80 | } 81 | 82 | struct AddView_Previews: PreviewProvider { 83 | static var previews: some View { 84 | 85 | NavigationView { 86 | AddView() 87 | } 88 | .environmentObject(ListViewModel()) 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Views/ListRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListRowView.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ListRowView: View { 11 | 12 | var item:ItemModel 13 | 14 | var body: some View { 15 | HStack{ 16 | Image(systemName: item.isCompleted ? "checkmark.circle" : "circle") 17 | .foregroundColor(item.isCompleted ? .green : .red) 18 | Text(item.title) 19 | Spacer() 20 | } 21 | .font(.title2) 22 | .padding(.vertical,8) 23 | } 24 | } 25 | 26 | struct ListRowView_Previews: PreviewProvider { 27 | 28 | static var item1 = ItemModel(title: "First Title", isCompleted: false) 29 | static var item2 = ItemModel(title: "Second Title", isCompleted: true) 30 | 31 | static var previews: some View { 32 | 33 | Group { 34 | ListRowView(item: item1) 35 | ListRowView(item: item2) 36 | } 37 | .previewLayout(.sizeThatFits) 38 | 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Views/ListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListView.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ListView: View { 11 | 12 | @EnvironmentObject var listViewModel: ListViewModel 13 | 14 | var body: some View { 15 | 16 | 17 | ZStack{ 18 | if listViewModel.items.isEmpty { 19 | NoItemsView() 20 | .transition(.opacity.animation(.easeIn)) 21 | 22 | } else { 23 | List { 24 | ForEach(listViewModel.items,id: \.self.id){ item in 25 | ListRowView(item:item) 26 | .onTapGesture { 27 | withAnimation(.linear ) { 28 | listViewModel.updateItem(item: item) 29 | } 30 | } 31 | 32 | } 33 | .onDelete(perform: listViewModel.deleteItem) 34 | .onMove(perform: listViewModel.moveItem) 35 | } 36 | .listStyle(PlainListStyle()) // 밑줄 구분자 있는 스타일 37 | } 38 | 39 | } 40 | 41 | 42 | 43 | .navigationTitle("Todo List ✏️") 44 | .toolbar { 45 | ToolbarItem(placement:.navigationBarLeading) { 46 | EditButton() 47 | } 48 | 49 | ToolbarItem(placement:.navigationBarTrailing) { 50 | NavigationLink("Add", destination: { 51 | AddView() 52 | }) 53 | } 54 | } 55 | } 56 | } 57 | 58 | struct ListView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | NavigationView { 61 | ListView() 62 | } 63 | .environmentObject(ListViewModel()) 64 | } 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /SwiftUI/Summary/TodoList/TodoList/Views/NoItemsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoItemsView.swift 3 | // TodoList 4 | // 5 | // Created by yongbeomkwak on 2023/04/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NoItemsView: View { 11 | 12 | @State var animate: Bool = false 13 | 14 | var body: some View { 15 | ScrollView{ 16 | VStack(spacing:10) { 17 | Text("There are no Items") 18 | .font(.title) 19 | .fontWeight(.semibold) 20 | .on 21 | Text("Are you blababalbblablalablalbalblabl?balbalbalblabalblaadasdfsdaf") 22 | .padding(.bottom,20) 23 | NavigationLink(destination: AddView(), label: { 24 | Text("Add Something..") 25 | .foregroundColor(.white) 26 | .font(.headline) 27 | .frame(height: 55) 28 | .frame(maxWidth: .infinity) 29 | .background( animate ? .red : .blue) 30 | .cornerRadius(10) 31 | }) 32 | .padding(.horizontal, animate ? 30 : 50) 33 | .shadow(color: animate ? .red.opacity(0.7) : .blue.opacity(0.7), radius: animate ? 30 : 10,x: 0,y: animate ? 50 : 30) 34 | .scaleEffect(animate ? 1.1 : 1.0) 35 | .offset(y: animate ? -7 : 0) 36 | 37 | } 38 | .multilineTextAlignment(.center) 39 | .padding(40) 40 | .onAppear(perform: addAnimation) 41 | } 42 | .frame(maxWidth: .infinity,maxHeight: .infinity ) 43 | } 44 | 45 | func addAnimation() { 46 | 47 | guard !animate else {return} 48 | 49 | DispatchQueue.main.asyncAfter(deadline: .now()+1.5, execute: { 50 | withAnimation(.easeInOut(duration: 2.0).repeatForever()) { 51 | animate.toggle() 52 | } 53 | }) 54 | } 55 | 56 | } 57 | 58 | struct NoItemsView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | NavigationView { 61 | NoItemsView() 62 | } 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SwiftUI/Summary/Toggle/README.md: -------------------------------------------------------------------------------- 1 | # Toggle 2 | 3 | ### 특징 4 | 1. 스위치 형태의 끄고 키는 형태 5 | 2. @State Bool 타입으 값을 바인딩 시켜 사용한다. 6 | 3. 스위치 색은 tint로 글자색은 foregroundColor로 변경한다. 7 | 8 | ### 사용 9 | 1. 기본 형태 10 | 11 | ```swift 12 | @State var toggleIsOne:Bool = false 13 | 14 | 15 | var body: some View { 16 | VStack{ 17 | Toggle(isOn: $toggleIsOne) { 18 | Text("DarkMode") 19 | } 20 | } 21 | } 22 | 23 | ``` 24 | 25 | 26 | 2. 색 변경 27 | 28 | ```swift 29 | Toggle(isOn: $toggleIsOne) { 30 | Text("DarkMode") 31 | .foregroundColor(.green) 32 | } 33 | .toggleStyle(.switch) 34 | .tint(.red) 35 | 36 | ``` 37 | 38 | 39 | -------------------------------------------------------------------------------- /SwiftUI/Summary/generateTemplate.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import subprocess 4 | 5 | # subprocess 모듈은 새로운 프로세스를 생성하고, 그들의 입력/출력/에러 파이프에 연결하고, 반환 코드를 얻을 수 있도록 합니다. 이 모듈은 몇 가지 이전 모듈과 함수를 대체하려고 합니다: 6 | ''' 7 | subprocess.run( 8 | args, *, 실행될 명령어와 문자열을 공백 문자 기준으로 나눈 문자열 리스트 (실행될 명령어와 필요한 인자들이 들어있음) 9 | stdin=None, 표준 입력 리다이렉션 10 | input=None, 표준입력을 전달,기본적으로 바이트 스트림이지만 ,encoding 옵션이나 text = True로 전달하면 문자열이 전달됨 11 | stdout=None, 서브 프로세스에서 캡쳐된 표준 출력 내용 12 | stderr=None, 표준 에러 리다이렉션 13 | capture_output=False, 표준출력과 표준에러를 캡쳐한다. 명령어를 실행하고 결과 값을 가져오는 check_output()을 대신 쓸 수 있다. 14 | shell=False, 별도의 서브 쉘을 실행한다. shell = True 일 경우 args는 리스트 형태가아닌 문자열 형태로 쓰는 것이 좋다. 15 | cwd=None, 16 | timeout=None, 17 | check=False, 비정상 종료 시 CalledProcessError를 발생시킬지 여부 18 | encoding=None, 19 | errors=None, 20 | text=None, 21 | env=None, 서브 프로세스의 환경 변수를 정희하는 맵핑 22 | universal_newlines=None, 23 | **other_popen_kwargs) 24 | ''' 25 | 26 | 27 | def make_dir(path): 28 | if not os.path.exists(path): 29 | os.makedirs(path) 30 | 31 | def make_markdown(file_path,codes): 32 | markdown_file_path = f"{file_path}/README.md" 33 | 34 | if not os.path.isfile(markdown_file_path): 35 | subprocess.run(['touch', markdown_file_path]) # filepath 생성하기 36 | 37 | master_key_file = open(markdown_file_path, 'w') 38 | master_key_file.write(codes) 39 | master_key_file.close() 40 | 41 | def make_lecture(lecture_name:str): 42 | make_dir(lecture_name) 43 | make_markdown(lecture_name,"Tmp") 44 | 45 | 46 | 47 | print('Input new lecture name ', end=': ', flush=True) 48 | 49 | lecture_name = sys.stdin.readline().replace("\n", "") 50 | 51 | print(f'Start to generate the new lecutre named {lecture_name}...') 52 | 53 | current_file_path = os.path.dirname(os.path.abspath(__file__)) 54 | 55 | os.chdir(current_file_path) # 디렉토리 변경 56 | #os.chdir(os.pardir) # 부모 디렉토리 설정 57 | 58 | make_lecture(lecture_name=lecture_name) 59 | --------------------------------------------------------------------------------