Говорят, что использование Canvas для создания сложных форм может обеспечить лучшую производительность в SwiftUI. В этой статье сравнивается производительность прокрутки нескольких экземпляров одного и того же шаблона карты, созданного с помощью формы, холста или изображения.
Говорят, что использование Canvas для создания сложных форм может обеспечить лучшую производительность в SwiftUI. В этой статье сравнивается производительность прокрутки нескольких экземпляров одного и того же шаблона карты, созданного с помощью формы, холста или изображения.
Создание шаблона карты с помощью Shape
Все детали построены с использованием фигуры Diamond, созданной в разделе Создание деталей из базовых фигур в SwiftUI. DiamondShape соответствует протоколу Shape и реализует функцию path для создания ромбовидной формы внутри содержащего прямоугольника. В оригинальную форму алмаза внесена одна поправка в функции offsetPoint, чтобы скорректировать начало координат содержащего прямоугольника не в точке (0,0).
struct DiamondShape: Shape { func path(in rect: CGRect) -> Path { let size = min(rect.width, rect.height) let xOffset = (rect.width > rect.height) ? (rect.width - rect.height) / 2.0 : 0.0 let yOffset = (rect.height > rect.width) ? (rect.height - rect.width) / 2.0 : 0.0 func offsetPoint(p: CGPoint) -> CGPoint { return CGPoint(x: p.x + xOffset + rect.origin.x, y: p.y+yOffset + rect.origin.y) } let path = Path { path in path.move(to: offsetPoint(p: CGPoint(x: 0, y: (size * 0.50)))) path.addQuadCurve(to: offsetPoint(p: CGPoint(x: (size * 0.50), y: 0)), control: offsetPoint(p: CGPoint(x: (size * 0.40), y: (size * 0.40)))) path.addQuadCurve(to: offsetPoint(p: CGPoint(x: size, y: (size * 0.50))), control: offsetPoint(p: CGPoint(x: (size * 0.60), y: (size * 0.40)))) path.addQuadCurve(to: offsetPoint(p: CGPoint(x: (size * 0.50), y: size)), control: offsetPoint(p: CGPoint(x: (size * 0.60), y: (size * 0.60)))) path.addQuadCurve(to: offsetPoint(p: CGPoint(x: 0, y: (size * 0.50))), control: offsetPoint(p: CGPoint(x: (size * 0.40), y: (size * 0.60)))) path.closeSubpath() } return path } }
DiamondCardView создается путем расположения строк и столбцов ромбов с помощью вертикальных и горизонтальных стеков.
struct DiamondCardView: View { var rows :Int var cols: Int var body: some View { GeometryReader { gr in let width = gr.size.width / CGFloat(cols) let height = gr.size.height / CGFloat(rows) VStack(spacing:0) { ForEach(0..<rows) { i in let rowCols = (i%2==0) ? cols : cols - 1 HStack(spacing:0) { Group { ForEach(0..<rowCols) { _ in DiamondShape() .fill(Color(red: 250/255, green: 100/255, blue: 90/255)) .frame(width: width, height: height) } } } } } } } }
Узор открытки можно дополнить, добавив бордюры и закруглив углы.
struct SingleShapeView: View { var body: some View { ZStack { Color(red: 214/255, green: 232/255, blue: 248/255) .edgesIgnoringSafeArea(.all) VStack { Text("Card from Shape") .font(.title) .fontWeight(.bold) .padding(.vertical, 30) Group { DiamondCardView(rows: 21, cols: 7) .padding(40) .frame(width: 250, height: 430) .background(Color(red: 30/255, green: 40/255, blue: 60/255)) .cornerRadius(30) .overlay(RoundedRectangle(cornerRadius: 40) .stroke(Color(red: 0.93, green: 0.94, blue: 0.77), lineWidth: 50) .cornerRadius(30)) .overlay(RoundedRectangle(cornerRadius: 20) .stroke(Color(red: 250/255, green: 100/255, blue: 90/255), lineWidth: 20) .cornerRadius(15) .padding(25)) .overlay(RoundedRectangle(cornerRadius: 30) .stroke(.black, lineWidth: 2) .cornerRadius(30)) } .rotationEffect(.degrees(10)) Spacer() } } } }
Создание шаблона карты с помощью Canvas
Как обсуждалось в разделе Использование canvas в SwiftUI, представление Canvas предоставляет механизм для эффективного рисования в SwiftUI. Холст использует GraphicsContext и размер, чтобы рисовать непосредственно в пределах содержащего прямоугольника. Тот же шаблон карты, что и выше, создается с использованием холста для компоновки отдельных фигур ромбов. Кроме того, тот же DiamondShape используется в холсте путем доступа к функции path фигуры и передачи содержащего прямоугольника.
struct SingleCanvasView: View { var body: some View { ZStack { Color(red: 214/255, green: 232/255, blue: 248/255) .edgesIgnoringSafeArea(.all) VStack { Text("Card from Canvas") .font(.title) .fontWeight(.bold) .padding(.vertical, 30) Canvas { context, size in let w = size.width let h = size.height let rows = 21 let cols = 7 for r in 0..<rows { for c in 0..<cols { let x = w/CGFloat(cols) * CGFloat(c) let y = h/CGFloat(rows) * CGFloat(r) context.fill( DiamondShape().path(in: CGRect( x: x, y: y, width: w/CGFloat(cols), height: h/CGFloat(rows))), with: .color(red: 250/255, green: 100/255, blue: 90/255)) } } } .padding(40) .background(Color(red: 30/255, green: 40/255, blue: 60/255)) .frame(width: 250, height: 430) .cornerRadius(30) .overlay(RoundedRectangle(cornerRadius: 40) .stroke(Color(red: 0.93, green: 0.94, blue: 0.77), lineWidth: 50) .cornerRadius(30)) .overlay(RoundedRectangle(cornerRadius: 20) .stroke(Color(red: 250/255, green: 100/255, blue: 90/255), lineWidth: 20) .cornerRadius(15) .padding(25)) .overlay(RoundedRectangle(cornerRadius: 30) .stroke(.black, lineWidth: 2) .cornerRadius(30)) .rotationEffect(.degrees(10)) Spacer() } } } }
ScrollView с карточками из Shape
По отдельности вышеупомянутые представления отображаются нормально, и, похоже, нет никаких различий в производительности. Создается ScrollView, содержащий 50 карт DiamondCardView, чтобы нагрузить систему и поискать проблемы с производительностью.
struct MultipleShapeView: View { var body: some View { ZStack { Color(red: 214/255, green: 232/255, blue: 248/255) .edgesIgnoringSafeArea(.all) ScrollView { ForEach(1...50, id: \.self) { _ in CardShapeView() } } .navigationTitle("50 Shape Cards") } } } struct CardShapeView: View { var body: some View { DiamondCardView(rows: 7, cols: 21) .padding(30) .frame(width: 300, height: 200) .background(Color(red: 30/255, green: 40/255, blue: 60/255)) .cornerRadius(30) .overlay(RoundedRectangle(cornerRadius: 40) .stroke(Color(red: 0.93, green: 0.94, blue: 0.77), lineWidth: 50) .cornerRadius(30)) .overlay(RoundedRectangle(cornerRadius: 20) .stroke(Color(red: 250/255, green: 100/255, blue: 90/255), lineWidth: 10) .cornerRadius(15) .padding(20)) .overlay(RoundedRectangle(cornerRadius: 30) .stroke(.black, lineWidth: 2) .cornerRadius(30)) } }
ScrollView с карточками из Canvas
Создается аналогичный ScrollView, содержащий 50 карточек из CardCanvasView, чтобы нагрузить систему и поискать проблемы с производительностью.
struct MultipleCanvasView: View { var body: some View { ZStack { Color(red: 214/255, green: 232/255, blue: 248/255) .edgesIgnoringSafeArea(.all) ScrollView { ForEach(1...50, id: \.self) { i in CardCanvasView() } } .navigationTitle("50 Canvas Card") } } } struct CardCanvasView: View { var body: some View { VStack { Canvas { context, size in let w = size.width let h = size.height let rows = 7 let cols = 21 for r in 0..<rows { for c in 0..<cols { let x = w/CGFloat(cols) * CGFloat(c) let y = h/CGFloat(rows) * CGFloat(r) context.fill( DiamondShape().path(in: CGRect(x: x, y: y, width: w/CGFloat(cols), height: h/CGFloat(rows))), with: .color(red: 250/255, green: 100/255, blue: 90/255)) } } } .padding(30) .frame(width: 300, height: 200) .background(Color(red: 30/255, green: 40/255, blue: 60/255)) .cornerRadius(30) .overlay(RoundedRectangle(cornerRadius: 40) .stroke(Color(red: 0.93, green: 0.94, blue: 0.77), lineWidth: 50) .cornerRadius(30)) .overlay(RoundedRectangle(cornerRadius: 20) .stroke(Color(red: 250/255, green: 100/255, blue: 90/255), lineWidth: 10) .cornerRadius(15) .padding(20)) .overlay(RoundedRectangle(cornerRadius: 30) .stroke(.black, lineWidth: 2) .cornerRadius(30)) } } }
ScrollView с карточками из изображения
Наконец, создается ScrollView, содержащий 50 изображений для карточек, чтобы сравнить производительность с использованием фигур и холста. Изображение создается из скриншота одной из вышеуказанных карточек и добавляется в активы.
struct MultipleImageView: View { var body: some View { ZStack { Color(red: 214/255, green: 232/255, blue: 248/255) .edgesIgnoringSafeArea(.all) ScrollView { ForEach(1...50, id: \.self) { _ in Image("card-background") .resizable() .scaledToFill() .clipShape(RoundedRectangle(cornerRadius: 40)) .frame(width: 310, height: 200) } } .navigationTitle("50 Image Card") } } }
Обзор производительности
Существует множество способов измерения производительности, включая автоматизированные тесты производительности, о которых я расскажу в другой статье. В этом приложении мы просто запустим приложение и посмотрим на использование процессора и памяти в Xcode. Видно, что ScrollView с 50 картами, созданными с использованием фигур для каждого алмаза и выкладыванием их в стопки, использует значительно больше памяти для загрузки. Он также использует значительно больше процессора при прокрутке представления вверх и вниз. Карточки, созданные с помощью canvas, используют значительно меньше памяти для загрузки и меньше процессора при прокрутке вверх и вниз. Использование изображения для карточек работает лучше всего, но лишь немного лучше, чем использование canvas.
Заключение
ScrollView, содержащий 50 экземпляров одной и той же карты, возможно, не самый реалистичный сценарий, но он иллюстрирует разницу в производительности при создании таких представлений. Использование холста для компоновки нескольких фигур, таких как алмазы, дает гораздо более высокую производительность, чем компоновка нескольких фигур в представлении. Это видно по отзывчивости прокрутки и использованию памяти и процессора. Если эти фигуры в приложении статичны, то использование изображения обеспечивает немного лучшую производительность, чем использование canvas. Использование canvas показало почти такие же результаты, как и использование изображений, поэтому если содержимое должно быть динамичным, то canvas может быть лучшим вариантом.