Skip to content

SwiftUI์˜ Property Wrapper๋“ค

Sue Cho edited this page Dec 20, 2020 · 4 revisions

Property Wrapper

  • Swift 5.1 ๋ถ€ํ„ฐ ์†Œ๊ฐœ ๋œ Property Wrapper์ด์ง€๋งŒ SwiftUI์—์„œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋จ
  • Property wrapper๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ SwiftUI์—์„œ๋Š” ๋ถˆํ•„์š”
  • SwiftUI๋Š” ์ด 17๊ฐœ์˜ Property Wrapper๋“ค์„ ์ œ๊ณต
Property Wrapper ๊ธฐ๋Šฅ ๋ฐ์ดํ„ฐ์˜ ์†Œ์œ ์ฃผ
@AppStorage UserDefault๋กœ๋ถ€ํ„ฐ ๊ฐ’์„ ์ฝ๊ณ  ์”€. X
@State ํ•˜๋‚˜์˜ view(๊ตฌ์กฐ์ฒด) ๋‚ด์—์„œ ์ž‘์€ ๋‹จ์œ„์˜ value type ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •. O
@Binding ๋‹ค๋ฅธ view๊ฐ€ ์†Œ์œ ํ•˜๊ณ  ์žˆ๋Š” value type์„ ๊ฐ€๋ฅดํ‚ค๋ฉฐ ํ•ด๋‹น ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ์™ธ๋ถ€์˜ ๊ฐ’๋„ ๋ฐ”๋€œ. X
@FocusedBinding key window์— ์žˆ๋Š” ๊ฐ’ ๋ณ€ํ™”๋ฅผ ๊ด€์ฐฐ(ex. ํ˜„์žฌ ์„ ํƒ ๋œ text field). X
@FocusedValue FocusedBinding์˜ ๊ฐ„๋‹จํ•œ ํ˜•ํƒœ. X
@StateObject ObservableObject protocol์„ ๋”ฐ๋ฅด๋Š” Reference type์˜ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ €์žฅ. O
@ObservedObject ObservableObject protocol์„ ๋”ฐ๋ฅด๋Š” Reference type์˜ ์™ธ๋ถ€ ํด๋ž˜์Šค์— ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฅดํ‚ด. X
@Environment System์œผ๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ(color scheme, accessibility option ๋“ฑ)๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ. ์ง์ ‘ ์ถ”๊ฐ€๋„ ๊ฐ€๋Šฅ X
@EnvironmentObject Environment์— ์ €์žฅํ•œ shared object๋ฅผ ์ฝ์–ด์˜ด. X
@Published ObservableObject ๋‚ด์˜ property์— ๋ถ™์œผ๋ฉฐ ํ•ด๋‹น ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ์ด property๋ฅผ ์“ฐ๋Š” view๋ฅผ ์ƒˆ๋กœ ๊ทธ๋ฆผ. O
@FetchRequest CoreData์˜ ํŠน์ • entity์— ๋Œ€ํ•œ fetch request๋ฅผ ์‹œ์ž‘ O
@GestureState ์ง€๊ธˆ ์ง„ํ–‰๋˜๊ณ  ์žˆ๋Š” gesture์™€ ๊ด€๋ จ๋œ ๊ฐ’์„ ์ €์žฅ(ex. ์–ผ๋งˆ๋‚˜ swipe ํ–ˆ๋Š”๊ฐ€) O
@Namespace creates an animation namespace to allow matched geometry effects O
@UIApplicationDelegateAdaptor class๋ฅผ iOS app์—์„œ app delegate์œผ๋กœ ์ƒ์„ฑ ๋ฐ ๋“ฑ๋ก O
@NSApplicationDelegateAdaptor class๋ฅผ macOS app์—์„œ app delegate์œผ๋กœ ์ƒ์„ฑ ๋ฐ ๋“ฑ๋ก O
@ScaledMetric reads the userโ€™s Dynamic Type setting and scales numbers up or down based on an original value you provide O
@SceneStorage ์ƒํƒœ ๋ณต๊ตฌ๋ฅผ ์œ„ํ•ด ์ ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ&๋ณต๊ตฌ O

@UIApplicationDelegateAdaptor

  • ์ตœ์‹  SwiftUI๋Š” AppDelegate์„ ์ƒ์„ฑํ•ด์ฃผ์ง€ ์•Š์ง€๋งŒ AppDelegate๋“ค์˜ ๋ฉ”์†Œ๋“œ๋“ค์ด ํ•„์š”ํ•  ๋•Œ๊ฐ€ ์žˆ์Œ
  • miniVIBE์—์„œ๋Š” didFinishLaunchingWithOptions ๋ฅผ ํ†ตํ•ด launch ๋œ ์‹œ์ ์„ ์žก์„ ํ•„์š”๊ฐ€ ์žˆ์–ด์„œ ์‚ฌ์šฉ
  • AppDelegate ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ  @main ์•ˆ์—์„œ ํ•ด๋‹น property wrapper๋ฅผ ์จ์„œ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
class AppDelegate: NSObject, UIApplicationDelegate {
   func application(_ application: UIApplication,
                    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
      // action
      return true
   }
}

@main
struct OurApp: App {
   @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
   ...
}

@State

  • ๊ตฌ์กฐ์ฒด๋Š” value type์ด๋ผ์„œ ๋‚ด๋ถ€ property๋“ค์˜ ๊ฐ’ ๋ณ€๊ฒฝ์ด (์›์น™์ ์œผ๋กœ๋Š”) ์•ˆ๋จ
  • @State๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๊ทธ ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ณณ์ด ๊ตฌ์กฐ์ฒด ๋‚ด๋ถ€๊ฐ€ ์•„๋‹Œ SwiftUI๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” shared storage๋กœ ๋ฐ”๋€œ. ๋”ฐ๋ผ์„œ state ๊ฐ’์— ๋”ฐ๋ผ ์–ธ์ œ๋“ ์ง€ SwiftUI๊ฐ€ ์ด ๊ตฌ์กฐ์ฒด๋ฅผ ์—†์• ๊ณ  ์žฌ์ƒ์„ฑ ๊ฐ€๋Šฅ. (๋นˆ๋ฒˆํžˆ ์ผ์–ด๋‚จ)
  • ๋‹ค๋ฅธ view ๋“ค๊ณผ ๊ณต์œ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์›์น™. (Apple์€ private์œผ๋กœ ์ƒ์„ฑํ•  ๊ฒƒ์„ ๊ถŒ์žฅ)
  • miniVIBE ์—์„œ๋Š” Player์—์„œ ๋ฉ”๋‰ด์ฐฝ์ด ์—ด๋ ค์žˆ๋Š”์ง€ ํŒ๋ณ„ํ•˜๊ณ  ์กฐ์ ˆ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
struct PlayerView: View {
   @State private var isMenuOpen: Bool = false
   ...
   var body: some View {
      ZStack {
      // ๋‹ค์–‘ํ•œ component
      }
      .fullScreenCover(isMenuOpen: $isLyricsOpen) {
         // ๋ณด์—ฌ์งˆ content(ex. MenuView)
      }
   }
}

@Binding

  • ๊ฐ’์˜ source๋Š” ์™ธ๋ถ€์— ์žˆ์œผ๋ฉฐ ๋‘ ๊ตฐ๋ฐ ๋ชจ๋‘์—์„œ ๊ฐ’์„ ๊ณต์œ ํ•ด์•ผํ•  ๋•Œ ์‚ฌ์šฉ(@State์˜ ๊ฐ’์„ ๋„˜๊ฒจ๋ฐ›๋Š” ๋“ฑ)
  • miniVIBE์—์„œ๋Š” Player์—์„œ ๊ฐ€์‚ฌ์ฐฝ์ด ์—ด๋ ค์žˆ๋Š”์ง€๋ฅผ ํ•˜์œ„ view์—์„œ ๊ฐ’์„ ์กฐ์ ˆ
struct PlayerThumbnail: View {
   @Binding var isLyricsOpen: Bool
   ...
   var body: some View {
      ...
      Text("Open")
         .onTapGesture {
            isOpenLyrics = true
         }
   }
}

@Environment

  • ์‚ฌ์ „์— ์ •์˜๋œ key ๊ฐ’๋“ค๋กœ ์ž‘๋™
  • miniVIBE์—์„œ๋Š” modal ์ฐฝ์„ ๋‹ซ๊ธฐ ์œ„ํ•ด ์‚ฌ์ „์— ์ •์˜๋œ presentationMode ์‚ฌ์šฉ
struct PlayerMenu: View {
   @Environment(\.presentationMode) var presentationMode
   ...
   var body: some View {
      Button {
         presentationMode.wrappedValue.dismiss()
      }, label: { Image(systemName: "xmark") }
   }
}

@EnvironmentObject

  • ์ฃผ๋กœ SwiftUI app ์ „๋ฐ˜์—์„œ ์“ฐ์ผ ๊ณต์œ  ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•ด ์“ฐ์ด๋Š” property wrapper
  • ObservableObject๋ฅผ ๋”ฐ๋ผ์•ผ ํ•จ
  • EnvironmentObject๋ฅผ ๊ฐ€์ง€๋Š” view๊ฐ€ ์ƒ์„ฑ์˜ ์ฑ…์ž„์„ ์ง€์ง€ ์•Š๊ณ  SwiftUI์˜ Environment์— ์˜ํ•ด ์ œ๊ณต
  • A, B, C, D view๊ฐ€ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋ฅผ ํ˜•์„ฑํ•˜๊ณ  ์žˆ๊ณ  A์—์„œ ์ƒ์„ฑ๋œ observableํ•œ ๊ฐ์ฒด๋ฅผ D๊ฐ€ ํ•„์š”๋กœ ํ• ๋•Œ ์ค‘๊ฐ„ B, C๊ฐ€ ๋„˜๊ฒจ์ฃผ์ง€ ์•Š์•„๋„ ๋จ
    • A๊ฐ€ ์ƒ์„ฑํ•ด์„œ environment์— ๋„ฃ์œผ๋ฉด hierarchy์— ์žˆ๋Š” B, C, D ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅ
  • miniVIBE์—์„œ๋Š” ํ˜„์žฌ ์žฌ์ƒ๊ณผ ๊ด€๋ จ๋œ class๋Š” ๋ชจ๋“  ๋ทฐ์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋ฏ€๋กœ App(์ตœ์ƒ๋‹จ)์—์„œ ์ƒ์„ฑ ํ›„ environment์— ๋„ฃ์Œ
class NowPlaying: ObservableObject {
   ...
}

@main
struct MiniVibeApp: App {
   let nowPlaying = NowPlaying()
   ...
   var body: some Scene {
      WindowGroup {
         MainTab()
            .environmentObject(nowPlaying)
      }
   }
}

@ObservedObject

  • ํ˜„์žฌ View ์™ธ๋ถ€์˜ data๋ฅผ ๊ด€์ฐฐํ•˜๊ณ ์ž ํ•  ๋•Œ ์‚ฌ์šฉ
  • ์‹ค์ˆ˜๋กœ ์ €์žฅํ•˜๊ณ  ์žˆ๋˜ object๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํ•ด์ œ์‹œํ‚ฌ ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊น€

@StateObject

  • View ๋‚ด์—์„œ reference type์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฌด์กฐ๊ฑด ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ์–ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉ

@Published

  • ObservableObject ๋‚ด์˜ property ์•ž์— ๋ถ™์ด๋ฉด ํ•ด๋‹น property์˜ ๊ฐ’์ด ๋ฐ”๋€Œ์—ˆ์Œ์„ ์•Œ๋ฆฌ๊ณ  ํ•ด๋‹น ๊ฐ’์„ ์“ฐ๋Š” view๋Š” ๋ฐ”๋€ ๊ฐ’์— ๋งž๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๊ฒŒ ํ•ด์คŒ
  • miniVIBE์—์„œ๋Š” View-ViewModel ๋ฐ”์ธ๋”ฉ์„ @Published๋กœ ํ•จ
Clone this wiki locally