NavigationStack
import SwiftUI
struct NavigationStackBootcamp: View {
let fruits = ["Apple", "Orange", "Banana"]
///路径数组
@State private var stackPath: [String] = []
var body: some View {
NavigationStack(path: $stackPath) {
VStack{
Button("stackPathButton") {
///一下进去三个view,然后一个一个返回
stackPath.append(contentsOf: [
"xxx ", "yyy", "zzz"
])
}
}
.navigationTitle("这是一个标题")
.navigationDestination(for: Int.self) { item in
MySecondViewScreen(index: item)
}
.navigationDestination(for: String.self) { item in
Text("next view: \(item)")
}
}
Divider()
//NavigationView废弃,改用NavigationStack
NavigationStack() {
ScrollView{
VStack(spacing: 40){
ForEach(fruits, id: \.self) { fruit in
NavigationLink(value: fruit) {
Text(fruit)
}
}
ForEach(0...20, id: \.self) { item in
///使用这种方式,不会将下级view初始化,点击的时候才会创建
NavigationLink(value: item) {
Text("current view: \(item)")
}
//使用这种方法,会将所有的下级view初始化
// NavigationLink {
// MySecondViewScreen(index: item)
// } label: {
// Text("current view: \(item)")
// }
}
}
}
.navigationTitle("这是一个标题")
.navigationDestination(for: Int.self) { item in
MySecondViewScreen(index: item)
}
.navigationDestination(for: String.self) { item in
Text("next view: \(item)")
}
}
}
}
struct MySecondViewScreen: View {
var index: Int
init(index: Int) {
self.index = index
print("Init Screen: \(index)")
}
var body: some View {
Text("next view: \(index)")
}
}
#Preview {
NavigationStackBootcamp()
}
Toolbar
import SwiftUI
struct ToolbarBootcamp: View {
@State private var textFieldString: String = ""
@State private var paths: [String] = []
var body: some View {
NavigationStack(path: $paths) {
ZStack{
//Color.blue.ignoresSafeArea()
Color.white.ignoresSafeArea()
ScrollView{
TextField("Placeholder", text: $textFieldString)
Text("你好~")
.font(.largeTitle)
.foregroundStyle(.white)
ForEach(0..<50) { _ in
Rectangle()
.frame(width: 200, height: 200)
.cornerRadius(10)
.foregroundColor(.blue)
}
}
}
.navigationTitle("导航标题")
//原来的做法
// .navigationBarItems(
// leading: Image(systemName: "heart.fill"),
// trailing: Image(systemName: "heart")
// )
//toolbar的做法
.toolbar(content: {
//左按钮
ToolbarItem(placement: .topBarLeading) {
Image(systemName: "heart.fill")
}
//中间按钮
ToolbarItem(placement: .principal) {
Image(systemName: "gear")
}
//右按钮
ToolbarItem(placement: .topBarTrailing) {
HStack{
Image(systemName: "heart.fill")
Image(systemName: "heart")
}
}
///在键盘上方自定义View
ToolbarItem(placement: .keyboard) {
Image(systemName: "gear")
}
//底部按钮
ToolbarItem(placement: .bottomBar) {
Image(systemName: "gear")
.background(.red)
.frame(maxWidth: .infinity, alignment: .leading)
}
})
//可以控制顶部navigationBar的隐藏与显示
//.toolbar(.hidden, for: ToolbarPlacement.navigationBar)
//控制顶部navigationBar是否隐藏,如果是hidden,则蓝色框直接穿过去
//.toolbarBackground(.hidden, for: ToolbarPlacement.navigationBar)
//控制navigationBar的颜色:dark light
//.toolbarColorScheme(.dark, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
.toolbarTitleMenu {
Button("按钮1"){
paths.append("按钮1")
}
Button("按钮2"){
paths.append("按钮2")
}
}
.navigationDestination(for: String.self) { value in
Text("value: \(value)")
}
}
}
}
#Preview {
ToolbarBootcamp()
}
ResizableSheet
import SwiftUI
struct ResizableSheetBootcamp: View {
@State private var showSheet: Bool = false
@State private var detents: PresentationDetent = .large
var body: some View {
Button("Click Me") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
MyNextView(detents: $detents)
//默认是large,也就是全屏
//medium是一半的效果
.presentationDetents([.large, .medium])
//hidden是隐藏上面的指示器
.presentationDragIndicator(.hidden)
//交互消失不可以,就是,不让消失
// .interactiveDismissDisabled()
//自定义高度,是一个比例值0-1取值
.presentationDetents([.fraction(0.9)])
//按照高度显示
.presentationDetents([.height(100)])
.presentationDetents([.medium, .large, .fraction(0.1)], selection: $detents)
})
}
}
struct MyNextView: View {
@Binding var detents: PresentationDetent
var body: some View {
ZStack{
Color.red.ignoresSafeArea()
VStack{
Text("Hello World~")
Button("Medim"){
//这里面的值,必须在包含在Detents数组里面
detents = .medium
}
Button("Large"){
//这里面的值,必须在包含在Detents数组里面
detents = .large
}
Button("fraction(0.1)"){
//这里面的值,必须在包含在Detents数组里面
detents = .fraction(0.1)
}
}
}
.onDisappear(perform: {
detents = .large
})
}
}
#Preview {
ResizableSheetBootcamp()
}
SafeAreaInset
import SwiftUI
struct SafeAreaInsetBootcamp: View {
var body: some View {
NavigationStack{
List(0..<10) { _ in
Rectangle()
.foregroundColor(.blue)//长方形的背景颜色
.frame(height: 300)
//list的背景颜色
.listRowBackground(Color.green)
}
.navigationTitle("Safe Area Inset")
//其他用法:固定在头部
.safeAreaInset(edge: .top, alignment: .trailing) {
Text("Hi1")
.frame(maxWidth: .infinity)
.background(.yellow)
}
//其他用法:固定在某处
.safeAreaInset(edge: .bottom, alignment: .trailing) {
Text("Hi2")
.padding()
.background(.yellow)
.clipShape(Circle())//剪成圆形
.padding()
}
//方法一:
// .overlay(alignment: .bottom, content: {
// Text("Hi")
// .frame(maxWidth: .infinity)
// .background(.yellow)
// })
//方法二:使用Safe Area Inset
.safeAreaInset(edge: .bottom, alignment: .center) {
Text("Hi3")
.frame(maxWidth: .infinity)
.background(.yellow)
}
}
}
}
#Preview {
SafeAreaInsetBootcamp()
}
Group
import SwiftUI
struct GroupBootcamp: View {
var body: some View {
VStack(spacing: 50){
Text("Hello, World!")
//使用Group,统一处理。而不需要建立Stack
Group{
Text("Hello, World!")
Text("Hello, World!")
}
.font(.subheadline)
.foregroundStyle(.green)
}
.foregroundColor(.red)
.font(.largeTitle)
}
}
#Preview {
GroupBootcamp()
}
AnimationUpdate
import SwiftUI
struct AnimationUpdateBootcamp: View {
@State private var animate1: Bool = false
@State private var animate2: Bool = false
var body: some View {
ZStack{
VStack{
Button("Action 1") {
animate1.toggle()
}
Button("Action 2") {
animate2.toggle()
}
ZStack{
Rectangle()
.foregroundColor(.blue)
.frame(width: 100, height: 100)
.frame(maxWidth: .infinity, alignment: animate1 ? .leading : .trailing)
.background(.green)
.frame(maxHeight: .infinity, alignment: animate2 ? .top : .bottom)
.background(.orange)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.red)
}
}
//value是animate1,则只有animate1才有动画效果
.animation(.spring, value: animate1)
}
}
#Preview {
AnimationUpdateBootcamp()
}
Menu
import SwiftUI
struct MenuBootcamp: View {
var body: some View {
VStack(spacing: 50){
Menu("Click Me") {
Button("1"){
}
Button("2"){
}
Button("3"){
}
Button("4"){
}
}
Menu("Click Me") {
ControlGroup("group"){
Button("1"){
}
Button("2"){
}
Button("3"){
}
}
Button("4"){
}
Menu("Click Me") {
ControlGroup("group2"){
Button("1"){
}
Button("2"){
}
Button("3"){
}
}
}
}
}
}
}
#Preview {
MenuBootcamp()
}
NativePopover
import SwiftUI
struct NativePopoverBootcamp: View {
@State private var showPopover1: Bool = false
@State private var showPopover2: Bool = false
@State private var showPopover3: Bool = false
@State private var showPopover4: Bool = false
@State private var showPopover5: Bool = false
private var array: [String] = [
"123", "456", "789"
]
var body: some View {
ZStack{
Color.green.ignoresSafeArea()
ZStack{
VStack(spacing: 50){
Button("Click Me .sheet") {
showPopover1.toggle()
}
Button("Click Me .fullScreenCover") {
showPopover2.toggle()
}
Button("Click Me .popover") {
showPopover3.toggle()
}
Button("Click Me .popover") {
showPopover4.toggle()
}
Button("Click Me .popover") {
showPopover5.toggle()
}
.padding(.bottom, -100)
}
.background(.red)
.popover(isPresented: $showPopover1, content: {
Text("Hello Next View")
.presentationCompactAdaptation(PresentationAdaptation.sheet)//出现的方式的设置
})
.popover(isPresented: $showPopover2, content: {
Text("Hello Next View")
.presentationCompactAdaptation(PresentationAdaptation.fullScreenCover)
})
.popover(isPresented: $showPopover3, content: {
Text("Hello Next View")
.presentationCompactAdaptation(PresentationAdaptation.popover)
})
.popover(isPresented: $showPopover4, attachmentAnchor: .point(UnitPoint.center), arrowEdge: .top, content: {
ScrollView{
VStack(alignment: .leading, spacing: 12, content: {
ForEach(0..<array.count) { item in
Button(array[item]) {
}
.foregroundStyle(.black)
if item != array.count - 1{
Divider()
}
}
})
.padding(20)
}
.presentationCompactAdaptation(PresentationAdaptation.popover)
})
.popover(isPresented: $showPopover5, attachmentAnchor: .point(UnitPoint.bottom), arrowEdge: .top, content: {
Text("Hello Next View")
.presentationCompactAdaptation(PresentationAdaptation.popover)
})
}
}
}
}
#Preview {
NativePopoverBootcamp()
}
AnyLayout
import SwiftUI
struct AnyLayoutBootcamp: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@Environment(\.verticalSizeClass) private var verticalSizeClass
var body: some View {
VStack(spacing: 12, content: {
Text("HorizontalSizeClass: \(horizontalSizeClass.debugDescription)")
Text("VerticalSizeClass: \(verticalSizeClass.debugDescription)")
})
///如果水平方向比较紧凑,那么就VStack
if horizontalSizeClass == .compact {
VStack{
Text("1111111111")
Text("2222222222")
Text("33333333333")
}
}else{//如果水平方向比较能放下,那么就HStack
HStack{
Text("1111111111")
Text("2222222222")
Text("33333333333")
}
}
//以上借助AnyLayout可以简化:
let layout: AnyLayout = horizontalSizeClass == .compact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout{
Text("1111111111")
Text("2222222222")
Text("33333333333")
}
}
}
#Preview {
AnyLayoutBootcamp()
}
ViewThatFits
import SwiftUI
struct ViewThatFitsBootcamp: View {
var body: some View {
ZStack{
Color.red.ignoresSafeArea()
VStack{
Text("123456789012345678901234567890")
Divider()
Text("123456789012345678901234567890")
.lineLimit(1)//限制1行
.minimumScaleFactor(0.1)//原来的0.1倍
Divider()
//按顺序找:找到最合适(可以显示全)的一个的显示
ViewThatFits{
Text("123456789012345678901234567890")
Text("12345678901234567")
Text("12345678")
}
}
}
.frame(height: 300)
.padding(20)
.font(.largeTitle)
}
}
#Preview {
ViewThatFitsBootcamp()
}
NavigationSplitView
import SwiftUI
///NavigationSplitView主要应用在iPad
struct NavigationSplitViewBootcamp: View {
@State private var visibility: NavigationSplitViewVisibility = .detailOnly
var body: some View {
// NavigationSplitView {
// ZStack{
// Color.red//左边的view
// Text("123")
// }
//
// } detail: {
// ZStack{
// Color.blue//留下的大的view
// Text("456")
// }
//
// }
//
NavigationSplitView {
Color.red//最左边的view
} content: {
Color.green//中间的view(横屏的时候,绿色是显示的)
} detail: {
Color.blue//留下的大的view
}
NavigationSplitView(columnVisibility: $visibility) {
Color.red//最左边的view
} content: {
Color.green//中间的view $visibility可以控制这个 相当于自定义
} detail: {
ZStack(alignment: .topLeading){
Color.blue//留下的大的view
Text("123")
.padding(.leading, 10)
.padding(.top, 15)
}
}
.navigationSplitViewStyle(.prominentDetail)//控制右边有没有黑色图层。其实是在他蓝色上面?还是在蓝色右边
}
}
#Preview {
NavigationSplitViewBootcamp()
}
GridView
import SwiftUI
struct GridViewBootcamp: View {
var body: some View {
ScrollView(){
Grid(){
GridRow {
cell(int: 1)
cell(int: 2)
cell(int: 3)
}
//神奇
// cell(int: 666666666)
//从左到右,顶格
// Divider()
//上面Grid多大,线多宽
Divider()
.frame(height: 10)
.gridCellUnsizedAxes(.horizontal)
.background(.blue)
GridRow {
cell(int: 4)
cell(int: 5)
}
cell(int: 6)
cell(int: 7)
cell(int: 8)
}
.background(.gray)
Grid(){
ForEach(0..<4) { rowIndex in
GridRow{
ForEach(0..<4) { columnIndex in
let cellNumber = (columnIndex + 1) + (rowIndex * 4)
cell(int: cellNumber)
}
}
}
}
.background(.purple)
Grid(alignment: .trailing, horizontalSpacing: 5, verticalSpacing: 20, content: {
ForEach(0..<4) { rowIndex in
GridRow{
ForEach(0..<4) { columnIndex in
let cellNumber = (columnIndex + 1) + (rowIndex * 4)
cell(int: cellNumber)
}
}
}
})
.background(.yellow)
Grid(alignment: .trailing, horizontalSpacing: 5, verticalSpacing: 20, content: {
ForEach(0..<4) { rowIndex in
GridRow{
ForEach(0..<4) { columnIndex in
let cellNumber = (columnIndex + 1) + (rowIndex * 4)
//第7个是空白
if cellNumber == 7 {
Color.clear
.gridCellUnsizedAxes([.horizontal, .vertical])
}else{
cell(int: cellNumber)
}
}
}
}
})
.background(.blue)
Grid(alignment: .center, horizontalSpacing: 5, verticalSpacing: 20, content: {
ForEach(0..<4) { rowIndex in
GridRow{
ForEach(0..<4) { columnIndex in
let cellNumber = (columnIndex + 1) + (rowIndex * 4)
//第7个是空白
if cellNumber == 7 {
EmptyView()
}else{
cell(int: cellNumber)
//做到7是空的,然后6占两列
.gridCellColumns(cellNumber == 6 ? 2 : 1)//一个单元格占几列,默认是1列
.gridColumnAlignment(cellNumber == 6 ? .center : .trailing)
}
}
}
}
})
.background(Color.brown)
}
}
private func cell(int: Int) -> some View {
Text("\(int)")
.font(.largeTitle)
.padding()
.background(.orange)
}
}
#Preview {
GridViewBootcamp()
}
ContentUnavailableView
import SwiftUI
struct ContentUnavailableViewBootcamp: View {
var body: some View {
ContentUnavailableView(
"暂无网络",
systemImage: "wifi.slash",
description: Text("请检查网络,刷新后再试~😄"))
//更简单
ContentUnavailableView.search
}
}
#Preview {
ContentUnavailableViewBootcamp()
}
ObservableBootcamp
iOS之前的做法:
import SwiftUI
private class ObservableViewModel: ObservableObject{
@Published var title: String = "Some title"
}
private struct ObservableBootcamp: View {
@StateObject private var viewModel = ObservableViewModel()
var body: some View {
VStack(spacing: 40){
Button(viewModel.title){
viewModel.title = "new title~"
}
.font(.largeTitle)
SomeChildView(viewModel: viewModel)
SomeThirdChildView()
}
.environmentObject(viewModel)
}
}
private struct SomeChildView: View {
//接收 使用`@ObservedObjec`t,调用的时候要初始化给值
@ObservedObject var viewModel: ObservableViewModel
var body: some View {
Button(viewModel.title){
viewModel.title = "subView title~"
}
.font(.largeTitle)
}
}
private struct SomeThirdChildView: View {
//接收 使用`@EnvironmentObject`t,调用的时候不需要初始化给值,但是需要手动写:`.environmentObject(viewModel)`
@EnvironmentObject var viewModel: ObservableViewModel
var body: some View {
Button(viewModel.title){
viewModel.title = "thirdChildView title~"
}
.font(.largeTitle)
}
}
#Preview {
ObservableBootcamp()
}
iOS17的做法
import SwiftUI
//1
@Observable private class ObservableViewModel{
//2
var title: String = "Some title"
}
private struct ObservableBootcampIOS17: View {
//3
@State private var viewModel = ObservableViewModel()
var body: some View {
VStack(spacing: 40){
Button(viewModel.title){
viewModel.title = "new title~"
}
.font(.largeTitle)
SomeChildView(viewModel: viewModel)
SomeThirdChildView()
}
//4
.environment(viewModel)
}
}
private struct SomeChildView: View {
//接收 使用`@ObservedObjec`t,调用的时候要初始化给值
//6
@Bindable var viewModel: ObservableViewModel
var body: some View {
Button(viewModel.title){
viewModel.title = "subView title~"
}
.font(.largeTitle)
}
}
private struct SomeThirdChildView: View {
//接收 使用`@EnvironmentObject`t,调用的时候不需要初始化给值,但是需要手动写:`.environmentObject(viewModel)`
//5
@Environment(ObservableViewModel.self) var viewModel
var body: some View {
Button(viewModel.title){
viewModel.title = "thirdChildView title~"
}
.font(.largeTitle)
}
}
#Preview {
ObservableBootcampIOS17()
}