Appearance
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()
}
}
}
}