Shapes, Curves, AnimateableData in SwiftUI

Updated:

Shapes, Curves, AnimateableData in SwiftUI

Custom Straight lines

By default SwiftUI actually comes with a bunch of shapes out of the box like rectangles rounded rectangles circles. By building custom and unique UI designs eventually you’ll run into a point where actually need a custom shape.

SwiftUI by actually drawing the shape from point to point on a path

// MARK: -  VIEW
struct CustomShapesBootCamp: View {
// MARK: -  PROPERTY
// MARK: -  BODY
var body: some View {
ZStack {
  Triangle()
    // .fill(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
    // .trim(from: 0, to: 0.5)
    .stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, dash: [10]))
    .foregroundColor(.blue)
    .frame(width: 300, height: 300)
} //: ZSTACK
}
}

// MARK: -  CUSTOM SHAPE
struct Triangle: Shape {

func path(in rect: CGRect) -> Path {
Path { path in
  path.move(to: CGPoint(x: rect.midX, y: rect.minY)) // Set Starting point
  path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
  path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
  path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
}
}
}

스크린샷

// MARK: -  VIEW
struct CustomShapesBootCamp: View {
// MARK: -  PROPERTY
// MARK: -  BODY
var body: some View {
ZStack {

Image("pic")
  .resizable()
  .scaledToFill()
  .frame(width: 300, height: 300)
  .clipShape(
    Triangle()
      .rotation(Angle(degrees: 180))
  )
} //: ZSTACK
}
}

// MARK: -  CUSTOM SHAPE
struct Triangle: Shape {

func path(in rect: CGRect) -> Path {
  Path { path in
    path.move(to: CGPoint(x: rect.midX, y: rect.minY)) // Set Starting point
    path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
    path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
    path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
  }
}
}

스크린샷

// MARK: -  VIEW
struct CustomShapesBootCamp: View {
// MARK: -  PROPERTY
// MARK: -  BODY
var body: some View {
  ZStack {

    Diamond()
      .frame(width: 300, height: 300)
  } //: ZSTACK
}
}

// MARK: -  CUSTOM SHAPE


struct Diamond: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
  let horizontalOffset: CGFloat = rect.width * 0.2
  path.move(to: CGPoint(x: rect.midX, y: rect.minY))
  path.addLine(to: CGPoint(x: rect.maxX - horizontalOffset, y: rect.midY))
  path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
  path.addLine(to: CGPoint(x: rect.minX + horizontalOffset, y: rect.midY))
  path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
}
}
}

스크린샷

// MARK: -  VIEW
struct CustomShapesBootCamp: View {
// MARK: -  PROPERTY
// MARK: -  BODY
var body: some View {
ZStack {

  Trapezoid()
    .frame(width: 300, height: 150)
} //: ZSTACK
}
}

// MARK: -  CUSTOM SHAPE
struct Trapezoid: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
  let horizontalOffset: CGFloat = rect.width * 0.2
  path.move(to: CGPoint(x: rect.minX + horizontalOffset, y: rect.minY ))
  path.addLine(to: CGPoint(x: rect.maxX - horizontalOffset, y: rect.minY))
  path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
  path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
  path.addLine(to: CGPoint(x: rect.minX + horizontalOffset, y: rect.minY))
}
}
}

스크린샷

Custom Curve Lines

Curves and arcs could be a little tricky to implement. To do that, arcs which is basically just a regular symmetrical curve and then quad curves which are a little more advanced and possibly more useful because they can connect two points and create an automatic curve between those two points

/ MARK: -  VIEW
struct CustomCurvesBootCamp: View {
// MARK: -  PROPERTY

// MARK: -  BODY
var body: some View {
  ArcSample()
    .stroke(lineWidth: 5)
    .frame(width: 200, height: 200)
}
}

// MARK: -  PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
  CustomCurvesBootCamp()
}
}

// MARK: -  CUSTOM SHAPE
struct ArcSample: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
  path.move(to: CGPoint(x: rect.maxX, y: rect.midY))
  path.addArc(
    center: CGPoint(x: rect.midX, y: rect.midY),
    radius: rect.height / 2,
    startAngle: Angle(degrees: 0),
    endAngle: Angle(degrees: 40),
    clockwise: true)
}
}
}

스크린샷

// MARK: -  VIEW
struct CustomCurvesBootCamp: View {
// MARK: -  PROPERTY

// MARK: -  BODY
var body: some View {
  ShapeWithArc()
    .frame(width: 200, height: 200)
    // .rotationEffect(Angle(degrees: 90))
}
}

// MARK: -  PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
  CustomCurvesBootCamp()
}
}

// MARK: -  CUSTOM SHAPE
struct ShapeWithArc: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
  // top left
  path.move(to: CGPoint(x: rect.minX, y: rect.minY))

  // top right
  path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))

  // mid right
  path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))

  // bottom
  // path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
  path.addArc(
    center: CGPoint(x: rect.midX, y: rect.midY),
    radius: rect.height / 2,
    startAngle: Angle(degrees: 0),
    endAngle: Angle(degrees: 180),
    clockwise: false)

  // mid left
  path.addLine(to: CGPoint(x: rect.minX, y: rect.midY))
}
}
}

스크린샷

  • Quad curve

image

// MARK: -  VIEW
struct CustomCurvesBootCamp: View {
// MARK: -  PROPERTY

// MARK: -  BODY
var body: some View {
  QuadSample()
    .frame(width: 200, height: 200)
}
}

// MARK: -  PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
  CustomCurvesBootCamp()
}
}

// MARK: -  CUSTOM SHAPE
struct QuadSample: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
  path.move(to: .zero)
  path.addQuadCurve(
    to: CGPoint(x: rect.maxX, y: rect.maxY),
    control: CGPoint(x: rect.minX, y: rect.maxY))
}
}
}

스크린샷

// MARK: -  VIEW
struct CustomCurvesBootCamp: View {
// MARK: -  PROPERTY

// MARK: -  BODY
var body: some View {
WaterShape()
  .fill(LinearGradient(
    gradient: Gradient(colors: [Color.blue, Color.cyan]),
    startPoint: .topTrailing,
    endPoint: .bottomTrailing))
  .ignoresSafeArea()
}
}

// MARK: -  PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
	static var previews: some View {
		CustomCurvesBootCamp()
	}
}

// MARK: -  CUSTOM SHAPE
struct WaterShape: 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.midX, y: rect.midY),
  control: CGPoint(x: rect.width * 0.25, y: rect.height * 0.40))

path.addQuadCurve(
  to: CGPoint(x: rect.maxX, y: rect.midY),
  control: CGPoint(x: rect.width * 0.75, y: rect.height * 0.60))

path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
}
}
}

스크린샷

Custom animated Lines

// MARK: -  VIEW
struct AnimatableDataBootCamp: View {
// MARK: -  PROPERTY
@State private var animate: Bool = false
// MARK: -  BODY
var body: some View {
ZStack {
  // RoundedRectangle(cornerRadius: animate ? 60 : 0)
  RectangleWithSingleCornerAnimation(cornerRadius: animate ? 60 : 0)
    .frame(width: 250, height: 250)
} //: ZSTACK
.onAppear {
  withAnimation(Animation.linear(duration: 2.0).repeatForever()) {
    animate.toggle()
  }
}
}
}

// MARK: -  PREVIEW
struct AnimatableDataBootCamp_Previews: PreviewProvider {
static var previews: some View {
  AnimatableDataBootCamp()
}
}

// MARK: -  CUSTOM SHAPE
struct RectangleWithSingleCornerAnimation: Shape {

var cornerRadius: CGFloat
var animatableData: CGFloat {
  get { cornerRadius }
  set { cornerRadius = newValue }
}

func path(in rect: CGRect) -> Path {
Path { path in
  path.move(to: .zero)
  path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
  path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))

  path.addArc(
    center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius),
    radius: cornerRadius,
    startAngle: Angle(degrees: 0),
    endAngle: Angle(degrees: 360),
    clockwise: false)
  path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY ))
  path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
}
}
}

스크린샷

// MARK: -  VIEW
struct AnimatableDataBootCamp: View {
// MARK: -  PROPERTY
@State private var animate: Bool = false
// MARK: -  BODY
var body: some View {
ZStack {
  Pacman(offsetAmount: animate ? 20 : 0)
    .frame(width: 250, height: 250)
} //: ZSTACK
.onAppear {
  withAnimation(Animation.easeInOut.repeatForever()) {
    animate.toggle()
  }
}
}
}

// MARK: -  PREVIEW
struct AnimatableDataBootCamp_Previews: PreviewProvider {
	static var previews: some View {
		AnimatableDataBootCamp()
	}
}

// MARK: -  CUSTOM SHAPE

struct Pacman: Shape {

var offsetAmount: Double
var animatableData: Double {
get { offsetAmount }
set { offsetAmount = newValue }
}

func path(in rect: CGRect) -> Path {
Path { path in
  path.move(to: CGPoint(x: rect.midX, y: rect.midY))
  path.addArc(
    center: CGPoint(x: rect.midX, y: rect.midY),
    radius: rect.height / 2,
    startAngle: Angle(degrees: offsetAmount),
    endAngle: Angle(degrees: 360 - offsetAmount),
    clockwise: false)
}
}
}

스크린샷


🗃 Reference

SwiftUI Thinking - https://www.youtube.com/watch?v=EHhgjOt_KFA&list=PLwvDm4Vfkdphc1LLLjCaEd87BEg07M97y&index=6

Categories:

Updated:

Leave a comment