SwiftUI

SwiftUI 官方教程读后笔记

2020-03-01  本文已影响0人  ADreamClusive

[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.")
上一篇 下一篇

猜你喜欢

热点阅读