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 = 電燈開關,當你在外面時,有人打開了您家中的燈。你回到家,你知道有人打開了它們。