2025๋…„ 04์›” 20์ผ

๐Ÿš€ SwiftUI macOS ์•ฑ์— ์˜คํ”„๋‹ ์Šคํฌ๋ฆฐ ์ถ”๊ฐ€


macOS ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์•ฑ ์‹คํ–‰ ์‹œ ๋ธŒ๋žœ๋“œ ๋กœ๊ณ ๋‚˜ ๊ฐ„๋‹จํ•œ ์ธํŠธ๋กœ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜์€ SwiftUI๋กœ ์˜คํ”„๋‹ ์Šคํฌ๋ฆฐ(์ธํŠธ๋กœ ๋ทฐ)๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , 5์ดˆ ํ›„ ์ž๋™์œผ๋กœ ๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜๋˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


โœจ ์ฃผ์š” ๊ธฐ๋Šฅ ์†Œ๊ฐœ

  • ์•ฑ ์‹คํ–‰ ํ›„ ์˜คํ”„๋‹ ํ™”๋ฉด์„ ๋จผ์ € ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ„๋„ ์กฐ์ž‘ ์—†์ด 5์ดˆ ํ›„ ๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.
  • ์˜คํ”„๋‹ ํ™”๋ฉด์—๋Š” ์ด๋ฏธ์ง€, ํ”„๋กœ๊ทธ๋žจ ์ด๋ฆ„, ๊ฐ„๋‹จํ•œ ์„ค๋ช…์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • ์•ฑ ์ƒ‰์ƒ๊ณผ ํ…์ŠคํŠธ๋Š” ๋‹ค๊ตญ์–ด ๋กœ์ปฌ๋ผ์ด์ง•์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก ์ „์ฒด ์ฝ”๋“œ ๊ตฌ์„ฑ ์š”์•ฝ
ContentView.swift

struct ContentView: View {
    @State private var showOpeningImage = true

    var body: some View {
        Group {
            if showOpeningImage {
                OpeningView()
                    .transition(.opacity)
                    .onAppear {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                            withAnimation {
                                showOpeningImage = false
                            }
                        }
                    }
            } else {
                MainView() // ์‹ค์ œ ์•ฑ ๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜
            }
        }
    }
}

@State ๋ณ€์ˆ˜์™€ DispatchQueue๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿ–ผ ์˜คํ”„๋‹ ํ™”๋ฉด UI (OpeningView)

struct OpeningView: View {
    var body: some View {
        VStack {
            HStack {
                Image("open_image")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 500, height: 500)
                    .clipShape(Circle())
                    .overlay(Circle().stroke(Color.black, lineWidth: 4))
                    .shadow(radius: 10)

                VStack {
                    Text(NSLocalizedString("OPEN_TEXT", comment: "์•ฑ ์ด๋ฆ„"))
                        .font(.title2)
                        .frame(maxWidth: 300)
                        .padding()

                    Text(NSLocalizedString("OPEN_CONTENT", comment: "์•ฑ ์„ค๋ช…"))
                        .font(.subheadline)
                        .multilineTextAlignment(.center)
                        .frame(maxWidth: 300)
                        .padding()

                    Text(NSLocalizedString("OPEN_VERSION", comment: "๋ฒ„์ „/์ €์ž‘๊ถŒ"))
                        .font(.footnote)
                        .multilineTextAlignment(.center)
                        .frame(maxWidth: 300)
                        .padding()
                }
            }
            .padding()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(hex: NSLocalizedString("COLOR_POP", comment: "๋ฐฐ๊ฒฝ์ƒ‰")))
        .edgesIgnoringSafeArea(.all)
    }
}

๐ŸŒ ๋‹ค๊ตญ์–ด ์ง€์›์„ ์œ„ํ•œ ์„ค์ • (Localizable.strings)

// ํ•œ๊ตญ์–ด ๋ฒ„์ „
"OPEN_TEXT" = "๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋ถ„์„๊ธฐ";
"OPEN_CONTENT" = "์‹œ์Šคํ…œ ํฌ๋ Œ์‹ ๋ฐ ๋ณด์•ˆ ์ง„๋‹จ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.";
"OPEN_VERSION" = "ยฉ 2025. Rho JeongSeok. All rights reserved.";
"COLOR_POP" = "#E6F0FA";
  • ๋ฒˆ์—ญ ํŒŒ์ผ๋งŒ ๋ฐ”๊พธ๋ฉด ๋‹ค์–‘ํ•œ ์–ธ์–ด์— ๋Œ€์‘ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ์•ฑ ์ด๋ฆ„, ์†Œ๊ฐœ ๋ฌธ๊ตฌ, ์ƒ‰์ƒ๊นŒ์ง€ ๋ชจ๋‘ ์™ธ๋ถ€์—์„œ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด ์œ ์ง€ ๋ณด์ˆ˜๋„ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽจ Hex ์ƒ‰์ƒ ์‚ฌ์šฉํ•˜๊ธฐ
SwiftUI๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Hex ์ƒ‰์ƒ ์ฝ”๋“œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์€ ํ™•์žฅ์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค:

extension Color {
    init(hex: String) {
        let scanner = Scanner(string: hex)
        _ = scanner.scanString("#")
        var rgb: UInt64 = 0
        scanner.scanHexInt64(&rgb)

        let r = Double((rgb >> 16) & 0xFF) / 255
        let g = Double((rgb >> 8) & 0xFF) / 255
        let b = Double(rgb & 0xFF) / 255

        self.init(red: r, green: g, blue: b)
    }
}

โœ… ๊ฐœ๋ฐœ ํŒ

  • ์˜คํ”„๋‹ ํ™”๋ฉด์€ ์•ฑ์˜ ์ฒซ์ธ์ƒ์ž…๋‹ˆ๋‹ค. ๋ธŒ๋žœ๋“œ ์ด๋ฏธ์ง€๋‚˜ ํšŒ์‚ฌ ๋กœ๊ณ ๋ฅผ ๋‹ด๋Š” ๋ฐ ์ ๊ทน์ ์œผ๋กœ ํ™œ์šฉํ•˜์„ธ์š”.
  • onAppear + asyncAfter ์กฐํ•ฉ์€ ๊ฐ„๋‹จํ•œ Splash๋‚˜ Intro ํ™”๋ฉด ๊ตฌ์„ฑ์— ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์—๊ฒŒ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์‹ถ์„ ๋• Text๋ฅผ ๋™์ ์œผ๋กœ ๋ฐ”๊ฟ”๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.