今年圣诞节又来了。
去年,我使用Core Animation创建了一棵圣诞树。
https://qiita.com/shiz/items/10cb712a26620f2e3bdc
所以
今年,我想使用Swift UI创建一棵圣诞树。
我在SwiftUI中使用了很多元素,但是
我要注意
创建适合形状的树形部分
首先,我们将为树制作必要的部分。
将形状定义为符合Shape协议的结构
合并它们以构建视图。
星
我提到了以下站点。
https://www.hackingwithswift.com/quick-start/swiftui/how-to-draw-polygons-and-stars
在
可以使用类似于UIBezierPath的方式来设置路径规范。
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | struct Star: Shape { let corners: Int let smoothness: CGFloat func path(in rect: CGRect) -> Path { guard corners >= 2 else { return Path() } let center = CGPoint(x: rect.width / 2, y: rect.height / 2) var currentAngle = -CGFloat.pi / 2 let angleAdjustment = .pi * 2 / CGFloat(corners * 2) let innerX = center.x * smoothness let innerY = center.y * smoothness var path = Path() path.move(to: CGPoint(x: center.x * cos(currentAngle), y: center.y * sin(currentAngle))) var bottomEdge: CGFloat = 0 for corner in 0..<corners * 2 { let sinAngle = sin(currentAngle) let cosAngle = cos(currentAngle) let bottom: CGFloat if corner.isMultiple(of: 2) { bottom = center.y * sinAngle path.addLine(to: CGPoint(x: center.x * cosAngle, y: bottom)) } else { bottom = innerY * sinAngle path.addLine(to: CGPoint(x: innerX * cosAngle, y: bottom)) } if bottom > bottomEdge { bottomEdge = bottom } currentAngle += angleAdjustment } let unusedSpace = (rect.height / 2 - bottomEdge) / 2 let transform = CGAffineTransform(translationX: center.x, y: center.y + unusedSpace) return path.applying(transform) } } |
详细信息>
星期四
接下来是木制部分。
树形部分
首先,在绿色部分中定义三角形Triangle。
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct Triangle: Shape { func path(in rect: CGRect) -> Path { Path { path in let middle = rect.midX let width: CGFloat = rect.size.width let height = rect.height path.move(to: CGPoint(x: middle, y: 0)) path.addLine(to: CGPoint(x: middle + (width / 2), y: height)) path.addLine(to: CGPoint(x: middle - (width / 2), y: height)) path.addLine(to: CGPoint(x: middle, y: 0)) } } } |
详细信息>
白色线性装饰物
在树上创建装饰。
这次使用
有点弯曲。
https://developer.apple.com/documentation/swiftui/path/3271274-addquadcurve
<详细信息>
1 2 3 4 5 6 7 8 9 | struct Slope: Shape { func path(in rect: CGRect) -> Path { Path { path in path.move(to: CGPoint(x: rect.minX, y: rect.midY)) path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.maxY), control: CGPoint(x: rect.midX * 0.8, y: rect.midY * 0.8)) } } } |
详细信息>
球形装饰物
接下来,进行球形装饰。
这是
使用
颜色是渐变。
https://developer.apple.com/documentation/swiftui/gradient
https://developer.apple.com/documentation/swiftui/lineargradient
指定初始化
您可以直接指定要更改的位置,但是
如果未指定,
似乎该框架会自动对其进行调整。
指定
<详细信息>
1 2 3 4 5 6 7 8 9 10 | struct BallView: View { let gradientColors = Gradient(colors: [Color.pink, Color.purple]) var body: some View { let linearGradient = LinearGradient( gradient: gradientColors, startPoint: .top, endPoint: .bottom) return Circle() .fill(linearGradient) } } |
详细信息>
并结合多个这些以创建一个View。
使用
https://developer.apple.com/documentation/swiftui/geometryreader
不要伸出三角形
我正在使用
https://developer.apple.com/documentation/swiftui/view/3278595-mask
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | struct BallsSlopeView<Mask: View>: View { let drawArea: Mask var body: some View { GeometryReader { gr in ForEach(1...10, id: \.self) { index in BallView() .position( self.getPosition(at: index, midX: gr.frame(in: .local).maxX, midY: gr.frame(in: .local).maxY)) .frame(height: 20) } } .mask(drawArea) } private func getPosition(at index: Int, midX: CGFloat, midY: CGFloat) -> CGPoint{ let x = midX * CGFloat(1 - CGFloat(index) * 0.1) let y = midY * CGFloat(1 - CGFloat(index) * 0.05) return CGPoint(x: x, y: y) } } |
详细信息>
结合
最后,结合上面制作的零件。
用
在白色装饰部分
通过利用
我旋转一点,使其挂在树上。
https://developer.apple.com/documentation/swiftui/scaledshape/3273943-rotationeffect
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | struct TreeView: View { var body: some View { GeometryReader { gr in ZStack(alignment: .center) { Triangle() .foregroundColor(Color.green) Slope() .stroke(lineWidth: 20) .mask(Triangle()) .foregroundColor(Color.white) Slope() .stroke(lineWidth: 20) .rotationEffect(Angle.degrees(300)) .mask(Triangle()) .foregroundColor(Color.white) BallsSlopeView(drawArea: Triangle()) } } } } |
详细信息>
底座
接下来,制作基础部分。
将白线
用
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | struct FoundationLine: Shape { func path(in rect: CGRect) -> Path { Path { path in path.move(to: CGPoint(x: 0, y: rect.maxY / 3)) path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY / 3)) path.move(to: CGPoint(x: 0, y: rect.maxY * 2 / 3)) path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY * 2 / 3)) path.move(to: CGPoint(x: rect.width / 3, y: 0)) path.addLine(to: CGPoint(x: rect.width / 3, y: rect.maxY)) path.move(to: CGPoint(x: rect.width * 2 / 3, y: 0)) path.addLine(to: CGPoint(x: rect.width * 2 / 3, y: rect.maxY)) } } } |
详细信息>
将白线和上面创建的
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct FoundationView: View { var body: some View { GeometryReader { gr in ZStack { Rectangle() .foregroundColor(Color.red) FoundationLine() .stroke(lineWidth: 3) .foregroundColor(Color.white) .mask(Rectangle()) } .frame(width: gr.size.width / 3, height: gr.size.width / 3) } } } |
详细信息>
组装一棵圣诞树
在
中,组合您到目前为止所做的。
调整位置,使星星和个别树木一点一点地重叠。
也
如果是这样
树木的三角形如何重叠
因为它是反向的(上部顶点部分似乎在顶部重叠)
重叠的方式由
https://developer.apple.com/documentation/swiftui/view/3278679-zindex
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | struct ChristmasTree: View { var body: some View { GeometryReader { gr in VStack(spacing: -12) { VStack(spacing: -(gr.size.width * 0.1)) { Star(corners: 5, smoothness: 0.5) .foregroundColor(Color.yellow) .frame(width: gr.size.width * 0.3, height: gr.size.width * 0.3) .zIndex(2) ZStack { VStack(spacing: -(gr.size.width / 5)) { TreeView() .frame(width: gr.size.width * 0.6) .zIndex(3) TreeView() .frame(width: gr.size.width * 0.7) .zIndex(2) TreeView() .frame(width: gr.size.width * 0.8) .zIndex(1) } .frame(height: gr.size.height * 0.5) .foregroundColor(Color.green) } .zIndex(1) } FoundationView() .frame(height: gr.size.height * 0.2) } } } } |
详细信息>
背景
接下来,我们将创建背景。
将
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct Particles: View { var body: some View { GeometryReader { gr in ZStack { ForEach(0...200, id: \.self) { _ in Circle() .foregroundColor(.red) .opacity(.random(in: 0.1...0.4)) .frame(width: .random(in: 10...100), height: .random(in: 10...100)) .position(x: .random(in: 0...gr.size.width), y: .random(in: 0...gr.size.height)) } } } } } |
详细信息>
设置背景动画
因为这很重要,所以请在背景中添加动画
让我们使其更加豪华(?)。
这次,我使用了名为
*
尝试各种值
我设置了一个我认为是这样的值,所以
合适的。
https://developer.apple.com/documentation/swiftui/animation/3344959-interactivespring
显示屏幕时引起动画的处理
设置SwiftUI动画时的注意事项
只需设置动画
动画无法开始。
在显示屏幕时生成此
例如,带有
通过更改
在View中动态更改值并重新呈现,等等。
需要处理。
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | struct Particles: View { // レンダリングを起こすために必要 @State private var scaling = false var animation: Animation { Animation .interactiveSpring(response: 5, dampingFraction: 0.5) .repeatForever() .speed(.random(in: 0.05...0.9)) .delay(.random(in: 0...2)) } var body: some View { GeometryReader { gr in ZStack { ForEach(0...200, id: \.self) { _ in Circle() .foregroundColor(.red) .opacity(.random(in: 0.1...0.4)) .animation(self.animation) // この値を変化させることで再レンダリングを起こしている .scaleEffect(self.scaling ? .random(in: 0.1...2) : 1) .frame(width: .random(in: 10...100), height: .random(in: 10...100)) .position(x: .random(in: 0...gr.size.width), y: .random(in: 0...gr.size.height)) } } .onAppear { // レンダリングを起こすために必要 self.scaling = true } } } } |
详细信息>
提高性能
屏幕已在上方完成,但
有一个问题。
通过处理上述动画
内存使用量将稳定增长。
*此后它将继续增加。
这是
当ZStack中的每个视图绘制时
在每个图层上构建图层。
那么使用那么多内存的结果
CPU上的负载将增加。
这可能会导致应用性能下降。
因此可以使用
drawingGroup
https://developer.apple.com/documentation/swiftui/group/3284805-drawinggroup
此方法是
视图中的所有视图
在屏幕外看不见的屏幕外
使用Metal API
在一张图片中绘制在一起
它将最终内容输出到屏幕。
这将减少内存上的负载
您可以提高性能。
<详细信息>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | struct Particles: View { // レンダリングを起こすために必要 @State private var scaling = false var animation: Animation { Animation .interactiveSpring(response: 5, dampingFraction: 0.5) .repeatForever() .speed(.random(in: 0.05...0.9)) .delay(.random(in: 0...2)) } var body: some View { GeometryReader { gr in ZStack { ForEach(0...200, id: \.self) { _ in Circle() .foregroundColor(.red) .opacity(.random(in: 0.1...0.4)) .animation(self.animation) // この値を変化させることで再レンダリングを起こしている .scaleEffect(self.scaling ? .random(in: 0.1...2) : 1) .frame(width: .random(in: 10...100), height: .random(in: 10...100)) .position(x: .random(in: 0...gr.size.width), y: .random(in: 0...gr.size.height)) } } // ここに設定をする .drawingGroup() .onAppear { // レンダリングを起こすために必要 self.scaling = true } } } } |
详细信息>
*
要注意的一点
drawingGroup文档具有以下描述。
1 | Views backed by native platform views don’t render into the image. |
本机平台视图
此本机平台视图是什么?
我不知道这是什么意思,但是
根据苹果公司在推特上的回答
看来是NSView或UIView。
https://twitter.com/jsh8080/status/1137045666939768833
概括
作为出现日历的资料
制作圣诞树时
我们已经研究了Swift UI的一些功能。
Swift UI声明性地制作了小零件
因为您可以将其合并
我再次感到它是高度可重用且易于阅读的。