I have a SubView
perfectly designed for 320x320 :). I want to let it scale down to a minimum of 100x100. Now i am using a scaling factor in each Shape
. Question: is there a more generic way of scaling Shapes
(dimension, lines width and length and rectangles width and height) in a reusable SwiftUI View
? Thank you.
import SwiftUI
private struct SecondsFace: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let square = CGRect(x: rect.minX, y: rect.minY, width: min(rect.maxX-rect.minX,rect.maxY-rect.minY), height: min(rect.maxX-rect.minX,rect.maxY-rect.minY))
let scaleFactor = CGFloat(square.width / 320.0)
var seconds : Path = Path()
seconds.addLines([CGPoint(x: (320.0/2.0-14.0)*scaleFactor, y: 0),CGPoint(x: (320.0/2-4.0)*scaleFactor, y: 0)])
for i in 0...59 {
path.addPath(seconds, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(i)/60.0))
}
return path.offsetBy(dx: (320.0/2.0)*scaleFactor, dy: (320.0/2.0)*scaleFactor)
}
}
private struct HoursFace: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let square = CGRect(x: rect.minX, y: rect.minY, width: min(rect.maxX-rect.minX,rect.maxY-rect.minY), height: min(rect.maxX-rect.minX,rect.maxY-rect.minY))
let scaleFactor = CGFloat(square.width / 320.0)
var seconds : Path = Path()
seconds.addLines([CGPoint(x: (320.0/2.0-16)*scaleFactor, y: 0),CGPoint(x: (320.0/2.0-2.0)*scaleFactor, y: 0)])
for i in 0...11 {
path.addPath(seconds, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(i)/12.0))
}
return path.offsetBy(dx: (320.0/2.0)*scaleFactor, dy: (320.0/2.0)*scaleFactor)
}
}
struct ThreeHoursFace: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let square = CGRect(x: rect.minX, y: rect.minY, width: min(rect.maxX-rect.minX,rect.maxY-rect.minY), height: min(rect.maxX-rect.minX,rect.maxY-rect.minY))
let scaleFactor = CGFloat(square.width / 320.0)
let threehours : Path = Path(CGRect(x: (320.0/2.0-20.0)*scaleFactor, y: -2.0*scaleFactor, width: 20.0*scaleFactor, height: 4.0*scaleFactor))
for i in 0...3 {
path.addPath(threehours, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(i)/4.0))
}
return path.offsetBy(dx: (320.0/2.0)*scaleFactor, dy: (320.0/2.0)*scaleFactor)
}
}
struct SubView: View {
@Binding var color : ColorValue
var body: some View {
ZStack {
SecondsFace()
.stroke(Color.green, style: StrokeStyle(lineWidth: 1, lineCap: .square, lineJoin: .bevel))
HoursFace()
.stroke(Color.red, style: StrokeStyle(lineWidth: 3, lineCap: .square, lineJoin: .bevel))
ThreeHoursFace()
.fill(Color.blue)
}
.aspectRatio(1, contentMode: .fit)
.clipped(antialiased: true)
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0))
}
}
struct SubViewPreview: PreviewProvider {
@State static var currentColor : ColorValue = ColorValue(color: UIColor.red)
static var previews: some View {
SubView(color: $currentColor)
}
}
(There is still a small issue with scaling of linewidths ...)
import Foundation
import SwiftUI
struct MainView: View {
@State var color = ColorValue(color: UIColor.red)
var body: some View {
VStack {
HStack(spacing: 10) {
SubView(color: $color)
}
.padding(10)
HStack(spacing: 10) {
SubView(color: $color)
SubView(color: $color)
}
.padding(10)
HStack(spacing: 10) {
SubView(color: $color)
SubView(color: $color)
SubView(color: $color)
}
.padding(10)
}
}
}
struct ShapeView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
I am using now applying(CGAffineTransform(a:, b: , c: , d:, tx: , ty: ))
on the result path
in Shape
.
import SwiftUI
private struct SecondsFace: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let seconds : Path = Path(CGRect(x: (320.0/2.0-14.0), y: -1.0, width: 10.0, height: 2.0))
for i in 0...59 {
path.addPath(seconds, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(i)/60.0))
}
let scaleFactor = CGFloat(min(rect.maxX-rect.minX,rect.maxY-rect.minY) / 320.0)
return path.applying(CGAffineTransform(a: scaleFactor, b: 0.0, c: 0.0, d: scaleFactor, tx: (320.0/2.0)*scaleFactor, ty: (320.0/2.0)*scaleFactor))
}
}
private struct HoursFace: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let seconds : Path = Path(CGRect(x: (320.0/2.0-16.0), y: -2.0, width: 14.0, height: 4.0))
for i in 0...11 {
path.addPath(seconds, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(i)/12.0))
}
let scaleFactor = CGFloat(min(rect.maxX-rect.minX,rect.maxY-rect.minY) / 320.0)
return path.applying(CGAffineTransform(a: scaleFactor, b: 0.0, c: 0.0, d: scaleFactor, tx: (320.0/2.0)*scaleFactor, ty: (320.0/2.0)*scaleFactor))
}
}
struct ThreeHoursFace: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let threehours : Path = Path(CGRect(x: (320.0/2.0-24.0), y: -4.0, width: 24.0, height: 8.0))
for i in 0...3 {
path.addPath(threehours, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(i)/4.0))
}
let scaleFactor = CGFloat(min(rect.maxX-rect.minX,rect.maxY-rect.minY) / 320.0)
return path.applying(CGAffineTransform(a: scaleFactor, b: 0.0, c: 0.0, d: scaleFactor, tx: (320.0/2.0)*scaleFactor, ty: (320.0/2.0)*scaleFactor))
}
}
struct SubView: View {
@Binding var color : ColorValue
var body: some View {
ZStack {
SecondsFace()
.fill(Color.green)
HoursFace()
.fill(Color.red)
ThreeHoursFace()
.fill(Color.blue)
}
.aspectRatio(1, contentMode: .fit)
.clipped(antialiased: true)
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0))
}
}
struct SubViewPreview: PreviewProvider {
@State static var currentColor : ColorValue = ColorValue(color: UIColor.red)
static var previews: some View {
SubView(color: $currentColor)
}
}