) -> Void = { (info, element) in
58 | let body = unsafeBitCast(info, to: Body.self)
59 | body(element.pointee)
60 | }
61 | //print(MemoryLayout.size(ofValue: body))
62 | let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
63 | self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
64 | }
65 | func getPathElementsPoints() -> [CGPoint] {
66 | var arrayPoints : [CGPoint]! = [CGPoint]()
67 | self.forEach { element in
68 | switch (element.type) {
69 | case CGPathElementType.moveToPoint:
70 | arrayPoints.append(element.points[0])
71 | case .addLineToPoint:
72 | arrayPoints.append(element.points[0])
73 | case .addQuadCurveToPoint:
74 | arrayPoints.append(element.points[0])
75 | arrayPoints.append(element.points[1])
76 | case .addCurveToPoint:
77 | arrayPoints.append(element.points[0])
78 | arrayPoints.append(element.points[1])
79 | arrayPoints.append(element.points[2])
80 | default: break
81 | }
82 | }
83 | return arrayPoints
84 | }
85 | func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
86 | var arrayPoints : [CGPoint]! = [CGPoint]()
87 | var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
88 | self.forEach { element in
89 | switch (element.type) {
90 | case CGPathElementType.moveToPoint:
91 | arrayPoints.append(element.points[0])
92 | arrayTypes.append(element.type)
93 | case .addLineToPoint:
94 | arrayPoints.append(element.points[0])
95 | arrayTypes.append(element.type)
96 | case .addQuadCurveToPoint:
97 | arrayPoints.append(element.points[0])
98 | arrayPoints.append(element.points[1])
99 | arrayTypes.append(element.type)
100 | arrayTypes.append(element.type)
101 | case .addCurveToPoint:
102 | arrayPoints.append(element.points[0])
103 | arrayPoints.append(element.points[1])
104 | arrayPoints.append(element.points[2])
105 | arrayTypes.append(element.type)
106 | arrayTypes.append(element.type)
107 | arrayTypes.append(element.type)
108 | default: break
109 | }
110 | }
111 | return (arrayPoints,arrayTypes)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/ProcessingKitTests/GestureTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventTests.swift
3 | // ProcessingKitTests
4 | //
5 | // Created by AtsuyaSato on 2018/07/04.
6 | // Copyright © 2018年 Atsuya Sato. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ProcessingKit
11 |
12 | class GestureTests: XCTestCase {
13 | func testFingerPressed() {
14 | let view = ProcessingView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
15 | XCTAssertEqual(view.fingerPressed, false)
16 |
17 | view.didTap(recognizer: view.tapGestureWithSingleTouch)
18 | XCTAssertEqual(view.fingerPressed, true)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ProcessingKitTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ProcessingKitTests/Input/DateTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateTests.swift
3 | // ProcessingKitTests
4 | //
5 | // Created by AtsuyaSato on 2017/09/27.
6 | // Copyright © 2017年 Atsuya Sato. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ProcessingKit
11 |
12 | class DateTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | }
17 |
18 | override func tearDown() {
19 | super.tearDown()
20 | }
21 |
22 | func testDateValue() {
23 | var components = DateComponents()
24 | components.year = 2017
25 | components.month = 5
26 | components.day = 20
27 | components.hour = 22
28 | components.minute = 50
29 | components.second = 10
30 |
31 | let calendar = Calendar(identifier: .gregorian)
32 | let date = calendar.date(from: components)
33 |
34 | guard let currentDate = date else {
35 | XCTFail()
36 | return
37 | }
38 |
39 | let dateModel = DateModel(startDate: Date(), currentDate: currentDate)
40 |
41 | XCTAssertEqual(dateModel.year(), 2017)
42 | XCTAssertEqual(dateModel.month(), 5)
43 | XCTAssertEqual(dateModel.day(), 20)
44 | XCTAssertEqual(dateModel.hour(), 22)
45 | XCTAssertEqual(dateModel.minute(), 50)
46 | XCTAssertEqual(dateModel.second(), 10)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/ProcessingKitTests/ProcessingViewDelegateSpy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessingViewDelegateSpy.swift
3 | // ProcessingKitTests
4 | //
5 | // Created by AtsuyaSato on 2017/09/26.
6 | // Copyright © 2017年 Atsuya Sato. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ProcessingKit
11 |
12 | class ProcessingViewDelegateSetupSpy: ProcessingViewDelegate {
13 | private let exception: XCTestExpectation
14 | private(set) var spyHistory: [Any] = []
15 |
16 | init(exception: XCTestExpectation) {
17 | self.exception = exception
18 | }
19 |
20 | func setup() {
21 | self.record(())
22 | exception.fulfill()
23 | }
24 |
25 | private func record(_ args: Void) {
26 | self.spyHistory += [args]
27 | }
28 | }
29 |
30 | class ProcessingViewDelegateDrawSpy: ProcessingViewDelegate {
31 | private let exception: XCTestExpectation
32 | private(set) var spyHistory: [Any] = []
33 |
34 | init(exception: XCTestExpectation) {
35 | self.exception = exception
36 | }
37 |
38 | func setup() {
39 | self.record(())
40 | }
41 |
42 | func draw() {
43 | self.record(())
44 | exception.fulfill()
45 | }
46 | private func record(_ args: Void) {
47 | self.spyHistory += [args]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ProcessingKitTests/ProcessingViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessingViewTests.swift
3 | // ProcessingViewTests
4 | //
5 | // Created by AtsuyaSato on 2017/08/04.
6 | // Copyright © 2017年 Atsuya Sato. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ProcessingKit
11 |
12 | class ProcessingViewTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | }
17 |
18 | override func tearDown() {
19 | super.tearDown()
20 | }
21 |
22 | func testCallSetup() {
23 | let view = ProcessingView(frame: CGRect.zero)
24 |
25 | let processingViewDelegateSpy = ProcessingViewDelegateSetupSpy(exception:
26 | expectation(description: "Setup")
27 | )
28 | view.delegate = processingViewDelegateSpy
29 | waitForExpectations(timeout: 100)
30 | XCTAssertEqual(processingViewDelegateSpy.spyHistory.count, 1)
31 | }
32 |
33 | func testCallDraw() {
34 | let view = ProcessingView(frame: CGRect.zero)
35 |
36 | let processingViewDelegateSpy = ProcessingViewDelegateDrawSpy(exception:
37 | expectation(description: "Draw")
38 | )
39 | view.delegate = processingViewDelegateSpy
40 | waitForExpectations(timeout: 100)
41 | print(processingViewDelegateSpy.spyHistory.count)
42 | XCTAssertEqual(processingViewDelegateSpy.spyHistory.count, 2)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/ProcessingKitTests/TransformTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransformTests.swift
3 | // ProcessingKitTests
4 | //
5 | // Created by AtsuyaSato on 2018/01/14.
6 | // Copyright © 2018年 Atsuya Sato. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ProcessingKit
11 |
12 | /* Quartz 2D
13 | https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_affine/dq_affine.html
14 |
15 | | a b 0 |
16 | [x' y' 1] = [x y 1] × | c d 0 |
17 | | tx ty 1 |
18 |
19 | y
20 | ^
21 | |
22 | |
23 | 0ーーーー> x
24 | */
25 |
26 | enum Transform {
27 | case translate(x: CGFloat, y: CGFloat)
28 | case rotate(angle: CGFloat)
29 | case shear(angleX: CGFloat, angleY: CGFloat)
30 | case scale(x: CGFloat, y: CGFloat)
31 | }
32 |
33 | class ProcessingViewDelegateTransformSpy: ProcessingViewDelegate {
34 | private let exception: XCTestExpectation
35 | private let view: ProcessingView
36 | private let transform: Transform
37 | private(set) var context: CGContext?
38 |
39 | init(exception: XCTestExpectation, view: ProcessingView, transform: Transform) {
40 | self.exception = exception
41 | self.view = view
42 | self.transform = transform
43 | }
44 |
45 | func setup() {
46 | switch transform {
47 | case .translate(let x, let y):
48 | self.view.translate(x, y)
49 | case .rotate(let angle):
50 | self.view.rotate(angle)
51 | case .shear(let x, let y):
52 | self.view.shear(x, y)
53 | case .scale(let x, let y):
54 | self.view.scale(x, y)
55 | }
56 | self.record(UIGraphicsGetCurrentContext())
57 | exception.fulfill()
58 | }
59 |
60 | private func record(_ arg: CGContext?) {
61 | self.context = arg
62 | }
63 | }
64 |
65 | class TransformTests: XCTestCase {
66 | let radians = { (angle: CGFloat) -> CGFloat in
67 | return .pi * angle / 360
68 | }
69 |
70 | override func setUp() {
71 | super.setUp()
72 | }
73 |
74 | override func tearDown() {
75 | super.tearDown()
76 | }
77 |
78 | func testTranslate() {
79 | let testCases: [UInt: TestCase] = [
80 | #line: TestCase(
81 | description: "Move 100pt to the right",
82 | transform: .translate(x: 100.0, y: 0.0),
83 | expect: CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 100.0, ty: 0.0)
84 | ),
85 | #line: TestCase(
86 | description: "Move 50pt to the bottom",
87 | transform: .translate(x: 0.0, y: 50.0),
88 | expect: CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: -50.0)
89 | ),
90 | #line: TestCase(
91 | description: "Move 40pt to the left, 20pt to the top",
92 | transform: .translate(x: -40.0, y: -20.0),
93 | expect: CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: -40.0, ty: 20.0)
94 | ),
95 | ]
96 |
97 | check(testCases: testCases)
98 | }
99 |
100 | func testRotate() {
101 | let testCases: [UInt: TestCase] = [
102 | #line: TestCase(
103 | description: "Rotate by 90 degrees",
104 | transform: .rotate(angle: radians(90)),
105 | expect: CGAffineTransform(a: cos(radians(90)), b: -sin(radians(90)), c: sin(radians(90)), d: cos(radians(90)), tx: 0.0, ty: 0.0)
106 | ),
107 | #line: TestCase(
108 | description: "Rotate by 360 degrees",
109 | transform: .rotate(angle: radians(360)),
110 | expect: CGAffineTransform(a: cos(radians(360)), b: -sin(radians(360)), c: sin(radians(360)), d: cos(radians(360)), tx: 0.0, ty: 0.0)
111 | ),
112 | #line: TestCase(
113 | description: "Rotate by -90 degrees",
114 | transform: .rotate(angle: radians(-90)),
115 | expect: CGAffineTransform(a: cos(radians(-90)), b: -sin(radians(-90)), c: sin(radians(-90)), d: cos(radians(-90)), tx: 0.0, ty: 0.0)
116 | ),
117 | ]
118 |
119 | check(testCases: testCases)
120 | }
121 |
122 | func testShear() {
123 | let testCases: [UInt: TestCase] = [
124 | #line: TestCase(
125 | description: "x-shear angle to 30°",
126 | transform: .shear(angleX: radians(30), angleY: radians(0)),
127 | expect: CGAffineTransform(a: 1.0, b: tan(radians(-0)), c: tan(radians(-30)), d: 1.0, tx: 0.0, ty: 0.0)
128 | ),
129 | #line: TestCase(
130 | description: "y-shear angle to 60°",
131 | transform: .shear(angleX: radians(0), angleY: radians(60)),
132 | expect: CGAffineTransform(a: 1.0, b: tan(radians(-60)), c: tan(radians(-0)), d: 1.0, tx: 0.0, ty: 0.0)
133 | ),
134 | #line: TestCase(
135 | description: "x-shear angle to 45° & y-shear angle to 45°",
136 | transform: .shear(angleX: radians(45), angleY: radians(45)),
137 | expect: CGAffineTransform(a: 1.0, b: tan(radians(-45)), c: tan(radians(-45)), d: 1.0, tx: 0.0, ty: 0.0)
138 | ),
139 | ]
140 | check(testCases: testCases)
141 | }
142 |
143 | func testScale() {
144 | let testCases: [UInt: TestCase] = [
145 | #line: TestCase(
146 | description: "Scale width by 2.0 times",
147 | transform: .scale(x: 2.0, y: 1.0),
148 | expect: CGAffineTransform(a: 2.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)
149 | ),
150 | #line: TestCase(
151 | description: "Scale width by 0.5 times, height by 2.0 times",
152 | transform: .scale(x: 0.5, y: 2.0),
153 | expect: CGAffineTransform(a: 0.5, b: 0.0, c: 0.0, d: 2.0, tx: 0.0, ty: 0.0)
154 | ),
155 | #line: TestCase(
156 | description: "Scale width by -2.0 times",
157 | transform: .scale(x: -2.0, y: 1.0),
158 | expect: CGAffineTransform(a: -2.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)
159 | ),
160 | ]
161 | check(testCases: testCases)
162 | }
163 |
164 | func check(testCases: [UInt: TestCase]) {
165 | _ = testCases.map { (line, testCase) in
166 | let view = ProcessingView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
167 |
168 | let transformDelegateSpy = ProcessingViewDelegateTransformSpy(
169 | exception: expectation(description: testCase.description),
170 | view: view,
171 | transform: testCase.transform
172 | )
173 |
174 | view.delegate = transformDelegateSpy
175 | waitForExpectations(timeout: 100)
176 |
177 | let actual = transformDelegateSpy.context?.ctm
178 | // Multiply scale(1.0, -1.0), translate(0.0, 100.0) and expect together for coordinate system
179 | let expected = CGAffineTransform(a: 1.0, b: 0.0, c: -0.0, d: -1.0, tx: 0.0, ty: 0.0).concatenating(testCase.expect.concatenating(CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 100.0)))
180 |
181 | XCTAssertEqual(actual, expected, String(line))
182 | }
183 | }
184 |
185 | struct TestCase {
186 | let description: String
187 | let transform: Transform
188 | let expect: CGAffineTransform
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
7 |
8 |
9 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ----------------
29 |
30 | # ProcessingKit
31 | ProcessingKit is a Visual designing library for iOS & OSX.
32 | ProcessingKit written in Swift🐧 and you can write like [processing](https://github.com/processing/processing).
33 |
34 | ## Demo
35 | 
36 |
37 | ### Demo Apps
38 | - [iOS Official Demo](https://github.com/natmark/ProcessingKit/tree/master/ProcessingKitExample)
39 | - [OSX Official Demo](https://github.com/natmark/ProcessingKit/tree/master/ProcessingKitOSXExample)
40 |
41 |
42 | #### [iPad Demo App (Developed for Open Source Conference)](https://github.com/natmark/OSCProcessingKitDemo)
43 |
44 | |Sketch Runner|Code Comparison (between Processing and ProcessingKit)|
45 | |:------------:|:----------------------------------------------------:|
46 | |||
47 |
48 | ## Example
49 | |OS|gif|code|
50 | |:---:|:------:|:------:|
51 | |iOS|
| |
52 | |OSX|  | |
53 |
54 | ## Requirements
55 | - Swift 3.0 or later
56 | - iOS 10.0 or later
57 | - OSX 10.11 or later
58 |
59 | If you use Swift 3.x, try [ProcessingKit 0.6.0](https://github.com/natmark/ProcessingKit/releases/tag/0.6.0).
60 |
61 | ## Usage
62 | 1. Create custom class that inherits from ProcessingView
63 |
64 | ```Swift
65 | import ProcessingKit
66 |
67 | class SampleView: ProcessingView {
68 | func setup() {
69 | // The setup() function is run once, when the view instantiated.
70 | }
71 | func draw() {
72 | // Called directly after setup(), the draw() function continuously executes the lines of code contained inside its block until the program is stopped or noLoop() is called.
73 | }
74 | }
75 | ```
76 |
77 | 2. Create a SampleView instance
78 | ### Create programmatically
79 | ```Swift
80 | lazy var sampleView: SampleView = {
81 | let sampleView = SampleView(frame: frame)
82 | sampleView.isUserInteractionEnabled = true // If you want to use touch events (default true)
83 | return sampleView
84 | }()
85 | ```
86 |
87 | ### Use InterfaceBuilder
88 |
89 | 1. Add UIView to ViewController
90 | 2. Select UIView & Open Identity inspector
91 | 3. Set SampleView to Custom class field
92 | 4. Add outlet connection
93 |
94 | ```Swift
95 | @IBOutlet weak var sampleView: SampleView!
96 |
97 | override func viewDidLoad() {
98 | super.viewDidLoad()
99 | sampleView.isUserInteractionEnabled = true // If you want to use touch events (default true)
100 | }
101 | ```
102 |
103 | ## Installation
104 |
105 | ### [CocoaPods](http://cocoadocs.org/docsets/ProcessingKit/)
106 | Add the following to your `Podfile`:
107 | ```
108 | pod "ProcessingKit"
109 | ```
110 |
111 | - (Example project here: [PKPodsExample](https://github.com/natmark/PKPodsExample))
112 |
113 | ### [Carthage](https://github.com/Carthage/Carthage)
114 | Add the following to your `Cartfile`:
115 | ```
116 | github "natmark/ProcessingKit"
117 | ```
118 |
119 | - (Example project here: [PKExample](https://github.com/natmark/PKExample))
120 |
121 | ## Xcode File Template
122 | - `ProcessingKit.xctemplate` is available.
123 | - Use [Donut](https://github.com/natmark/Donut)(Xcode file template manager) to install.
124 |
125 | `$ donut install https://github.com/natmark/ProcessingKit`
126 |
127 | ## Documentation
128 | - [ProcessingKit/wiki](https://github.com/natmark/ProcessingKit/wiki)
129 |
130 | ## License
131 | ProcessingKit is available under the MIT license. See the LICENSE file for more info.
132 |
--------------------------------------------------------------------------------
/Resources/OSX_Example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/OSX_Example.gif
--------------------------------------------------------------------------------
/Resources/OSX_ExampleCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/OSX_ExampleCode.png
--------------------------------------------------------------------------------
/Resources/ProcessingKit-Header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/ProcessingKit-Header.png
--------------------------------------------------------------------------------
/Resources/Storyboard-Usage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/Storyboard-Usage.png
--------------------------------------------------------------------------------
/Resources/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/demo.gif
--------------------------------------------------------------------------------
/Resources/iOS_Example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/iOS_Example.gif
--------------------------------------------------------------------------------
/Resources/iOS_ExampleCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natmark/ProcessingKit/0cc2a6fa90c69ba218df96644b0b496bf399b36a/Resources/iOS_ExampleCode.png
--------------------------------------------------------------------------------