Skip to content

SwiftUI 实用页面案例

VStack/HStack/ZStack 经典案例:名片小组件

swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack { // 背景层叠
            Color.blue.opacity(0.2) // 背景底色

            VStack(spacing: 16) { // 垂直堆叠
                // 头像和名字
                HStack(spacing: 12) { // 水平堆叠
                    Image(systemName: "person.circle.fill")
                        .resizable()
                        .frame(width: 50, height: 50)
                        .foregroundColor(.blue)

                    VStack(alignment: .leading) {
                        Text("王小明")
                            .font(.headline)
                        Text("iOS 开发工程师")
                            .font(.subheadline)
                            .foregroundColor(.gray)
                    }
                }

                // 底部联系方式
                HStack {
                    Image(systemName: "phone.fill")
                    Text("123-456-7890")
                }
                .font(.footnote)
            }
            .padding()
            .background(Color.black)
            .cornerRadius(12)
            .shadow(radius: 5)
        }
        .frame(height: 200)
    }
}

视图树结构图

ZStack
 ├── Color(蓝色半透明背景)
 └── VStack
       ├── HStack (头像 + 名字 + 职位)
       │     ├── Image(头像)
       │     └── VStack
       │           ├── Text("王小明")
       │           └── Text("iOS 开发工程师")
       └── HStack (联系方式)
             ├── Image(图标)
             └── Text(电话)

落地页

swift
import SwiftUI

@main
struct LandingDemoApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}

struct RootView: View {
    @Environment(\.scenePhase) private var scenePhase
    @State private var showLanding = true
    @State private var lastBackgroundDate: Date? = nil

    var body: some View {
        ZStack {
            if showLanding {
                LandingView()
                    .onAppear {
                        // 每次出现落地页,2 秒后切到主页面
                        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                            withAnimation {
                                showLanding = false
                            }
                        }
                    }
            } else {
                MainView()
            }
        }
        .onChange(of: scenePhase) { newPhase in
            switch newPhase {
            case .background:
                // 进入后台时,记录时间
                lastBackgroundDate = Date()

            case .active:
                // 回到前台时,检查时间差
                if let last = lastBackgroundDate {
                    let diff = Date().timeIntervalSince(last)
                    if diff > 30 { // 超过 30 秒,重新显示落地页
                        showLanding = true
                    }
                }

            default:
                break
            }
        }
    }
}

struct LandingView: View {
    var body: some View {
        ZStack {
            Color.blue.ignoresSafeArea()
            Text("落地页 / 开屏页")
                .font(.largeTitle)
                .foregroundColor(.white)
        }
    }
}

struct MainView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("主页面 🎉")
                    .font(.largeTitle)
                Text("挂后台太久再回来 → 会重新看到落地页")
                    .padding()
            }
            .navigationTitle("首页")
        }
    }
}

广告页

swift
import SwiftUI

@main
struct LandingAdDemoApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}

struct RootView: View {
    @Environment(\.scenePhase) private var scenePhase
    @State private var showLanding = true
    @State private var lastBackgroundDate: Date? = nil

    var body: some View {
        ZStack {
            if showLanding {
                LandingAdView(showLanding: $showLanding)
                    .onAppear {
                        // 自动倒计时 3 秒后进入主页面
                        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                            withAnimation { showLanding = false }
                        }
                    }
            } else {
                MainView()
            }
        }
        .onChange(of: scenePhase) { newPhase in
            switch newPhase {
            case .background:
                lastBackgroundDate = Date()
            case .active:
                if let last = lastBackgroundDate {
                    let diff = Date().timeIntervalSince(last)
                    if diff > 30 { // 超过 30 秒重新显示开屏广告
                        showLanding = true
                    }
                }
            default:
                break
            }
        }
    }
}

// 落地页 / 广告页
struct LandingAdView: View {
    @Binding var showLanding: Bool
    @State private var countdown: Int = 3

    var body: some View {
        ZStack {
            Color.orange.ignoresSafeArea()

            VStack {
                Spacer()
                Text("🔥 开屏广告 / 落地页")
                    .font(.largeTitle)
                    .foregroundColor(.white)
                Spacer()

                Button(action: { showLanding = false }) {
                    Text("跳过 \(countdown)s")
                        .padding()
                        .background(Color.white.opacity(0.7))
                        .cornerRadius(8)
                }
                .padding(.bottom, 40)
            }
        }
        .onAppear {
            // 倒计时
            countdown = 3
            Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
                countdown -= 1
                if countdown <= 0 {
                    timer.invalidate()
                    showLanding = false
                }
            }
        }
    }
}

// 主页面
struct MainView: View {
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Text("主页面 🎉")
                    .font(.largeTitle)
                Text("后台挂久再回来会显示落地页/广告")
                    .padding()
            }
            .navigationTitle("首页")
        }
    }
}

App 第一次启动显示引导页(Onboarding)

核心思路:用 @AppStorage 或 UserDefaults 存一个标记,判断用户是不是第一次启动

用户看完引导页后,把 hasSeenOnboarding = true,下次就直接进主页面

swift
@main
struct MyApp: App {
    @AppStorage("hasSeenOnboarding") var hasSeenOnboarding = false

    var body: some Scene {
        WindowGroup {
            if hasSeenOnboarding {
                MainView() // 主页面
            } else {
                OnboardingView()
            }
        }
    }
}