Deep_Dev

 

 

๐ŸŽ Onboarding

 

์ด๋ฒˆ MC3 ํ”„๋กœ์ ํŠธ์—์„œ ์˜จ๋ณด๋”ฉํ™”๋ฉด ๊ตฌํ˜„์„ ๋งก์•˜์–ด์„œ,

์˜›๋‚ ์— ์˜จ๋ณด๋”ฉ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ๊ทธ๋ƒฅ ๋˜๋Š”๋Œ€๋กœ ๋ง‰ ๊ตฌํ˜„์„ ํ•ด์„œ ์ •๋ฆฌ๊ฐ€ ์•ˆ๋˜์—ˆ์ง€๋งŒ

์ด๋ฒˆ์— ๋‹ค์‹œํ•˜๋Š” ๊น€์— ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

 

 

1. ์žฌ์‹œ์ž‘ ์—ฌ๋ถ€๋ฅผ ์ €์žฅํ•  ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ๋ณ€์ˆ˜ + ์ตœ์ดˆ ์‹คํ–‰์‹œ ์˜จ๋ณด๋”ฉ ํ™”๋ฉด ๋„์šฐ๊ฒŒ ํ•˜๋Š” ์ž‘์—…

์•ฑ์ด ์žฌ์‹œ์ž‘ ์—ฌ๋ถ€๋ฅผ ๊ธฐ์–ตํ•˜๊ธฐ ์œ„ํ•ด์„ , ๊ธฐ๊ธฐ์— ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

@AppStorage๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

์ตœ์ดˆ ๊ตฌ๋™์‹œ์—๋งŒ ํ•ด๋‹น ๋ฐ์ดํ„ฐ ๊ฐ’์ด true์ด๋ฏ€๋กœ ์˜จ๋ณด๋”ฉํ™”๋ฉด์ด .fullScreen์œผ๋กœ ๋„์›Œ์ง€๊ณ , ์ดํ›„์— false๋กœ ๋ฐ”๊ฟ”์ฃผ๋ฉด์„œ

์žฌ์‹คํ–‰์‹œ์—๋„ ๋œจ์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

import SwiftUI

struct ContentView: View {
	// ์˜จ๋ณด๋”ฉ ํ™”๋ฉด์„ ์•ฑ ์ตœ์ดˆ ์‹คํ–‰๋•Œ ํ•œ๋ฒˆ๋งŒ ๋„์šฐ๋„๋ก ํ•˜๋Š” ๋กœ์ปฌ ๋ณ€์ˆ˜
	@AppStorage("isFirstOnboarding) var isFirstOnboarding: Bool = true
	
	var body: some View {
		Text("์•ฑ ๋ฉ”์ธํ™”๋ฉด")
			// ์•ฑ ์ตœ์ดˆ ์‹คํ–‰ ์‹œ full Screen์œผ๋กœ ๋„์›Œ์ง
				.fullScreenCover(isPresented: $isFirstOnboarding) {
					OnboardingTabView(isFirstOnboarding: $isFirstOnboarding)
				}
	}
}
 

 

2. ์˜จ๋ณด๋”ฉ ๊ธฐ๋ณธ(๊ณตํ†ต) ํŽ˜์ด์ง€ ๊ตฌ์„ฑํ•˜๊ธฐ 

์˜จ๋ณด๋”ฉ ํ™”๋ฉด์˜ ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋‚˜ํƒ€๋‚ผ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 

๋ณดํ†ต ๋งˆ์ง€๋ง‰ ๋ทฐ๋Š” ์˜จ๋ณด๋”ฉํ™”๋ฉด์„ ๋๋‚ด๋Š” ๋ฒ„ํŠผ์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ํŽ˜์ด์ง€๋“ค์˜ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

import SwiftUI

struct OnboardingCommonView: View {
    let imageName: String
    let title: String
    let subtitle: String
	
    var body: some View {
        VStack {
            Image(systemName: ImageName)
                .font(.system(size: 100))
                .padding()
            Text(title)
                .bold()
                .font(.largeTitle)
                .padding()
            Text(subtitle)
                .font(.title2)
        }
    }
}

 

3. ์˜จ๋ณด๋”ฉ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ

๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์—๋Š” ๋ณดํ†ต ์˜จ๋ณด๋”ฉ์„ ๋งˆ์น˜๋Š” ์™„๋ฃŒ์™€ ๊ฐ™์€ ๋ฒ„ํŠผ์ด ์žˆ๊ณ , ์ด๋ฅผ AppStorage์—์„œ ์ €์žฅํ–ˆ๋˜ Bool ๊ฐ’์„ false๋กœ ๋ณ€๊ฒฝํ•˜๊ฒŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

import SwiftUI

struct OnboardingLastView: View {
    let imageName: String
    let title: String
    let subtitle: String
	
    @Binding var isFirstOnboarding: Bool
	
    var body: some View {
        VStack {
            Image(systemName: imageName)
                .font(.system(size: 100)
                .padding()
            Text(title)
                .font(.largeTitle)
                                .bold()
                .padding()
            Text(subtitle)
                .font(.title2)
				
            Button {
                isFirstOnboarding.toggle()
            } label: {
                Text("์•ฑ ์‹œ์ž‘ํ•˜๊ธฐ")
                    .bold()
            }
        }
    }
}

 

4. ์˜จ๋ณด๋”ฉ ํƒญ๋ทฐ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์œ„์—์„œ ๋งŒ๋“ค์–ด์ค€ ์˜จ๋ณด๋”ฉ ํŽ˜์ด์ฆˆ๋“ค์„ ๋ชจ์•„ ์ฑ…์ฒ˜๋Ÿผ ๋„˜๊ธธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

OnboardingTabView์•ˆ์— TabView๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค๊ณ , tabViewStyle์„ PageTabViewStyle()๋กœ ๋„ฃ์–ด ์ขŒ์šฐ๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

import SwiftUI

struct OnboardingTabView: View {
    @Binding var isFirstLaunching: Bool
    
    var body: some View {
        TabView {
            // ํŽ˜์ด์ง€ 1: ์˜จ๋ณด๋”ฉ ์ฒซ๋ฒˆ์งธ ํŽ˜์ด์ง€ ( ์•ฑ ์†Œ๊ฐœ )
            OnboardingPageView(
                imageName: "person.3.fill",
                title: "Alright",
                subtitle: "๋ชฉ์†Œ๋ฆฌ ํฌ๊ธฐ ์กฐ์ ˆ์„ ๋„์™€๋“œ๋ฆฌ๋Š” ์•ฑ์ด์—์š”."
            )
            
            // ํŽ˜์ด์ง€ 2: ์˜จ๋ณด๋”ฉ ๋‘๋ฒˆ์งธ ํŽ˜์ด์ง€ ( ๊ธฐ๋Šฅ ์†Œ๊ฐœ )
            OnboardingPageView(
                imageName: "note.text.badge.plus",
                title: "์ƒํ™ฉ ์„ ํƒ",
                subtitle: "์ƒํ™ฉ์— ๋งž์ถ”์–ด ๋ชฉ์†Œ๋ฆฌ ํฌ๊ธฐ ์กฐ์ ˆ์„ ๋„์™€๋“œ๋ ค์š”!"
            )
            
            // ํŽ˜์ด์ง€ 3: ์ฝ๊ธฐ ํŽ˜์ด์ง€ ์•ˆ๋‚ด + ์˜จ๋ณด๋”ฉ ์™„๋ฃŒ
            OnboardingLastPageView(
                imageName: "house",
                title: "๋ฐฑ๊ทธ๋ผ์šด๋“œ",
                subtitle: "๋ฐฑ๊ทธ๋ผ์šด๋“œ๋ชจ๋“œ๋กœ ์‹คํ–‰ํ•ด๋ณด์„ธ์š”!",
                isFirstOnboarding: $isFirstOnboarding
            )
        }
        // ์—ฌ๊ธฐ indexDisplayMode์˜ never ํ˜น์€ always๋กœ ๊ธฐ๋ณธ ์ธ๋””์ผ€์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•ด์คŒ
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) 
    }
}

 


>> ๋งŒ์•ฝ์— SKIP ๋ฒ„ํŠผ์ด ์žˆ๋‹ค๋ฉด

์•ฑ ์ตœ์ดˆ ์‹คํ–‰์‹œ, Onboarding์ด fullScreen์œผ๋กœ ๋„์›Œ์ง€๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— OnboardingTabView์•ˆ์—์„œ SKIP ๋ฒ„ํŠผ์„ ๋งŒ๋“ค๊ณ  ๊ทธ์•ˆ์— dismiss๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋์ž…๋‹ˆ๋‹ค.

import SwiftUI

struct OnboardingTabView: View {
    
    @Environment(\.dismiss) private var dismiss
    @Binding var isFirstOnboarding: Bool
    
    private let totalPages = 4
    @State private var selectedPage = 0
    
    var body: some View {
        ZStack {
            Color.sgmGray2
                .ignoresSafeArea()
            
            VStack {
                HStack {
                    Spacer()
                    
                    Button {
                        isFirstOnboarding = false
                        dismiss()
                    } label: {
                        Text("Skip")
                            .font(.Pretendard.Light.size17)
                            .foregroundColor(.sgmGrayA)
                    }
                    .padding(.trailing)
                    .opacity(selectedPage == 3 ? 0 : 1)
                }
                
                TabView(selection: $selectedPage) {
                    ...
                }
                .ignoresSafeArea()
                .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            }
        }
    }
}

 

>> Indicator๋ฅผ ์ปค์Šคํ…€ํ•ด์•ผ ํ•œ๋‹ค๋ฉด

๋งŒ์•ฝ ๊ธฐ๋ณธ์œผ๋กœ ์ƒ๊ธฐ๋Š” indicator๋ฅผ .never๋กœ ์„ค์ •ํ•˜์—ฌ ์‚ฌ์šฉ์„ ํ•˜์ง€ ์•Š๊ณ , ๋”ฐ๋กœ indicator๋ฅผ ๋งŒ๋“ค์–ด ์ปค์Šคํ…€ํ•ด์•ผํ•œ๋‹ค๋ฉด ! 

// ์ด๋ ‡๊ฒŒ ์ „์ฒด ํŽ˜์ด์ง€์™€ ํ˜„์žฌ ์„ ํƒํ•œ ํŽ˜์ด์ง€์˜ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ด์ฃผ๊ณ 
private let totalPages = 4
@State private var selectedPage = 0

// ์ด๋ ‡๊ฒŒ HStack์•ˆ์— ForEach๋กœ Circle()์„ ์ƒ์„ฑํ•ด์ฃผ๊ณ , ์ปฌ๋Ÿฌ๋ฅผ selectedPage์— ๋”ฐ๋ผ
// ๊ตฌ๋ถ„ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. 
// ( ํ˜„์žฌ ์ฝ”๋“œ์—์„œ VStack์ดํ›„ Spacer()๊ฐ€ ์žˆ๋Š” ์ด์œ ๋Š”, ์ˆ˜์ง์œผ๋กœ ๋งจ ์•„๋ž˜์ชฝ์— ์ธ๋””์ผ€์ดํ„ฐ๋ฅผ ์œ„์น˜์‹œํ‚ค๋ ค๊ณ  )
VStack {
    Spacer() 
        HStack(spacing: 8) {
            ForEach(0..<totalPages, id: \.self) { index in
                Circle()
                    .frame(width: 8, height: 8)
                    .foregroundColor(index == selectedPage ? .sgmBlue1 : .sgmBlue1.opacity(0.3))
                    .opacity(selectedPage == 3 ? 0 : 1)
            }
        }
        .padding(.bottom, 40)
}

 

 

๋‚ด๊ฐ€ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜, OnboardingTabView ์ฝ”๋“œ 

๋”๋ณด๊ธฐ
๋”๋ณด๊ธฐ
import SwiftUI

struct AppOnboardingView: View {
    
    @Environment(\.dismiss) private var dismiss
    @Binding var isFirstOnboarding: Bool // ์•ฑ Onboarding
    @Binding var isCompletedOnboarding: Bool // ๋„์›€๋ง Onboarding
    
    private let totalPages = 4
    @State private var selectedPage = 0
    
    var body: some View {
        ZStack {
            Color.sgmGray2
                .ignoresSafeArea()
            
            VStack {
                HStack {
                    Spacer()
                    
                    Button {
                        isFirstOnboarding = false
                        dismiss()
                    } label: {
                        Text("Skip")
                            .font(.Pretendard.Light.size17)
                            .foregroundColor(.sgmGrayA)
                    }
                    .padding(.trailing)
                    .opacity(selectedPage == 3 ? 0 : 1)
                }
                
                TabView(selection: $selectedPage) {
                    OnboardingCommonPageView(
                        title: "Alright",
                        subtitle: "์ฒญ๊ฐ์žฅ์• ์ธ์˜ ๋ชฉ์†Œ๋ฆฌ ํฌ๊ธฐ ์กฐ์ ˆ์„\n๋„์™€๋“œ๋ฆฌ๋Š” ์•ฑ ์˜ฌ๋ผ์ž‡์ด์—์š”." ,
                        imageName: "OnboardingImage1"
                    )
                    .tag(0)
                    
                    OnboardingCommonPageView(
                        title: "์ƒํ™ฉ ์„ ํƒ",
                        subtitle: "๋Œ€ํ™”, ๋ฐœํ‘œ ๋“ฑ ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์— ๋งž์ถ”์–ด\n๋ชฉ์†Œ๋ฆฌ ํฌ๊ธฐ ์กฐ์ ˆ์„ ๋„์™€๋“œ๋ ค์š”!" ,
                        imageName: "OnboardingImage2"
                    )
                    .tag(1)
                    
                    OnboardingCommonPageView(
                        title: "์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ๋ฐฑ",
                        subtitle: "์• ๋‹ˆ๋ฉ”์ด์…˜, ์ƒ‰์ƒ ๋“ฑ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ์„ ํ†ตํ•ด\n๋ชฉ์†Œ๋ฆฌ ํฌ๊ธฐ๋ฅผ ํ•œ ๋ˆˆ์— ํ™•์ธํ•ด์š”!" ,
                        imageName: "OnboardingImage3"
                    )
                    .tag(2)
                    
                    OnboardingLastPageView(
                        title: "๋ฐฑ๊ทธ๋ผ์šด๋“œ",
                        subtitle: "Dynamic Island, Live Activity๋กœ\n๋‹ค๋ฅธ ํ™”๋ฉด์„ ๋ณด๋ฉฐ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ์–ด์š”!" ,
                        imageName: "OnboardingImage4",
                        isFirstOnboarding: $isFirstOnboarding,
                        isCompletedOnboarding: $isCompletedOnboarding
                    )
                    .tag(3)
                }
                .ignoresSafeArea()
                .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            }
            
            // Indicator
            VStack {
                Spacer()
                
                HStack(spacing: 8) {
                    ForEach(0..<totalPages, id: \.self) { index in
                        Circle()
                            .frame(width: 8, height: 8)
                            .foregroundColor(index == selectedPage ? .sgmBlue1 : .sgmBlue1.opacity(0.3))
                            .opacity(selectedPage == 3 ? 0 : 1)
                    }
                }
                .padding(.bottom, 40)
            }
        }
    }
}

 

 

 

'๐ŸŽ iOS > SwiftUI' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[SwiftUI] Invalid frame dimension  (0) 2024.08.19
[SwiftUI] Custom Alert View  (0) 2024.06.16
[SwiftUI] View -> Flip ๊ธฐ๋Šฅ  (1) 2024.06.16
[SwiftUI] SwiftData  (0) 2024.04.22
[SwiftUI] TextEditor BackgroundColor ์ ์šฉ  (0) 2024.04.22