Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Feature/path #85

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AltSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
7D2D4D6E25148269000F5DDC /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2D4D6825148269000F5DDC /* Rectangle.swift */; };
7D2D4D6F25148269000F5DDC /* Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2D4D6925148269000F5DDC /* Shapes.swift */; };
7D2D4D7025148269000F5DDC /* RoundedRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2D4D6A25148269000F5DDC /* RoundedRectangle.swift */; };
7DEA1BEB2668D38500FE86E0 /* Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEA1BEA2668D38500FE86E0 /* Path.swift */; };
EACD577F25EC694800FEF5A1 /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACD577E25EC694800FEF5A1 /* Menu.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -174,6 +175,7 @@
7D2D4D6825148269000F5DDC /* Rectangle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rectangle.swift; sourceTree = "<group>"; };
7D2D4D6925148269000F5DDC /* Shapes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = "<group>"; };
7D2D4D6A25148269000F5DDC /* RoundedRectangle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedRectangle.swift; sourceTree = "<group>"; };
7DEA1BEA2668D38500FE86E0 /* Path.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Path.swift; sourceTree = "<group>"; };
EACD577E25EC694800FEF5A1 /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -438,6 +440,7 @@
7D2D4D6725148269000F5DDC /* Ellipse.swift */,
7D2D4D6825148269000F5DDC /* Rectangle.swift */,
7D2D4D6A25148269000F5DDC /* RoundedRectangle.swift */,
7DEA1BEA2668D38500FE86E0 /* Path.swift */,
7D2D4D6925148269000F5DDC /* Shapes.swift */,
);
path = Shapes;
Expand Down Expand Up @@ -633,6 +636,7 @@
2CD1A0252512066D00E2CCBA /* ViewPropertyGestureTypes.swift in Sources */,
2CD1A01A2512066D00E2CCBA /* State.swift in Sources */,
2CD1A0482512066D00E2CCBA /* NavigationViews.swift in Sources */,
7DEA1BEB2668D38500FE86E0 /* Path.swift in Sources */,
7D2D4D6B25148269000F5DDC /* Capsule.swift in Sources */,
2CD1A0242512066D00E2CCBA /* ViewPropertyBuilder.swift in Sources */,
2CD1A0292512066D00E2CCBA /* ViewPropertyAnimationTypes.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Example/AltSwiftUIExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
35D1A58825C7EDA100861DC4 /* AlertsExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D1A58525C7EDA100861DC4 /* AlertsExampleView.swift */; };
3ED3C29525F9CE5700F0F602 /* TextFieldExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED3C29425F9CE5700F0F602 /* TextFieldExampleView.swift */; };
682C3A7C2594B18A005E798E /* SecureFieldExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682C3A7B2594B18A005E798E /* SecureFieldExampleView.swift */; };
7DEA1BE22668D23800FE86E0 /* PathExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEA1BE12668D23800FE86E0 /* PathExampleView.swift */; };
EACD577925EC689200FEF5A1 /* MenuExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACD577825EC689200FEF5A1 /* MenuExampleView.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -103,6 +104,7 @@
35D1A58525C7EDA100861DC4 /* AlertsExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertsExampleView.swift; sourceTree = "<group>"; };
3ED3C29425F9CE5700F0F602 /* TextFieldExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldExampleView.swift; sourceTree = "<group>"; };
682C3A7B2594B18A005E798E /* SecureFieldExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureFieldExampleView.swift; sourceTree = "<group>"; };
7DEA1BE12668D23800FE86E0 /* PathExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathExampleView.swift; sourceTree = "<group>"; };
EACD577825EC689200FEF5A1 /* MenuExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuExampleView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -152,6 +154,7 @@
2C74075D262833D600FF4380 /* ScrollViewTextFieldExample.swift */,
682C3A7B2594B18A005E798E /* SecureFieldExampleView.swift */,
2CADC316251CA15400EA3F17 /* ShapesExampleView.swift */,
7DEA1BE12668D23800FE86E0 /* PathExampleView.swift */,
2CDCE7F225C40D2500BB52D1 /* StackUpdateExample.swift */,
2C49E3CB2535382F00543E7D /* TextExampleView.swift */,
3ED3C29425F9CE5700F0F602 /* TextFieldExampleView.swift */,
Expand Down Expand Up @@ -381,6 +384,7 @@
2C7407642628365100FF4380 /* ListTextFieldExampleView.swift in Sources */,
682C3A7C2594B18A005E798E /* SecureFieldExampleView.swift in Sources */,
35D1A58825C7EDA100861DC4 /* AlertsExampleView.swift in Sources */,
7DEA1BE22668D23800FE86E0 /* PathExampleView.swift in Sources */,
2C83ED522554E50400C378DC /* NavigationExampleView.swift in Sources */,
2C74075E262833D600FF4380 /* ScrollViewTextFieldExample.swift in Sources */,
2CADC30F251CA0DF00EA3F17 /* RamenExampleView.swift in Sources */,
Expand Down
127 changes: 127 additions & 0 deletions Example/AltSwiftUIExample/ExampleViews/PathExampleView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// PathExampleView.swift
// AltSwiftUIExample
//
// Created by Nodehi, Jabbar on 2021/06/03.
// Copyright © 2021 Rakuten Travel. All rights reserved.
//

import Foundation

import AltSwiftUI
import UIKit

struct PathExampleView: View {
var viewStore = ViewValues()

@State private var toggle = true

private let correctY: CGFloat = 50
private let messedUpY: CGFloat = 250

private let correctX: CGFloat = 20
private let messedUpX: CGFloat = 56

private var tickHeight: CGFloat = 50
private var tickWidth: CGFloat = 50

@State private var percentage: CGFloat = .zero

var body: View {
ScrollView {
VStack {

// MARK: - Tick
Path { path in
path.move(to: CGPoint(x: 0, y: tickHeight/2))
path.addLine(to: CGPoint(x: tickWidth/2, y: tickHeight))
path.addLine(to: CGPoint(x: tickWidth, y: 0))
}
.trim(from: 0, to: percentage)
.stroke(Color.black, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.animation(.easeOut(duration: 2.0))
.onAppear {
self.percentage = 1.0
}
.onDisappear {
self.percentage = .zero
}

// MARK: - Toggle Button
Button("\(toggle ? "Distort" : "Fix") the 📱 with animation") {
withAnimation { self.toggle.toggle() }
}
.padding(.vertical, 30)

// MARK: - iPhone Shape
ZStack {
Path { path in
path.move(to: CGPoint(x: 0, y: toggle ? correctY : messedUpY))
path.addQuadCurve(to: CGPoint(x: 50, y: 0), control: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 300, y: 0))
path.addQuadCurve(to: CGPoint(x: 350, y: toggle ? correctY : messedUpY), control: CGPoint(x: 350, y: 0))
path.addLine(to: CGPoint(x: 350, y: 600))
path.addQuadCurve(to: CGPoint(x: 300, y: 650), control: CGPoint(x: 350, y: 650))
path.addLine(to: CGPoint(x: 50, y: 650))
path.addQuadCurve(to: CGPoint(x: 0, y: 600), control: CGPoint(x: 0, y: 650))
path.addLine(to: CGPoint(x: 0, y: toggle ? correctY : messedUpY))
}
.fill(.gray)
.strokeBorder(.black)

Path { path in
path.move(to: CGPoint(x: toggle ? correctX : messedUpX, y: toggle ? correctY : messedUpY))
path.addQuadCurve(to: CGPoint(x: 50, y: 20), control: CGPoint(x: toggle ? correctX : messedUpX, y: 20))

path.addLine(to: CGPoint(x: 90, y: 20))
path.addQuadCurve(to: CGPoint(x: 100, y: 30), control: CGPoint(x: 100, y: 20))
path.addQuadCurve(to: CGPoint(x: 120, y: correctY), control: CGPoint(x: 100, y: toggle ? correctY : messedUpY))
path.addLine(to: CGPoint(x: 230, y: toggle ? correctY : messedUpY))
path.addQuadCurve(to: CGPoint(x: 250, y: 30), control: CGPoint(x: 250, y: toggle ? correctY : messedUpY))
path.addQuadCurve(to: CGPoint(x: 260, y: 20), control: CGPoint(x: 250, y: 20))

path.addLine(to: CGPoint(x: 300, y: 20))
path.addQuadCurve(to: CGPoint(x: 330, y: toggle ? correctY : messedUpY), control: CGPoint(x: 330, y: 20))
path.addLine(to: CGPoint(x: 330, y: 600))
path.addQuadCurve(to: CGPoint(x: 300, y: 630), control: CGPoint(x: 330, y: 630))
path.addLine(to: CGPoint(x: 50, y: 630))
path.addQuadCurve(to: CGPoint(x: toggle ? correctX : messedUpX, y: 600), control: CGPoint(x: toggle ? correctX : messedUpX, y: 630))
path.addLine(to: CGPoint(x: toggle ? correctX : messedUpX, y: toggle ? correctY : messedUpY))
}
.fill(.secondary)
.strokeBorder(.black)
}

// MARK: - Text
Text("Text to mix up UI elements and show that Path occupy actual space.")
.foregroundColor(.orange)
.padding(.vertical, 20)

// MARK: - Nested red Rectangles
shrinkingSquares()
.strokeBorder(.black)
.fill(.red)
}
.padding(.vertical, 20)
}
.edgesIgnoringSafeArea(.bottom)
.frame(maxWidth: .infinity)
}

private func shrinkingSquares() -> Path {
let size = 400
let change = 20
let path = Path()

for i in stride(from: 1, through: size, by: change) {
let rect = CGRect(
x: CGFloat(i),
y: CGFloat(i),
width: CGFloat(size) - CGFloat(2 * i),
height: CGFloat(size) - CGFloat(2 * i)
)
path.addRect(rect)
}
return path
}
}
1 change: 1 addition & 0 deletions Example/AltSwiftUIExample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct ExampleView: View {
ExampleViewData(title: "SecureField", destination: SecureFieldExampleView()),
ExampleViewData(title: "Stack Update", destination: StackUpdateExample()),
ExampleViewData(title: "Shapes", destination: ShapesExampleView()),
ExampleViewData(title: "Path", destination: PathExampleView()),
ExampleViewData(title: "Stack Update", destination: StackUpdateExample()),
ExampleViewData(title: "Texts", destination: TextExampleView()),
ExampleViewData(title: "TextField", destination: TextFieldExampleView())
Expand Down
153 changes: 153 additions & 0 deletions Sources/AltSwiftUI/Source/Views/Shapes/Path.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//
// Path.swift
// AltSwiftUI
//
// Created by Nodehi, Jabbar on 2021/06/03.
//

import UIKit

/// A view that represents a Path shape.
public struct Path: Shape {
public var viewStore = ViewValues()

public var fillColor = Color.clear
public var strokeBorderColor = Color.clear
public var style = StrokeStyle()
var trimStartFraction: CGFloat = 0
var trimEndFraction: CGFloat = 1

var myPath = CGMutablePath()

public var body: View {
EmptyView()
}

public init() {}

public init(_ callback: @escaping (inout Path) -> Void) {
callback(&self)
}

public func trim(from startFraction: CGFloat = 0, to endFraction: CGFloat = 1) -> Self {
var path = self
path.trimStartFraction = startFraction
path.trimEndFraction = endFraction
return path
}

public func createView(context: Context) -> UIView {
let view = AltShapeView().noAutoresizingMask()
view.overrideIntrinsicContentSize = true
view.updateOnLayout = { [weak view] rect in

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Unused parameter "rect" in a closure should be replaced with _. (unused_closure_parameter)

guard let view = view else { return }
updatePath(view: view, path: UIBezierPath(cgPath: myPath), animation: nil)
}
updateView(view, context: context.withoutTransaction)
return view
}

public func updateView(_ view: UIView, context: Context) {
guard let view = view as? AltShapeView else { return }
let oldView = view.lastRenderableView?.view as? Path

let width = context.viewValues?.viewDimensions?.width ?? view.bounds.width
let height = context.viewValues?.viewDimensions?.height ?? view.bounds.height
view.pathBoundingBox = myPath.boundingBox
view.setNeedsLayout()
let animation = context.transaction?.animation
view.lastSizeFromViewUpdate = CGSize(width: width, height: height)

if fillColor.rawColor != Color.clear.rawColor {
updatePath(
view: view,
path: UIBezierPath(cgPath: myPath),
animation: animation)
} else {
if context.viewValues?.viewDimensions != oldView?.viewStore.viewDimensions {
updatePath(view: view, path: UIBezierPath(cgPath: myPath), animation: animation)
}
performUpdate(layer: view.caShapeLayer, keyPath: "strokeStart", newValue: trimStartFraction, animation: animation, oldValue: oldView?.trimStartFraction)
performUpdate(layer: view.caShapeLayer, keyPath: "strokeEnd", newValue: trimEndFraction, animation: animation, oldValue: oldView?.trimEndFraction)
}
updateShapeLayerValues(view: view, context: context)
}
}

extension Path {

public func addRoundedRect(
in rect: CGRect,
cornerWidth: CGFloat,
cornerHeight: CGFloat,
transform: CGAffineTransform = .identity
) {
myPath.addRoundedRect(in: rect, cornerWidth: cornerWidth, cornerHeight: cornerHeight, transform: transform)
}

public func move(to p: CGPoint) {
myPath.move(to: p)
}
public func addLine(to p: CGPoint) {
myPath.addLine(to: p)
}

public func addQuadCurve(to end: CGPoint, control: CGPoint, transform: CGAffineTransform = .identity) {
myPath.addQuadCurve(to: end, control: control, transform: transform)
}

public func addCurve(to end: CGPoint, control1: CGPoint, control2: CGPoint, transform: CGAffineTransform = .identity) {
myPath.addCurve(to: end, control1: control1, control2: control2, transform: transform)
}

public func addRect(_ rect: CGRect, transform: CGAffineTransform = .identity) {
myPath.addRect(rect, transform: transform)
}

public func addRects(_ rects: [CGRect], transform: CGAffineTransform = .identity) {
myPath.addRects(rects, transform: transform)
}

public func addLines(between points: [CGPoint], transform: CGAffineTransform = .identity) {
myPath.addLines(between: points, transform: transform)
}

public func addEllipse(in rect: CGRect, transform: CGAffineTransform = .identity) {
myPath.addEllipse(in: rect, transform: transform)
}

public func addRelativeArc(
center: CGPoint,
radius: CGFloat,
startAngle: CGFloat,
delta: CGFloat,
transform: CGAffineTransform = .identity
) {
myPath.addRelativeArc(center: center, radius: radius, startAngle: startAngle, delta: delta, transform: transform)
}

public func addArc(
center: CGPoint,
radius: CGFloat,
startAngle: CGFloat,
endAngle: CGFloat,
clockwise: Bool,
transform: CGAffineTransform = .identity
) {
myPath.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise, transform: transform)
}

public func addArc(
tangent1End: CGPoint,
tangent2End: CGPoint,
radius: CGFloat,
transform: CGAffineTransform = .identity
) {
myPath.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: radius, transform: transform)
}

public func addPath(_ path: CGPath, transform: CGAffineTransform = .identity) {
myPath.addPath(path, transform: transform)
}

}
13 changes: 13 additions & 0 deletions Sources/AltSwiftUI/Source/Views/Shapes/Shapes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class AltShapeView: UIView {
var lastSizeFromViewUpdate: CGSize = .zero
var strokeColor: UIColor?
var fillColor: UIColor?
var overrideIntrinsicContentSize = false
var pathBoundingBox = CGRect.zero

init() {
super.init(frame: .zero)
Expand Down Expand Up @@ -45,6 +47,17 @@ class AltShapeView: UIView {
private func setupLayer() {
layer.addSublayer(caShapeLayer)
}

override var intrinsicContentSize: CGSize {
if overrideIntrinsicContentSize {
return CGSize(
width: pathBoundingBox.width + (2 * pathBoundingBox.origin.x),
height: pathBoundingBox.height + (2 * pathBoundingBox.origin.y)
)
}
return super.intrinsicContentSize

}
}

public protocol Shape: View, Renderable {
Expand Down