SwiftUI中的一切都是视图。spring
文章来源:Building Custom Views with SwiftUIswift
更多SwiftUI文章:安全
手把手教你用SwiftUI写程序markdown
struct ContentView: View {
var body: some View {
Text("Hello, world!")
// .edgesIgnoringSafeArea(.all)
}
}
复制代码
这段代码包含三个视图:app
视图等级底部的文本(图中Hello World)ide
内容视图(和文本的布局一致,即图中Hello World四周白线内)函数
根视图(屏幕Size - 安全区域)oop
若是想将根视图扩展到安全区,可使用edgesIgnoringSafeArea(.all)
修饰器布局
固然,文本和文本的内容视图,咱们一般当作同一个来操做post
在SwiftUI中,不能给子视图强制规定一个尺寸,而是应该有父视图决定
例一:查看一段代码的布局:
var body: some View {
Text("Avocado Toast")
.padding(10)
.background(Color.green)
}
复制代码
在设置background
、padding
修饰器时,会在Text视图和根视图中间插入对应的背景视图和边距视图
例二:图片的原尺寸为20x20,咱们但愿1.5倍尺寸展现图片
struct ContentView: View {
var body: some View {
Image("20x20_avoado")
}
}
复制代码
作法:
.frame(width: 30, height: 30)
复制代码
效果:图片尺寸不会发生变化,可是在图片周围会插入一个30x30尺寸的Frame视图
在SwiftUI中frame并非一个重要的布局元素,它其实只是一个View。
例三:
// 子视图必须平等竞争一个空间
HStack {
Text("Delicious")
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1)
复制代码
设置文字底基线对齐
设置图片的底基线
例四:让不一样容器中的视图对齐
自定义对齐方式
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
// 告诉SwiftUI如何计算默认值
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
复制代码
设置文字的基线
SwiftUI默认提供了多种样式的图形,好比圆形,胶囊和椭圆
实现渐变色
角渐变色
使用角渐变色填充圆
使用渐变色填充圆环
实现复杂图形绘制
完整代码可参见:官方Demo
整体步骤主要包括:
class Ring: ObservableObject {
/// A single wedge within a chart ring.
struct Wedge: Equatable {
/// 弧度值(全部楔形的弧度值之合最大为2π,即360°)
var width: Double
/// 横轴深度比例 [0,1]. (用来计算楔形的长度)
var depth: Double
/// 颜色值
var hue: Double
}
}
复制代码
struct WedgeShape: Shape {
func path(in rect: CGRect) -> Path {
// WedgeGeometry是用来计算绘制信息的类,详细代码见Demo。
let points = WedgeGeometry(wedge, in: rect)
var path = Path()
path.addArc(center: points.center, radius: points.innerRadius,
startAngle: .radians(wedge.start), endAngle: .radians(wedge.end),
clockwise: false)
path.addLine(to: points[.bottomTrailing])
path.addArc(center: points.center, radius: points.outerRadius,
startAngle: .radians(wedge.end), endAngle: .radians(wedge.start),
clockwise: true)
path.closeSubpath()
return path
}
// ···
}
复制代码
let wedges = ZStack {
ForEach(ring.wedgeIDs, id: \.self) { wedgeID in
WedgeView(wedge: self.ring.wedges[wedgeID]!)
// use a custom transition for insertions and deletions.
.transition(.scaleAndFade)
// remove wedges when they're tapped.
.onTapGesture {
withAnimation(.spring()) {
self.ring.removeWedge(id: wedgeID)
}
}
}
// 若是不加这个Spacer(),会使Mac程序,在没添加任何楔形时,APP尺寸为0。
Spacer()
}
复制代码
为了更好的理解这个工程,你还须要一些知识:
Animatable
自定义复杂的动画假如咱们想实现一个简单的SwiftUI动画,好比点击Button按钮渐变消失,咱们能够这样来实现:
@State private var hidden = false
var body: some View {
Button("Tap Me") {
self.hidden = true
}
.opacity(hidden ? 0 : 1)
.animation(.easeInOut(duration: 2))
}
复制代码
在这个例子中,咱们使用@State
修饰变量hidden
,当hidden
的值发生变化时,SwiftUI会自动为咱们处理渐变更画。而SwiftUI可以为咱们自动执行动画的前提是:SwiftUI已经知道要若是展现该动画效果,那什么状况下,SwiftUI不知道要如何展现动画呢?
好比:咱们经过下面代码绘制出了多角形。
Shape(sides: 3)
复制代码
该方法支持传入不一样的值,生成不一样的多边形。若是咱们但愿从三边形变成四边型,那么能够写以下代码:
Shape(sides: isSquare ? 4 : 3)
.stroke(Color.blue, lineWidth: 3)
.animation(.easeInOut(duration: duration))
复制代码
可是运行代码后发现,这段代码并没有动画过渡效果。这是由于在执行动画的过程当中,SwiftUI会从起始状态到终止状态分红不一样的阶段来绘制,像opacity
从0-1,可能会分红0,0.1,0.2,0.3,···,0.9,1.0,SwiftUI依次进行绘制,从而展现过渡状态。
同理,从三角形变到四边形,SwiftUI也须要绘制中间状态,但SwiftUI并不知道该如何绘制3.5边形。这时就须要咱们本身来告诉SwiftUI该如何绘制了。
animatableData
是Animatable
协议中,惟一须要实现的方法,经过这个方法来告诉SwiftUI须要监听哪些属性的变化。
// 表明须要监听的值为Float类型
var animatableData: Float {
get { //··· }
set { //··· }
}
复制代码
然而并非全部类型的属性都可以被SwiftUI所监听,只有遵循VectorArithmetic
协议的对象AnimatablePair
, CGFloat
, Double
, EmptyAnimatableData
and Float
才能被SwiftUI监听。
要实现Demo中楔形图片变换的效果,须要监听的值有start
,end
,depth
和hue
这四个值。animatableData
属性的返回值也应该包含这四个值。
extension Ring.Wedge: Animatable {
typealias AnimatableData = AnimatablePair<AnimatablePair<Double, Double>, AnimatablePair<Double, Double>>
var animatableData: AnimatableData {
get {
.init(.init(start, end), .init(depth, hue))
}
set {
start = newValue.first.first
end = newValue.first.second
depth = newValue.second.first
hue = newValue.second.second
}
}
}
复制代码
接下来在这四个属性发生变化时,SwiftUI会经过函数func path(in rect: CGRect) -> Path {}
从新绘制,这样就能够展现动画的过渡效果了
使用drawingGroup()
提升复杂UI的渲染效率
以往每建立一个楔形,都是一个单独的View,当楔形数量很是多时,再加上每一个View都在执行动画,很是耗费性能。在SwiftUI中,能够经过drawingGroup()
将相同类型的View经过Metal绘制在一张画布上,从而减小渲染耗费的性能,避免卡顿。
使用Equatable
防止视图的新值和旧值相同时,更新子视图
struct Wedge: Equatable {
// ···
}
复制代码
使用PassthroughSubject
通知SwiftUI值发生变化
PassthroughSubject
PassthroughSubject
通知绑定的属性的视图,属性发生变化,须要从新绘制。let objectWillChange = PassthroughSubject<Void, Never>()
private(set) var wedgeIDs = [Int]() {
willSet {
objectWillChange.send()
}
}
复制代码
contentView
中监听了Ring
模型@EnvironmentObject var ring: Ring
复制代码
所以在Ring模型的wedgeIDs
发生变化时,会发出通知告知contentView
使用其绘制的地方,须要从新绘制
CurrentValueSubject
咱们经常使用的@Published
属性包装器,实际上就是一种CurrentValueSubject
简单来讲:PassthroughSubject
用于表示事件。CurrentValueSubject
用于表示状态。用现实世界的案例进行类比。
PassthroughSubject = 门铃按钮,当有人按门时,只有在你在家时才会收到通知。CurrentValueSubject = 电灯开关,当你在外面时,有人打开了您家中的灯。你回到家,你知道有人打开了它们。