SwiftUI 官方教程读后笔记
[TOC]
官方教程链接
坑
modifier修饰符层级关系不同,并不会报错,这时候实现的效果就会与预期不同
带圆边框的image
// 设置一个带边框/阴影的圆形image
Image("turtlerock250")
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
preview设置代码
preview画布静态模式下只展示swiftUI视图;如果需要展示UIView及其子视图需要使用Live模式
默认preview画布是当前scheme中选中的设备
LandmarkRow(landmark: landmarkData[1])
.previewLayout(PreviewLayout.fixed(width: 300, height: 70)) // 只展示view近似大小的窗口,这个必须在静态预览模式下使用
// 使用group配合 PreviewLayout.fixed(width: 300, height: 70) 可以展示多个cell
Group {
LandmarkRow(landmark: landmarkData[0])
LandmarkRow(landmark: landmarkData[1])
}.previewLayout(PreviewLayout.fixed(width: 300, height: 70))
// 通过foreach + previewDevice展示多个设备preview画布(静态模式下,动态模式只展示一个)
ForEach(["iPhone 11", "iPhone 8", "iPhone 8 Plus"], id: \.self) { deviceName in
LandmarkList()
.previewDevice(PreviewDevice(rawValue: deviceName))
.previewDisplayName(deviceName) // 为preview设置一个名称
}
list 及 foreach遍历
// 注意属性id,可以唯一标识一条内容;
List(landmarkData, id: \.id) { landmark in
LandmarkRow(landmark: landmark)
}
// 或者landmark实现Identifiable协议
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
}
list设置内边距
.listRowInsets(EdgeInsets())
导航栏/导航标题/页面跳转
navigationLink/presentationLink
// NavigationLink
NavigationView {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail()){
LandmarkRow(landmark: landmark)
}
}
.navigationBarTitle(Text("LandMarks"))
}
如果navigationLink不在navigationView下使用,默认显示会被渲染成灰色,这时image修改rendingMode为origin/text修改foregroundColor即可
官方解释:
Text that you pass as the label for a navigation link renders using the environment’s accent color, and images may render as template images. You can modify either behavior to best suit your design.
通过数据绑定决定是否展示一个presentView
.sheet(isPresented: $showingProfile) {
Text("User Profile")
}
为导航栏左右添加view
.navigationBarItems(trailing: profileButton)
数据绑定
State is a value, or a set of values, that can change over time, and that affects a view’s behavior, content, or layout.
Binding: 有时候我们会把一个视图的属性传至子节点中,但是又不能直接的传递给子节点,因为在 Swift 中值的传递形式是值类型传递方式,也就是传递给子节点的是一个拷贝过的值。但是通过 @Binding 修饰器修饰后,属性变成了一个引用类型,传递变成了引用传递,这样父子视图的状态就能关联起来了。
declare a @State property in PageView, and pass a binding to this property down to the PageViewController view. The PageViewController updates the binding to match the visible page.
Remember to use the $ syntax to create a binding to a value that is stored as state.
局部使用
@State
多页面共享
@Binding
全局(App)共享
@Published
@EnvironmentObject
@Environment(.editMode)
绑定数据到一个action使用$符号
@State var showFavoriteOnly = false
// Toggle: UIKit中switch控件的使用
Toggle(isOn: $showFavoriteOnly) {
Text("Favorites only")
}
// 传递 .constant(.xxx) 就是被binding包裹的变量
ProfileEditor(profile: .constant(.xxx))
// 接收
@Binding var profile: Profile
// 使用@Published修饰的变量当改变时,会通知所有使用他的视图更新
final class UserData: ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
}
// app内全局更新的数据使用@EnvironmentObject声明
@EnvironmentObject var userData: UserData
// 传递EnvironmentObject声明的参数使用environmentObject()修饰器
LandmarkList()
.environmentObject(UserData())
// 获取一个当前view环境绑定的变量,并命名为mode
// A linked View property that reads a value from the view's environment.
@Environment(\.editMode) var mode
// 显示编辑按钮
HStack {
Spacer()
EditButton()
}
// The value referenced by the binding.
// 使用wrappedValue读取数据
if self.mode?.wrappedValue == .inactive {
} else {
}
常用控件之Button
Button(action: {
self.userData.landmarks[self.landmarkIndex].isFavorite.toggle()
}) {
if self.userData.landmarks[self.landmarkIndex].isFavorite {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
} else {
Image(systemName: "star")
.foregroundColor(.gray)
}
}
布局之GeometryReader
其他常用布局:VStack/HStack/ZStack
可以利用container的几何size来布局子视图
GeometryReader { geometry in
Path { path in
let width: CGFloat = min(geometry.size.width, geometry.size.height)
}
}
旋转一个角度
let angle: Angle
BadgeSymbol()
.rotationEffect(angle, anchor: .bottom)
关键字之Self
Self表示的当前的Struct,对于static 修饰的变量或方法使用Self调用
使用关键字作为变量名
static let `default` = Self(username: "Jiaozl")
值类型转换
// 将Int类型的index专为Double类型
Double(index)
颜色叠加
// 当path绘制颜色时,只能通过颜色叠加在外部更改颜色,内部需要设置为white,fou
.colorMultiply(self.color)
两个改变颜色的extension
HStack {
HikeBadge(name: "First Hike")
HikeBadge(name: "Earth Day")
.hueRotation(Angle(degrees: 90))
HikeBadge(name: "Tenth Hike")
.grayscale(0.5)
.hueRotation(Angle(degrees: 45))
}
keypath
\操作符, #操作符
keyPath
动画
// 1. 对@State修饰的属性设置动画,只要属性更改,所有用到该属性的位置都会执行动画
withAnimation(.easeInOut(duration: 4)) {
self.showDetail.toggle()
}
// 2. 对单个view设置动画
Image(systemName: "chevron.right.circle")
.animation(.spring())
// 对两类动画的扩展
extension Animation {
static func ripple(index: Int) -> Animation {
Animation.spring(dampingFraction: 0.5)
.speed(0.5)
.delay(0.03 * Double(index))
}
}
extension AnyTransition {
static var moveAndFade: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale
.combined(with: .opacity) // 将动画结合:大小/透明度
// 组合两种动画:进入和出去动画不一样
return .asymmetric(insertion: insertion, removal: removal)
}
}
picker和DatePicker
var dateRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate)!
let max = Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate)!
return min...max
}
...
VStack(alignment: .leading, spacing: 20) {
Text("Seasonal Photo").bold()
Picker("Seasonal Photo", selection: $profile.seasonalPhoto) {
ForEach(Profile.Season.allCases, id: \.self) { season in
Text(season.rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
}
.padding(.top)
VStack(alignment: .leading, spacing: 20) {
Text("Goal Date").bold()
DatePicker("Goal Date", selection: $profile.goalDate, in: dateRange, displayedComponents: .date)
}
.padding(.top)
view的onAppear和onDisappear
ProfileEditor(profile: $draftProfile)
.onAppear {
self.draftProfile = self.userData.profile
}
.onDisappear {
self.userData.profile = self.draftProfile
}
swiftUI结合UIkit使用
// 使用UIViewRepresentable包裹要显示的UIView
// 使用UIViewControllerRepresentable包裹要显示的UIViewController
// coordinator主要用于处理delgate和target--action事件
func makeCoordinator() -> PageControl.Coordinator {
Coordinator(self)
}
// 初始化配置UIView
func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
control.numberOfPages = numberOfPages
control.addTarget(context.coordinator, action: #selector(Coordinator.updateCurrentPage(sender:)), for: .valueChanged)
return control
}
// 更新UIView
func updateUIView(_ uiView: UIPageControl, context: Context) {
uiView.currentPage = currentPage
}
class Coordinator: NSObject {
var control: PageControl
init(_ control: PageControl) {
self.control = control
}
// target--action
@objc func updateCurrentPage(sender: UIPageControl) {
control.currentPage = sender.currentPage
}
}
其他
MapView()
.edgesIgnoringSafeArea(.top) // 忽略安全区域
.frame(height: 300) // 设置frame,如果设置一个height,width会充满父视图
// 使用offset/padding可以设置某个view的偏移位置
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
// 字符串嵌套使用
fatalError("Couldn't find \(filename) in main bundle.")