├── _config.yml
├── Examples
├── patternLock.png
├── AppDelegate.swift
├── Info.plist
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
└── ViewController.swift
├── .gitignore
├── SPPatternLock.podspec
├── LICENSE
├── README.md
├── Sources
├── Components.swift
└── LockScreen.swift
└── SPPatternLock.xcodeproj
└── project.pbxproj
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/Examples/patternLock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freesuraj/SPPatternLock/HEAD/Examples/patternLock.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | .DS_Store
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | *.xcworkspace
13 | !default.xcworkspace
14 | xcuserdata
15 | profile
16 | *.moved-aside
17 | DerivedData
18 | .idea/
19 |
--------------------------------------------------------------------------------
/SPPatternLock.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'SPPatternLock'
3 | s.version = '2.0.2'
4 | s.license = { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'Simple and elegant Pattern Lock for iOS'
6 | s.social_media_url = 'http://twitter.com/iosCook'
7 | s.homepage = 'https://github.com/freesuraj/SPPatternLock'
8 | s.authors = { 'Suraj Pathak' => 'freesuraj@gmail.com' }
9 | s.source = { :git => 'https://github.com/freesuraj/SPPatternLock.git', :tag => s.version }
10 | s.ios.deployment_target = '9.0'
11 | s.source_files = 'Sources/*swift'
12 | end
--------------------------------------------------------------------------------
/Examples/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SPPatternLock
4 | //
5 | // Created by Suraj Pathak on 14/2/17.
6 | // Copyright © 2017 Suraj. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 | window?.rootViewController = UINavigationController(rootViewController: ViewController())
20 | window?.makeKeyAndVisible()
21 |
22 | return true
23 | }
24 |
25 |
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Suraj Pathak
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the Software), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19 | Permission is hereby granted,.
20 |
--------------------------------------------------------------------------------
/Examples/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Examples/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://cocoadocs.org/docsets/SPPatternLock/)
2 | [](https://github.com/Carthage/Carthage)
3 | [](http://cocoadocs.org/docsets/SPPatternLock)
4 | [](http://twitter.com/iosCook)
5 | [](https://github.com/freesuraj/SPPatternlock)
6 | [](https://github.com/freesuraj/SPPatternlock)
7 |
8 | Pattern Lock for iOS
9 | ========================
10 | Revamped PatternLock for iOS written in Swift3.
11 |
12 | 
13 |
14 |
15 | * **no graphics or images**
16 | * **all colors and sizes are customizable**
17 | * **can enable closed-type(complex type) patterns**
18 | * **support for both iPhone and iPad**
19 | * **Easy to bring in and use**
20 |
21 | ### CocoaPods
22 | pod 'SPPatternLock'
23 | ### Initialization
24 |
25 | ```swift
26 | /**
27 | Initializes the main lock screen
28 |
29 | - parameter frame: `CGRect` where the screen will be drawn
30 | - parameter size: Size of the lock screen. It will create grids of size X size. Default value is 3
31 | - parameter allowClosedPattern: If set to `true`, it allows for complicated pattern. Otherwise a circle can't be used twice for a pattern
32 | - parameter config: Configuration for colors and line width, etc
33 | - parameter handler: Callback to receive the user pattern
34 | - returns: Returns the Lock screen
35 | */
36 | convenience init(frame: CGRect, size: Int = 3, allowClosedPattern: Bool = true, config: Config = Config(), handler: PatternHandlerBlock? = nil)
37 |
38 | ```
39 |
40 |
41 | ### About
42 |
43 | If you found this little tool useful, I'd love to hear about it. You can also follow me on Twitter at [@realSurajP](https://twitter.com/realSurajP)
44 |
45 |
46 | [](https://github.com/freesuraj)
47 |
--------------------------------------------------------------------------------
/Examples/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SPPatternLock
4 | //
5 | // Created by Suraj Pathak on 14/2/17.
6 | // Copyright © 2017 Suraj. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | var lockScreenView: LockScreen!
14 |
15 | lazy var slider: UISlider = {
16 | let _slider = UISlider(frame: CGRect(x: 100, y: 60, width: self.view.frame.width - 120, height: 50))
17 | _slider.minimumValue = 3
18 | _slider.maximumValue = 7
19 | _slider.addTarget(self, action: #selector(onDrag), for: .valueChanged)
20 | return _slider
21 | }()
22 |
23 | lazy var complexSwitch: UISwitch = {
24 | let _switch = UISwitch(frame: CGRect(x: 200, y: 120, width: 40, height: 50))
25 | _switch.addTarget(self, action: #selector(onSwitch), for: .valueChanged)
26 | _switch.isOn = true
27 | return _switch
28 | }()
29 |
30 | private var currentSize: Int = 3
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 |
35 | let labelSlider = UILabel(frame: CGRect(x: 10, y: 80, width: 100, height: 30))
36 | labelSlider.text = "Grid size"
37 | labelSlider.sizeToFit()
38 | view.addSubview(labelSlider)
39 | view.addSubview(slider)
40 |
41 | let labelSwitch = UILabel(frame: CGRect(x: 10, y: 130, width: 100, height: 30))
42 | labelSwitch.text = "Allow Complex"
43 | labelSwitch.sizeToFit()
44 | view.addSubview(labelSwitch)
45 | view.addSubview(complexSwitch)
46 |
47 | title = "SPPatternLock - version 2"
48 | view.backgroundColor = UIColor.lightGray
49 |
50 | updateLockScreen(withSize: currentSize, allowComplex: true)
51 | }
52 |
53 | func updateLockScreen(withSize size: Int, allowComplex: Bool) {
54 | title = "\(size)x\(size) pattern lock"
55 | if let v = lockScreenView { v.removeFromSuperview() }
56 | let lockFrame = CGRect(origin: CGPoint(x: 0, y: complexSwitch.frame.maxY+10), size: CGSize(width: view.frame.width, height: view.frame.width))
57 | // Example of using config
58 | var config = LockScreen.Config()
59 | config.lineColor = UIColor.purple
60 | lockScreenView = LockScreen(frame: lockFrame, size: size, allowClosedPattern: allowComplex, config: config) { [weak self] (pattern, order) in
61 | print(order.description)
62 | print(pattern)
63 | self?.title = "\(pattern)"
64 | }
65 | view.addSubview(lockScreenView)
66 | }
67 |
68 | override func didReceiveMemoryWarning() {
69 | super.didReceiveMemoryWarning()
70 | // Dispose of any resources that can be recreated.
71 | }
72 |
73 | func onSwitch(sender: AnyClass) {
74 | updateLockScreen(withSize: currentSize, allowComplex: complexSwitch.isOn)
75 | }
76 |
77 | func onDrag(sender: AnyClass) {
78 | let size = Int(slider.value)
79 | if size != currentSize {
80 | updateLockScreen(withSize: size, allowComplex: complexSwitch.isOn)
81 | currentSize = size
82 | }
83 | }
84 |
85 |
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/Sources/Components.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Components.swift
3 | // SPPatternLock
4 | //
5 | // Created by Suraj Pathak on 14/2/17.
6 | // Copyright © 2017 Suraj. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreGraphics
11 |
12 | /// A customizable circle that aligned together form the input for pattern lock
13 | class Circle: UIView {
14 |
15 | // *** Customizable attributes ***
16 | var outerColor = UIColor.magenta {
17 | didSet { setNeedsDisplay() }
18 | }
19 |
20 | var innercolor = UIColor.darkGray {
21 | didSet { setNeedsDisplay() }
22 | }
23 |
24 | var highlightColor = UIColor.yellow {
25 | didSet { setNeedsDisplay() }
26 | }
27 |
28 | var lineWidth: CGFloat = 5 {
29 | didSet { setNeedsDisplay() }
30 | }
31 |
32 | /// If selected is `true`, the circle will appear as filled with a smaller circle
33 | var isSelected: Bool {
34 | didSet { setNeedsDisplay() }
35 | }
36 |
37 | override init(frame: CGRect) {
38 | self.isSelected = false
39 | super.init(frame: frame)
40 | }
41 |
42 | convenience init(radius: CGFloat) {
43 | let frame = CGRect(origin: .zero, size: CGSize(width: radius*2, height: radius*2))
44 | self.init(frame: frame)
45 | backgroundColor = UIColor.clear
46 | }
47 |
48 | required init?(coder aDecoder: NSCoder) {
49 | fatalError("init(coder:) has not been implemented")
50 | }
51 |
52 | override func draw(_ rect: CGRect) {
53 | let ctx = UIGraphicsGetCurrentContext()
54 | // Outermost circle
55 | let outerRect = CGRect(origin: CGPoint(x: rect.origin.x + lineWidth, y: rect.origin.y + lineWidth), size: CGSize(width: rect.size.width - 2*lineWidth, height: rect.height - 2*lineWidth))
56 | ctx?.setLineWidth(lineWidth)
57 | ctx?.setStrokeColor(outerColor.cgColor)
58 | ctx?.fillEllipse(in: outerRect)
59 | // Second circle
60 | let innerRect = outerRect.insetBy(dx: 1, dy: 1)
61 | ctx?.setFillColor(innercolor.cgColor)
62 | ctx?.fillEllipse(in: innerRect)
63 | // For selected circles, the third circle
64 | if isSelected {
65 | let highlightRect = outerRect.insetBy(dx: 10, dy: 10)
66 | ctx?.setFillColor(highlightColor.cgColor)
67 | ctx?.fillEllipse(in: highlightRect)
68 | }
69 | }
70 |
71 | }
72 |
73 | /// A Line represents the line that will be drawn on the canvas when user drags around on the pattern lock
74 | struct Line {
75 |
76 | var fromPoint: CGPoint
77 | var toPoint: CGPoint
78 | /// Boolean to indicate if the line is a full edge or a partial edge
79 | var isFullLength: Bool
80 |
81 | init(fromPoint: CGPoint, toPoint: CGPoint, isFullLength: Bool) {
82 | self.fromPoint = fromPoint
83 | self.toPoint = toPoint
84 | self.isFullLength = isFullLength
85 | }
86 |
87 | }
88 |
89 | /// A pattern view represents the drawing of bunch of lines on the canvas. It's composed of a number of lines
90 | class PatternView: UIView {
91 | var lines: [Line] {
92 | didSet { setNeedsDisplay() }
93 | }
94 |
95 | var lineWidth: CGFloat = 5 {
96 | didSet { setNeedsDisplay() }
97 | }
98 |
99 | var lineColor: UIColor = UIColor.green {
100 | didSet { setNeedsDisplay() }
101 | }
102 |
103 | var linePointColor: UIColor = UIColor.white {
104 | didSet { setNeedsDisplay() }
105 | }
106 |
107 | override init(frame: CGRect) {
108 | lines = []
109 | super.init(frame: frame)
110 | backgroundColor = UIColor.clear
111 | }
112 |
113 | required init?(coder aDecoder: NSCoder) {
114 | fatalError("init(coder:) has not been implemented")
115 | }
116 |
117 | override func draw(_ rect: CGRect) {
118 | let ctx = UIGraphicsGetCurrentContext()
119 | ctx?.setLineWidth(lineWidth)
120 | ctx?.setStrokeColor(lineColor.cgColor)
121 | for line in lines {
122 | ctx?.move(to: line.fromPoint)
123 | ctx?.addLine(to: line.toPoint)
124 | ctx?.strokePath()
125 | let bubbleWidth = lineWidth // The top and end of the line contains a circular bubble for better UI feel
126 | let frontBubbleFrame = CGRect(origin: CGPoint(x: line.fromPoint.x - bubbleWidth, y: line.fromPoint.y - bubbleWidth), size: CGSize(width: bubbleWidth * 2, height: bubbleWidth * 2))
127 | ctx?.setFillColor(linePointColor.cgColor)
128 | ctx?.fillEllipse(in: frontBubbleFrame)
129 |
130 | if line.isFullLength {
131 | let backBubbleFrame = CGRect(origin: CGPoint(x: line.toPoint.x - bubbleWidth, y: line.toPoint.y - bubbleWidth), size: CGSize(width: bubbleWidth * 2, height: bubbleWidth * 2))
132 | ctx?.setFillColor(linePointColor.cgColor)
133 | ctx?.fillEllipse(in: backBubbleFrame)
134 | }
135 | }
136 | }
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/Sources/LockScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LockScreen.swift
3 | // SPPatternLock
4 | //
5 | // Created by Suraj Pathak on 14/2/17.
6 | // Copyright © 2017 Suraj. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class LockScreen: UIView {
12 |
13 | private let kSeed = 23
14 | private let kAlter1 = 1234
15 | private let kAlter2 = 4321
16 | private let kTagId = 222333
17 |
18 | private var selectedCircle: Circle?
19 | private var patternView: PatternView!
20 | private var oldCellIndex: Int = -1
21 | private var currentCellIndex: Int = -1
22 | private var drawnLines: [Int] = []
23 | private var finalLines: [Line] = []
24 | private var cellsInOrder: [Int] = []
25 |
26 | private var allowClosedPattern: Bool = true
27 |
28 | private var size: Int = 3
29 |
30 | private var numberOfCircles: Int {
31 | return size*size
32 | }
33 |
34 | // Public configurable values
35 | public struct Config {
36 | public var lineWidth: CGFloat = 5
37 | public var lineColor: UIColor = UIColor.white
38 | public var lineEdgeColor: UIColor = UIColor.white
39 | public var circleOuterRingColor: UIColor = UIColor.magenta
40 | public var circleInnerRingColor: UIColor = UIColor.darkGray
41 | public var circleHighlightColor: UIColor = UIColor.yellow
42 |
43 | // Public Initializer for Config
44 | public init() {}
45 | }
46 |
47 | public var config: Config = Config()
48 |
49 | public required override init(frame: CGRect) {
50 | super.init(frame: frame)
51 | }
52 |
53 | public typealias PatternHandlerBlock = ((Double,[Int]) -> Void)
54 | var patternHandler: PatternHandlerBlock?
55 |
56 | /**
57 | Initializes the main lock screen
58 |
59 | - parameter frame: `CGRect` where the screen will be drawn
60 | - parameter size: Size of the lock screen. It will create grids of size X size. Default value is 3
61 | - parameter allowClosedPattern: If set to `true`, it allows for complicated pattern. Otherwise a circle can't be used twice for a pattern
62 | - parameter config: Configuration for colors and line width, etc
63 | - parameter handler: Callback to receive the user pattern
64 | - returns: Returns the Lock screen
65 | */
66 | public convenience init(frame: CGRect, size: Int = 3, allowClosedPattern: Bool = true, config: Config = Config(), handler: PatternHandlerBlock? = nil) {
67 | self.init(frame: frame)
68 | self.size = size
69 | self.allowClosedPattern = allowClosedPattern
70 | self.config = config
71 | self.patternHandler = handler
72 | setNeedsDisplay()
73 | setupScreen()
74 | setupGestures()
75 | }
76 |
77 | required public init?(coder aDecoder: NSCoder) {
78 | fatalError("init(coder:) has not been implemented")
79 | }
80 |
81 | func setupScreen() {
82 | let grid = Double(min(frame.width, frame.height))/Double(2*size+1)
83 | let gap = grid
84 | let topOffset = grid
85 | let radius = grid/2
86 |
87 | for index in 0.. 0 { endPattern() }
123 | else { resetScreen() }
124 | } else {
125 | handlePan(at: point)
126 | }
127 | } else {
128 | let cellPosition = index(point)
129 | oldCellIndex = currentCellIndex
130 | if cellPosition >= 0 {
131 | cellsInOrder.append(currentCellIndex)
132 | perform(#selector(endPattern), with: nil, afterDelay: 0.3)
133 | }
134 | }
135 | }
136 |
137 | /// MARK: Helpers
138 |
139 | func endPattern() {
140 | patternHandler?(uniqueIdOfCurrentPattern,cellsInOrder)
141 | resetScreen()
142 | }
143 |
144 | func cell(at index: Int) -> Circle? {
145 | guard index >= 0 && index < numberOfCircles else { return nil }
146 | return viewWithTag((index/size+kSeed)*kTagId+index % size + kSeed) as? Circle
147 | }
148 |
149 | func index(of circle: Circle) -> Int {
150 | return (circle.tag/kTagId - kSeed)*size + (circle.tag % kTagId - kSeed)
151 | }
152 |
153 | func index(_ point: CGPoint) -> Int {
154 | for view in self.subviews {
155 | if let circle = view as? Circle, circle.frame.contains(point) {
156 | if circle.isSelected == false {
157 | circle.isSelected = true
158 | currentCellIndex = index(of: circle)
159 | selectedCircle = circle
160 | } else if circle.isSelected == true && allowClosedPattern == true {
161 | currentCellIndex = index(of: circle)
162 | selectedCircle = circle
163 | }
164 |
165 | let row = circle.tag/kTagId - kSeed
166 | let col = circle.tag % kTagId - kSeed
167 | return row * size + col
168 | }
169 | }
170 | return -1
171 | }
172 |
173 | func handlePan(at point: CGPoint) {
174 | oldCellIndex = currentCellIndex
175 | let cellPos = index(point) // This part will also change currentCellIndex
176 | if cellPos >= 0 && cellPos != oldCellIndex && allowClosedPattern == true {
177 |
178 | cellsInOrder.append(currentCellIndex)
179 |
180 | }else if cellPos >= 0 && cellPos != oldCellIndex && allowClosedPattern == false && cellsInOrder.count < size*size{
181 |
182 | cellsInOrder.append(currentCellIndex)
183 |
184 | }
185 | print(finalLines)
186 | if cellPos < 0 && oldCellIndex < 0 {
187 | return
188 | } else if cellPos < 0, let circle = cell(at: oldCellIndex) {
189 | let line = Line(fromPoint: circle.center, toPoint: point, isFullLength: false)
190 | patternView.lines = []
191 | patternView.lines.append(contentsOf: finalLines)
192 | patternView.lines.append(line)
193 | patternView.setNeedsDisplay()
194 | } else if cellPos >= 0 && currentCellIndex == oldCellIndex {
195 | return
196 | } else if cellPos >= 0 && oldCellIndex == -1 {
197 | return
198 | } else if cellPos >= 0 && oldCellIndex != currentCellIndex {
199 | let uniqueId = uniqueLineIdJoining(cellA: oldCellIndex, cellB: currentCellIndex)
200 | if drawnLines.index(of: uniqueId) == nil, let circle = cell(at: oldCellIndex), let selected = selectedCircle {
201 | let line = Line(fromPoint: circle.center, toPoint: selected.center, isFullLength: true)
202 | finalLines.append(line)
203 | patternView.lines = []
204 | patternView.lines.append(contentsOf: finalLines)
205 | drawnLines.append(uniqueId)
206 | }
207 | } else {
208 | return
209 | }
210 |
211 | }
212 |
213 | func uniqueLineIdJoining(cellA: Int, cellB: Int) -> Int {
214 | return abs(cellA+cellB)*kAlter1 + abs(cellA-cellB)*kAlter2
215 | }
216 |
217 | var uniqueIdOfCurrentPattern: Double {
218 | var finalNum = 0.0
219 | for index in 0..