├── .DS_Store
├── .gitignore
├── SwiftyOverlay.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── saeid.basirnia.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ ├── saeid.basirnia.xcuserdatad
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ └── saeid.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── Tests
└── SwiftyOverlayTests
│ └── SwiftyOverlayTests.swift
├── Package.swift
├── SwiftyOverlay.podspec
├── SwiftyOverlay.podspec~
├── Licence.txt
├── README.md
└── Sources
└── SwiftyOverlay
├── Utilities.swift
└── GDOverlay.swift
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saeid/SwiftyOverlay/HEAD/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/SwiftyOverlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftyOverlay.xcodeproj/project.xcworkspace/xcuserdata/saeid.basirnia.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saeid/SwiftyOverlay/HEAD/SwiftyOverlay.xcodeproj/project.xcworkspace/xcuserdata/saeid.basirnia.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/SwiftyOverlay.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Tests/SwiftyOverlayTests/SwiftyOverlayTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SwiftyOverlay
3 |
4 | final class SwiftyOverlayTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(SwiftyOverlay().text, "Hello, World!")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SwiftyOverlay.xcodeproj/xcuserdata/saeid.basirnia.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SwiftyOverlay.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftyOverlay",
8 | dependencies: [],
9 | targets: [
10 | .target(
11 | name: "SwiftyOverlay",
12 | dependencies: []),
13 | .testTarget(
14 | name: "SwiftyOverlayTests",
15 | dependencies: ["SwiftyOverlay"]),
16 | ]
17 | )
18 |
--------------------------------------------------------------------------------
/SwiftyOverlay.xcodeproj/xcuserdata/saeid.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SwiftyOverlay.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 | SwiftyOverlay.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SwiftyOverlay.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "SwiftyOverlay"
3 | s.version = "1.1.6"
4 | s.summary = "App showcase and runtime tour"
5 | s.homepage = "https://github.com/saeid/SwiftyOverlay"
6 | s.license = 'MIT'
7 | s.author = { "Saeid Basirnia" => "saeid.basirniaa@gmail.com" }
8 | s.source = { :git => "https://github.com/saeid/SwiftyOverlay.git", :tag => "1.1.6"}
9 |
10 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' }
11 | s.platform = :ios
12 | s.ios.deployment_target = '9.0'
13 | s.requires_arc = true
14 | s.swift_version = '5.0'
15 | s.source_files = 'SwiftyOverlay/**/*.{swift}'
16 | s.frameworks = 'UIKit'
17 |
18 | end
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SwiftyOverlay.podspec~:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "SwiftyOverlay"
3 | s.version = "1.1.4"
4 | s.summary = "App showcase and runtime tour"
5 | s.homepage = "https://github.com/saeid/SwiftyOverlay"
6 | s.license = 'MIT'
7 | s.author = { "Saeid Basirnia" => "saeid.basirniaa@gmail.com" }
8 | s.source = { :git => "https://github.com/saeid/SwiftyOverlay.git", :tag => "1.1.4"}
9 |
10 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '4.2' }
11 | s.platform = :ios
12 | s.ios.deployment_target = '9.0'
13 | s.requires_arc = true
14 | s.swift_version = '4.2'
15 | s.source_files = 'SwiftyOverlay/**/*.{swift}'
16 | s.frameworks = 'UIKit'
17 |
18 | end
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Licence.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Saeid Basirnia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftyOverlay
2 | A component to show instructions and info on UI at run time with easy to setup and customizable API.
3 |
4 | Supported Components are : `UITabbarItem`, `TableView`, `TabbarView`, all `UIView` controls and components!
5 |
6 | For demo project check [this repo](https://github.com/saeid/SwiftyGuideOverlay)
7 |
8 | 
9 | 
10 |
11 |
12 | ## Requirements
13 | - Xcode 9+
14 | - Swift 4+
15 | - iOS 9+
16 |
17 |
18 | ## Installation
19 |
20 | ## Swift Package Manager
21 |
22 | ```swift
23 | .package(url: "https://github.com/saeid/SwiftyOverlay.git", from: "1.1.14")
24 | ```
25 |
26 | ## Cocoapods
27 |
28 | ```
29 | source 'https://github.com/CocoaPods/Specs.git'
30 | platform :ios, '9.0'
31 | use_frameworks!
32 |
33 | target '' do
34 | pod 'SwiftyOverlay'
35 | end
36 | ```
37 | pod update
38 | pod install
39 |
40 | ## Usage
41 |
42 | Inherit `SkipOverlayDelegate`
43 | ```swift
44 | class ViewController: UIViewController, SkipOverlayDelegate
45 | ```
46 |
47 | Create an instance of GDOverlay
48 | ```swift
49 | var overlay: GDOverlay = GDOverlay()
50 | ```
51 |
52 | Set delegate
53 | ```swift
54 | overlay.delegate = self
55 | ```
56 |
57 | Set properties
58 | ```swift
59 | overlay.arrowColor = UIColor.red
60 | overlay.arrowWidth = 2.0
61 | overlay.lineType = LineType.line_bubble
62 |
63 | ...
64 |
65 | // Full properties list can be found on sample project
66 |
67 | ```
68 |
69 | Now call Overlay View Skip function to show!
70 | ```swift
71 | onSkipSignal()
72 | ```
73 |
74 | Override `onSkipSignal` function
75 | ```swift
76 | func onSkipSignal(){
77 | /// Add an attributed string over the screen
78 | overlay.drawOverlay(desc: NSMutableAttributedString)
79 |
80 | /// TableView
81 | overlay.drawOverlay(to: self.tableView, section: 0, row: 0, desc: "Description ...")
82 |
83 | /// UIBarButtonItem
84 | overlay.drawOverlay(to: barButtonItem, desc: "Description ...")
85 |
86 | /// Any other views
87 | overlay.drawOverlay(to: self.someView, desc: "Description ...", isCircle: true)
88 |
89 | /// TabBar Items
90 | overlay.drawOverlay(to: self.tabbarView, item: 0, desc: "Description ...")
91 |
92 | /// For StackViews, Eg. first view of stackview
93 | let targetView = stackView.arrangedSubviews[0]
94 | o.drawOverlay(to: targetView, desc: "Description ...", isCircle: true)
95 | }
96 | ```
97 |
98 | ## Licence
99 | SwiftyHelpOverlay is available under the MIT license. See the [LICENSE.txt](https://github.com/SaeidBsn/SwiftyGuideOverlay/blob/master/Licence.txt) file for more info.
100 |
101 |
--------------------------------------------------------------------------------
/Sources/SwiftyOverlay/Utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Utilities.swift
3 | // SwiftyGuideOverlay
4 | //
5 | // Created by Saeid Basirnia on 1/16/18.
6 | // Copyright © 2018 Saeid Basirnia. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | enum LineDirection: UInt32 {
12 | case left
13 | case right
14 |
15 | static let _count: LineDirection.RawValue = {
16 | var maxValue: UInt32 = 0
17 | while let _ = LineDirection(rawValue: maxValue) {
18 | maxValue += 1
19 | }
20 | return maxValue
21 | }()
22 |
23 | static var randomDir: LineDirection {
24 | let rand = arc4random_uniform(2)
25 | return LineDirection(rawValue: rand) ?? .left
26 | }
27 | }
28 |
29 | public enum LineType{
30 | case line_arrow
31 | case line_bubble
32 | case dash_bubble
33 | }
34 |
35 | // MARK: - helpers
36 | extension GDOverlay {
37 | private var keyWindow: UIWindow? {
38 | var keyWindow: UIWindow?
39 | if #available(iOS 13.0, *) {
40 | keyWindow = UIApplication.shared.connectedScenes
41 | .filter { $0.activationState == .foregroundActive }
42 | .map { $0 as? UIWindowScene }
43 | .compactMap { $0 }
44 | .first?.windows
45 | .filter { $0.isKeyWindow }.first
46 | } else {
47 | keyWindow = UIApplication.shared.keyWindow
48 | }
49 | return keyWindow
50 | }
51 |
52 | private var statusBarHeight: CGFloat {
53 | if #available(iOS 13.0, *) {
54 | return keyWindow?.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
55 | } else {
56 | return UIApplication.shared.statusBarFrame.height
57 | }
58 | }
59 |
60 | var topView: UIView? {
61 | return keyWindow
62 | }
63 |
64 | func calculateNavHeight(_ vc: UIViewController) -> CGFloat {
65 | guard let navigationController = vc.navigationController else { return 0 }
66 | return navigationController.navigationBar.frame.height + statusBarHeight
67 | }
68 | }
69 |
70 | extension CGRect {
71 | var center: CGPoint {
72 | return CGPoint(x: midX, y: midY)
73 | }
74 | }
75 |
76 | extension UIBezierPath {
77 | func addArrowForm(point: CGPoint, controlPoint: CGPoint, width: CGFloat, height: CGFloat){
78 | let angle: CGFloat = CGFloat(atan2f(Float(point.y - controlPoint.y), Float(point.x - controlPoint.x)))
79 | let angleAdjustment: CGFloat = CGFloat(atan2f(Float(width), Float(-height)))
80 | let distance: CGFloat = CGFloat(hypotf(Float(width), Float(height)))
81 |
82 | move(to: point)
83 | addLine(to: calculatePointFromPoint(point: point, angle: angle + angleAdjustment, distance: distance))
84 | addLine(to: point)
85 | addLine(to: calculatePointFromPoint(point: point, angle: angle - angleAdjustment, distance: distance))
86 | addLine(to: point)
87 | }
88 |
89 | private func calculatePointFromPoint(point: CGPoint, angle: CGFloat, distance: CGFloat) -> CGPoint {
90 | return CGPoint(x: CGFloat(Float(point.x) + cosf(Float(angle)) * Float(distance)), y: CGFloat(Float(point.y) + sinf(Float(angle)) * Float(distance)))
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/SwiftyOverlay.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 36FD596F200E6C5100374CA7 /* SwiftyOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 36FD596D200E6C5100374CA7 /* SwiftyOverlay.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | 36FD5977200E6C5900374CA7 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD5975200E6C5900374CA7 /* Utilities.swift */; };
12 | 36FD5978200E6C5900374CA7 /* GDOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD5976200E6C5900374CA7 /* GDOverlay.swift */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXFileReference section */
16 | 36FD596A200E6C5100374CA7 /* SwiftyOverlay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyOverlay.framework; sourceTree = BUILT_PRODUCTS_DIR; };
17 | 36FD596D200E6C5100374CA7 /* SwiftyOverlay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyOverlay.h; sourceTree = ""; };
18 | 36FD596E200E6C5100374CA7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
19 | 36FD5975200E6C5900374CA7 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; };
20 | 36FD5976200E6C5900374CA7 /* GDOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GDOverlay.swift; sourceTree = ""; };
21 | /* End PBXFileReference section */
22 |
23 | /* Begin PBXFrameworksBuildPhase section */
24 | 36FD5966200E6C5100374CA7 /* Frameworks */ = {
25 | isa = PBXFrameworksBuildPhase;
26 | buildActionMask = 2147483647;
27 | files = (
28 | );
29 | runOnlyForDeploymentPostprocessing = 0;
30 | };
31 | /* End PBXFrameworksBuildPhase section */
32 |
33 | /* Begin PBXGroup section */
34 | 36FD5960200E6C5100374CA7 = {
35 | isa = PBXGroup;
36 | children = (
37 | 36FD596C200E6C5100374CA7 /* SwiftyOverlay */,
38 | 36FD596B200E6C5100374CA7 /* Products */,
39 | );
40 | sourceTree = "";
41 | };
42 | 36FD596B200E6C5100374CA7 /* Products */ = {
43 | isa = PBXGroup;
44 | children = (
45 | 36FD596A200E6C5100374CA7 /* SwiftyOverlay.framework */,
46 | );
47 | name = Products;
48 | sourceTree = "";
49 | };
50 | 36FD596C200E6C5100374CA7 /* SwiftyOverlay */ = {
51 | isa = PBXGroup;
52 | children = (
53 | 36FD5976200E6C5900374CA7 /* GDOverlay.swift */,
54 | 36FD5975200E6C5900374CA7 /* Utilities.swift */,
55 | 36FD596D200E6C5100374CA7 /* SwiftyOverlay.h */,
56 | 36FD596E200E6C5100374CA7 /* Info.plist */,
57 | );
58 | path = SwiftyOverlay;
59 | sourceTree = "";
60 | };
61 | /* End PBXGroup section */
62 |
63 | /* Begin PBXHeadersBuildPhase section */
64 | 36FD5967200E6C5100374CA7 /* Headers */ = {
65 | isa = PBXHeadersBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | 36FD596F200E6C5100374CA7 /* SwiftyOverlay.h in Headers */,
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXHeadersBuildPhase section */
73 |
74 | /* Begin PBXNativeTarget section */
75 | 36FD5969200E6C5100374CA7 /* SwiftyOverlay */ = {
76 | isa = PBXNativeTarget;
77 | buildConfigurationList = 36FD5972200E6C5100374CA7 /* Build configuration list for PBXNativeTarget "SwiftyOverlay" */;
78 | buildPhases = (
79 | 36FD5965200E6C5100374CA7 /* Sources */,
80 | 36FD5966200E6C5100374CA7 /* Frameworks */,
81 | 36FD5967200E6C5100374CA7 /* Headers */,
82 | 36FD5968200E6C5100374CA7 /* Resources */,
83 | );
84 | buildRules = (
85 | );
86 | dependencies = (
87 | );
88 | name = SwiftyOverlay;
89 | productName = SwiftyOverlay;
90 | productReference = 36FD596A200E6C5100374CA7 /* SwiftyOverlay.framework */;
91 | productType = "com.apple.product-type.framework";
92 | };
93 | /* End PBXNativeTarget section */
94 |
95 | /* Begin PBXProject section */
96 | 36FD5961200E6C5100374CA7 /* Project object */ = {
97 | isa = PBXProject;
98 | attributes = {
99 | LastUpgradeCheck = 0930;
100 | ORGANIZATIONNAME = "Saeid Basirnia";
101 | TargetAttributes = {
102 | 36FD5969200E6C5100374CA7 = {
103 | CreatedOnToolsVersion = 9.2;
104 | LastSwiftMigration = 1020;
105 | ProvisioningStyle = Automatic;
106 | };
107 | };
108 | };
109 | buildConfigurationList = 36FD5964200E6C5100374CA7 /* Build configuration list for PBXProject "SwiftyOverlay" */;
110 | compatibilityVersion = "Xcode 8.0";
111 | developmentRegion = en;
112 | hasScannedForEncodings = 0;
113 | knownRegions = (
114 | en,
115 | Base,
116 | );
117 | mainGroup = 36FD5960200E6C5100374CA7;
118 | productRefGroup = 36FD596B200E6C5100374CA7 /* Products */;
119 | projectDirPath = "";
120 | projectRoot = "";
121 | targets = (
122 | 36FD5969200E6C5100374CA7 /* SwiftyOverlay */,
123 | );
124 | };
125 | /* End PBXProject section */
126 |
127 | /* Begin PBXResourcesBuildPhase section */
128 | 36FD5968200E6C5100374CA7 /* Resources */ = {
129 | isa = PBXResourcesBuildPhase;
130 | buildActionMask = 2147483647;
131 | files = (
132 | );
133 | runOnlyForDeploymentPostprocessing = 0;
134 | };
135 | /* End PBXResourcesBuildPhase section */
136 |
137 | /* Begin PBXSourcesBuildPhase section */
138 | 36FD5965200E6C5100374CA7 /* Sources */ = {
139 | isa = PBXSourcesBuildPhase;
140 | buildActionMask = 2147483647;
141 | files = (
142 | 36FD5977200E6C5900374CA7 /* Utilities.swift in Sources */,
143 | 36FD5978200E6C5900374CA7 /* GDOverlay.swift in Sources */,
144 | );
145 | runOnlyForDeploymentPostprocessing = 0;
146 | };
147 | /* End PBXSourcesBuildPhase section */
148 |
149 | /* Begin XCBuildConfiguration section */
150 | 36FD5970200E6C5100374CA7 /* Debug */ = {
151 | isa = XCBuildConfiguration;
152 | buildSettings = {
153 | ALWAYS_SEARCH_USER_PATHS = NO;
154 | CLANG_ANALYZER_NONNULL = YES;
155 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
156 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
157 | CLANG_CXX_LIBRARY = "libc++";
158 | CLANG_ENABLE_MODULES = YES;
159 | CLANG_ENABLE_OBJC_ARC = YES;
160 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
161 | CLANG_WARN_BOOL_CONVERSION = YES;
162 | CLANG_WARN_COMMA = YES;
163 | CLANG_WARN_CONSTANT_CONVERSION = YES;
164 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
165 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
166 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
167 | CLANG_WARN_EMPTY_BODY = YES;
168 | CLANG_WARN_ENUM_CONVERSION = YES;
169 | CLANG_WARN_INFINITE_RECURSION = YES;
170 | CLANG_WARN_INT_CONVERSION = YES;
171 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
172 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
173 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
174 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
175 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
176 | CLANG_WARN_STRICT_PROTOTYPES = YES;
177 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
178 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
179 | CLANG_WARN_UNREACHABLE_CODE = YES;
180 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
181 | CODE_SIGN_IDENTITY = "iPhone Developer";
182 | COPY_PHASE_STRIP = NO;
183 | CURRENT_PROJECT_VERSION = 1;
184 | DEBUG_INFORMATION_FORMAT = dwarf;
185 | ENABLE_STRICT_OBJC_MSGSEND = YES;
186 | ENABLE_TESTABILITY = YES;
187 | GCC_C_LANGUAGE_STANDARD = gnu11;
188 | GCC_DYNAMIC_NO_PIC = NO;
189 | GCC_NO_COMMON_BLOCKS = YES;
190 | GCC_OPTIMIZATION_LEVEL = 0;
191 | GCC_PREPROCESSOR_DEFINITIONS = (
192 | "DEBUG=1",
193 | "$(inherited)",
194 | );
195 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
196 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
197 | GCC_WARN_UNDECLARED_SELECTOR = YES;
198 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
199 | GCC_WARN_UNUSED_FUNCTION = YES;
200 | GCC_WARN_UNUSED_VARIABLE = YES;
201 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
202 | MTL_ENABLE_DEBUG_INFO = YES;
203 | ONLY_ACTIVE_ARCH = YES;
204 | SDKROOT = iphoneos;
205 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
206 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
207 | SWIFT_VERSION = 5.0;
208 | VERSIONING_SYSTEM = "apple-generic";
209 | VERSION_INFO_PREFIX = "";
210 | };
211 | name = Debug;
212 | };
213 | 36FD5971200E6C5100374CA7 /* Release */ = {
214 | isa = XCBuildConfiguration;
215 | buildSettings = {
216 | ALWAYS_SEARCH_USER_PATHS = NO;
217 | CLANG_ANALYZER_NONNULL = YES;
218 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
220 | CLANG_CXX_LIBRARY = "libc++";
221 | CLANG_ENABLE_MODULES = YES;
222 | CLANG_ENABLE_OBJC_ARC = YES;
223 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
224 | CLANG_WARN_BOOL_CONVERSION = YES;
225 | CLANG_WARN_COMMA = YES;
226 | CLANG_WARN_CONSTANT_CONVERSION = YES;
227 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
229 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
230 | CLANG_WARN_EMPTY_BODY = YES;
231 | CLANG_WARN_ENUM_CONVERSION = YES;
232 | CLANG_WARN_INFINITE_RECURSION = YES;
233 | CLANG_WARN_INT_CONVERSION = YES;
234 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
235 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
236 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
238 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
239 | CLANG_WARN_STRICT_PROTOTYPES = YES;
240 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
241 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
242 | CLANG_WARN_UNREACHABLE_CODE = YES;
243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
244 | CODE_SIGN_IDENTITY = "iPhone Developer";
245 | COPY_PHASE_STRIP = NO;
246 | CURRENT_PROJECT_VERSION = 1;
247 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
248 | ENABLE_NS_ASSERTIONS = NO;
249 | ENABLE_STRICT_OBJC_MSGSEND = YES;
250 | GCC_C_LANGUAGE_STANDARD = gnu11;
251 | GCC_NO_COMMON_BLOCKS = YES;
252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
254 | GCC_WARN_UNDECLARED_SELECTOR = YES;
255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
256 | GCC_WARN_UNUSED_FUNCTION = YES;
257 | GCC_WARN_UNUSED_VARIABLE = YES;
258 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
259 | MTL_ENABLE_DEBUG_INFO = NO;
260 | SDKROOT = iphoneos;
261 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
262 | SWIFT_VERSION = 5.0;
263 | VALIDATE_PRODUCT = YES;
264 | VERSIONING_SYSTEM = "apple-generic";
265 | VERSION_INFO_PREFIX = "";
266 | };
267 | name = Release;
268 | };
269 | 36FD5973200E6C5100374CA7 /* Debug */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | CLANG_ENABLE_MODULES = YES;
273 | CODE_SIGN_IDENTITY = "";
274 | CODE_SIGN_STYLE = Automatic;
275 | DEFINES_MODULE = YES;
276 | DEVELOPMENT_TEAM = 54N8PNS7FS;
277 | DYLIB_COMPATIBILITY_VERSION = 1;
278 | DYLIB_CURRENT_VERSION = 1;
279 | DYLIB_INSTALL_NAME_BASE = "@rpath";
280 | INFOPLIST_FILE = SwiftyOverlay/Info.plist;
281 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
282 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
283 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
284 | PRODUCT_BUNDLE_IDENTIFIER = com.saeidbsn.SwiftyOverlay;
285 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
286 | SKIP_INSTALL = YES;
287 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
288 | SWIFT_VERSION = 5.0;
289 | TARGETED_DEVICE_FAMILY = "1,2";
290 | };
291 | name = Debug;
292 | };
293 | 36FD5974200E6C5100374CA7 /* Release */ = {
294 | isa = XCBuildConfiguration;
295 | buildSettings = {
296 | CLANG_ENABLE_MODULES = YES;
297 | CODE_SIGN_IDENTITY = "";
298 | CODE_SIGN_STYLE = Automatic;
299 | DEFINES_MODULE = YES;
300 | DEVELOPMENT_TEAM = 54N8PNS7FS;
301 | DYLIB_COMPATIBILITY_VERSION = 1;
302 | DYLIB_CURRENT_VERSION = 1;
303 | DYLIB_INSTALL_NAME_BASE = "@rpath";
304 | INFOPLIST_FILE = SwiftyOverlay/Info.plist;
305 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
306 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
307 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
308 | PRODUCT_BUNDLE_IDENTIFIER = com.saeidbsn.SwiftyOverlay;
309 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
310 | SKIP_INSTALL = YES;
311 | SWIFT_VERSION = 5.0;
312 | TARGETED_DEVICE_FAMILY = "1,2";
313 | };
314 | name = Release;
315 | };
316 | /* End XCBuildConfiguration section */
317 |
318 | /* Begin XCConfigurationList section */
319 | 36FD5964200E6C5100374CA7 /* Build configuration list for PBXProject "SwiftyOverlay" */ = {
320 | isa = XCConfigurationList;
321 | buildConfigurations = (
322 | 36FD5970200E6C5100374CA7 /* Debug */,
323 | 36FD5971200E6C5100374CA7 /* Release */,
324 | );
325 | defaultConfigurationIsVisible = 0;
326 | defaultConfigurationName = Release;
327 | };
328 | 36FD5972200E6C5100374CA7 /* Build configuration list for PBXNativeTarget "SwiftyOverlay" */ = {
329 | isa = XCConfigurationList;
330 | buildConfigurations = (
331 | 36FD5973200E6C5100374CA7 /* Debug */,
332 | 36FD5974200E6C5100374CA7 /* Release */,
333 | );
334 | defaultConfigurationIsVisible = 0;
335 | defaultConfigurationName = Release;
336 | };
337 | /* End XCConfigurationList section */
338 | };
339 | rootObject = 36FD5961200E6C5100374CA7 /* Project object */;
340 | }
341 |
--------------------------------------------------------------------------------
/Sources/SwiftyOverlay/GDOverlay.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GDOverlay.swift
3 | // SwiftyGuideOverlay
4 | //
5 | // Created by Saeid Basirnia on 8/16/16.
6 | // Copyright © 2016 Saeidbsn. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SkipOverlayDelegate: AnyObject {
12 | func onSkipSignal()
13 | }
14 |
15 | public final class GDOverlay: UIView {
16 | //MARK: - Attributes
17 | fileprivate var _backColor: UIColor = UIColor.black.withAlphaComponent(0.8)
18 |
19 | public var backColor: UIColor {
20 | get {
21 | return _backColor
22 | }
23 | set {
24 | _backColor = newValue
25 | }
26 | }
27 |
28 | fileprivate var _boxBackColor: UIColor = UIColor.white.withAlphaComponent(0.05)
29 | public var boxBackColor: UIColor {
30 | get {
31 | return _boxBackColor
32 | }
33 | set {
34 | _boxBackColor = newValue
35 | }
36 | }
37 |
38 | fileprivate var _boxBorderColor: UIColor = UIColor.white
39 | public var boxBorderColor: UIColor {
40 | get {
41 | return _boxBorderColor
42 | }
43 | set {
44 | _boxBorderColor = newValue
45 | }
46 | }
47 |
48 | fileprivate var _showBorder: Bool = true
49 | public var showBorder: Bool {
50 | get {
51 | return _showBorder
52 | }
53 | set {
54 | _showBorder = newValue
55 | }
56 | }
57 |
58 | fileprivate var _lineType: LineType = .dash_bubble
59 | public var lineType: LineType {
60 | get {
61 | return _lineType
62 | }
63 | set {
64 | _lineType = newValue
65 | }
66 | }
67 |
68 | fileprivate var _arrowColor: UIColor = UIColor.white
69 | public var arrowColor: UIColor {
70 | get {
71 | return _arrowColor
72 | }
73 | set {
74 | _arrowColor = newValue
75 | }
76 | }
77 |
78 | fileprivate var _headColor: UIColor = UIColor.white
79 | public var headColor: UIColor {
80 | get {
81 | return _headColor
82 | }
83 | set {
84 | _headColor = newValue
85 | }
86 | }
87 |
88 | fileprivate var _arrowWidth: CGFloat = 2.0
89 | public var arrowWidth: CGFloat {
90 | get {
91 | return _arrowWidth
92 | }
93 | set {
94 | _arrowWidth = newValue
95 | }
96 | }
97 |
98 | fileprivate var _headRadius: CGFloat = 4.0
99 | public var headRadius: CGFloat {
100 | get {
101 | return _headRadius
102 | }
103 | set {
104 | _headRadius = newValue
105 | }
106 | }
107 |
108 | fileprivate var _highlightView: Bool = false
109 | public var highlightView: Bool {
110 | get {
111 | return _highlightView
112 | }
113 | set {
114 | _highlightView = newValue
115 | }
116 | }
117 |
118 | //MARK: - Self Init
119 | public weak var delegate: SkipOverlayDelegate? = nil
120 | fileprivate var helpView: UIView!
121 |
122 | public init(){
123 | super.init(frame: CGRect.zero)
124 |
125 | self.frame = self.topView?.frame ?? CGRect.zero
126 | self.backgroundColor = UIColor.clear
127 | }
128 |
129 | required public init?(coder aDecoder: NSCoder) {
130 | super.init(coder: aDecoder)
131 | }
132 |
133 | private func calculateCenter() -> CGPoint {
134 | let targetRect = helpView.convert(helpView.bounds , to: topView)
135 | return targetRect.center
136 | }
137 |
138 | private func initViews(_ circle: Bool, textOnly: Bool = false) {
139 | if !textOnly {
140 | let targetCenter: CGPoint = calculateCenter()
141 | self.createBackgroundView()
142 | self.createContainerView()
143 |
144 | self.topView?.addSubview(self)
145 | setupContainerViewConstraints(to: targetCenter)
146 |
147 | layoutIfNeeded()
148 | if _highlightView {
149 | self.unmaskView(targetCenter, isCircle: circle)
150 | }
151 |
152 | self.createTargetView(center: targetCenter)
153 | } else {
154 | self.createBackgroundView()
155 | self.createContainerView()
156 |
157 | self.topView?.addSubview(self)
158 | setupContainerViewConstraints()
159 |
160 | layoutIfNeeded()
161 | }
162 | }
163 |
164 | public func drawOverlay(to barButtonItem: UIBarButtonItem, desc: NSAttributedString){
165 | if let barView = barButtonItem.value(forKey: "view") as? UIView {
166 | let barFrame = barView.frame
167 | let windowRect = barView.convert(barFrame, to: topView)
168 | let v = UIView()
169 | v.frame = windowRect
170 | self.addSubview(v)
171 | helpView = v
172 | }
173 |
174 | descLabel.attributedText = desc
175 | initViews(true)
176 | }
177 |
178 | public func drawOverlay(to tabbarView: UITabBar, item: Int, desc: NSAttributedString){
179 | var vs = tabbarView.subviews.filter { $0.isUserInteractionEnabled }
180 | vs = vs.sorted(by: { $0.frame.minX < $1.frame.minX })
181 |
182 | var windowRect = vs[item].convert(vs[0].frame, to: topView)
183 |
184 | if UIDevice.current.userInterfaceIdiom == .pad {
185 | windowRect.origin.x = vs[item].frame.minX
186 | windowRect.size.width = vs[item].frame.width
187 | }
188 |
189 | let v = UIView()
190 | v.frame = windowRect
191 | self.addSubview(v)
192 |
193 | helpView = v
194 |
195 | descLabel.attributedText = desc
196 | initViews(false)
197 | }
198 |
199 | public func drawOverlay(to tableView: UITableView, section: Int, row: Int, desc: NSAttributedString) {
200 | let indexPath: IndexPath = IndexPath(row: row, section: section)
201 | let tableRect = tableView.rectForRow(at: indexPath)
202 | let windowRect = tableView.convert(tableRect, to: topView)
203 |
204 | let v = UIView()
205 | v.frame = windowRect
206 | self.addSubview(v)
207 |
208 | helpView = v
209 |
210 | descLabel.attributedText = desc
211 | initViews(false)
212 | }
213 |
214 |
215 | public func drawOverlay(desc: NSAttributedString) {
216 | initViews(false, textOnly: true)
217 | descLabel.attributedText = desc
218 | }
219 |
220 | public func drawOverlay(to view: UIView, desc: NSAttributedString, isCircle: Bool = true) {
221 | let windowRect = view.convert(view.bounds , to: topView)
222 | let v = UIView()
223 | v.frame = windowRect
224 | self.addSubview(v)
225 |
226 | helpView = v
227 |
228 | initViews(isCircle)
229 | descLabel.attributedText = desc
230 | }
231 |
232 | //MARK: - Background View
233 | fileprivate var backgroundView: UIView!
234 | private func createBackgroundView(){
235 | backgroundView = UIView()
236 | backgroundView.frame = self.frame
237 | backgroundView.isUserInteractionEnabled = true
238 | backgroundView.backgroundColor = UIColor.clear
239 | backgroundView.backgroundColor = _backColor
240 |
241 | self.addSubview(backgroundView)
242 | self.setupGestures()
243 | }
244 |
245 | private func setupGestures() {
246 | let tapGest = UITapGestureRecognizer(target: self, action: #selector(gotoNext(_:)))
247 | tapGest.numberOfTapsRequired = 1
248 | tapGest.numberOfTouchesRequired = 1
249 |
250 | self.backgroundView.addGestureRecognizer(tapGest)
251 | }
252 |
253 | @objc private func gotoNext(_ sender: UIGestureRecognizer) {
254 | self.removeFromSuperview()
255 | self.backgroundView.removeFromSuperview()
256 | self.delegate?.onSkipSignal()
257 | }
258 |
259 | //MARK: - Description Label
260 | fileprivate var descLabel: UILabel = {
261 | let lbl = UILabel()
262 | lbl.numberOfLines = 0
263 | lbl.lineBreakMode = .byWordWrapping
264 | lbl.sizeToFit()
265 | lbl.translatesAutoresizingMaskIntoConstraints = false
266 | lbl.textAlignment = .center
267 | return lbl
268 | }()
269 |
270 | private lazy var getLabelHeight: CGFloat = {
271 | let lblHeight = self.descLabel.frame.height
272 | return lblHeight
273 | }()
274 |
275 | //MARK: - Container View
276 | fileprivate var contView: UIView!
277 | private func createContainerView() {
278 | guard let topView = topView else { return }
279 |
280 | contView = UIView()
281 | contView.frame = CGRect(x: 0, y: 0, width: topView.frame.width - 60, height: 50)
282 | contView.backgroundColor = _boxBackColor
283 | if _showBorder {
284 | contView.layer.borderColor = _boxBorderColor.cgColor
285 | contView.layer.borderWidth = 2
286 | contView.layer.cornerRadius = 5
287 | }
288 | contView.translatesAutoresizingMaskIntoConstraints = false
289 | contView.addSubview(descLabel)
290 | backgroundView.addSubview(contView)
291 | setupLabelConstraints()
292 | }
293 |
294 | //MARK: - Tools
295 | private func unmaskView(_ targetPoint: CGPoint, isCircle: Bool) {
296 | let maskLayer = CAShapeLayer()
297 | let path = CGMutablePath()
298 |
299 | let radius: CGFloat = isCircle ? (max(helpView.frame.width + 20, helpView.frame.height + 10)) / 2 : 0
300 | let clipPath: CGPath = UIBezierPath(roundedRect: CGRect(x: helpView.frame.origin.x - 20, y: helpView.frame.origin.y - 10, width: helpView.frame.width + 40, height: helpView.frame.height + 20), cornerRadius: radius).cgPath
301 |
302 | path.addPath(clipPath)
303 | path.addRect(CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
304 |
305 | maskLayer.backgroundColor = UIColor.black.cgColor
306 | maskLayer.path = path
307 | maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
308 |
309 | backgroundView.layer.mask = maskLayer
310 | backgroundView.clipsToBounds = false
311 | }
312 | }
313 |
314 | // MARK: - setup constraints
315 | extension GDOverlay {
316 | fileprivate func setupLabelConstraints(){
317 | descLabel.leftAnchor.constraint(equalTo: contView.leftAnchor, constant: 10.0).isActive = true
318 | descLabel.rightAnchor.constraint(equalTo: contView.rightAnchor, constant: -10.0).isActive = true
319 | descLabel.topAnchor.constraint(equalTo: contView.topAnchor, constant: 10.0).isActive = true
320 | descLabel.bottomAnchor.constraint(equalTo: contView.bottomAnchor, constant: -10.0).isActive = true
321 | }
322 |
323 | fileprivate func setupContainerViewConstraints() {
324 | let centerX = contView.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0)
325 | centerX.isActive = true
326 | let centerY = contView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0)
327 | centerY.isActive = true
328 |
329 | let right = contView.rightAnchor.constraint(equalTo: rightAnchor, constant: -16)
330 | right.isActive = true
331 | let left = contView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16)
332 | left.isActive = true
333 |
334 | topView?.addConstraints([centerY, centerX, right, left])
335 | }
336 |
337 | fileprivate func setupContainerViewConstraints(to point: CGPoint) {
338 | let section = setSection(point)
339 | let consts = setSectionPoint(section)
340 |
341 | topView?.addConstraints(consts)
342 | }
343 | }
344 |
345 | //MARK: - Create and calculate points
346 | extension GDOverlay{
347 | private func calcCenterPoint(_ start: CGPoint, end: CGPoint) -> CGPoint {
348 | let x = (start.x + end.x) / 2
349 | let y = (start.y + end.y) / 2
350 |
351 | return CGPoint(x: x, y: y)
352 | }
353 |
354 | fileprivate func createTargetView(center: CGPoint) {
355 | let section = setSection(center)
356 | var startPoint: CGPoint!
357 | var endPoint: CGPoint!
358 | var controlPoint: CGPoint!
359 |
360 | let dir = LineDirection.randomDir
361 | let offsetTop: CGFloat = highlightView ? 20.0 : 0.0
362 | let offsetBottom: CGFloat = highlightView ? -20.0 : 0.0
363 |
364 | switch section {
365 | case 0, 1:
366 | if dir == .left {
367 | startPoint = CGPoint(x: contView.frame.midX - 50, y: contView.frame.minY - 10)
368 | endPoint = CGPoint(x: helpView.frame.midX, y: helpView.frame.maxY + offsetTop)
369 |
370 | let cp = calcCenterPoint(startPoint, end: endPoint)
371 | controlPoint = CGPoint(x: cp.x - 50, y: cp.y)
372 | } else {
373 | startPoint = CGPoint(x: contView.frame.midX, y: contView.frame.minY - 20)
374 | endPoint = CGPoint(x: helpView.frame.midX + 25, y: helpView.frame.maxY + offsetTop)
375 |
376 | let cp = calcCenterPoint(startPoint, end: endPoint)
377 | controlPoint = CGPoint(x: cp.x + 50, y: cp.y)
378 | }
379 | case 2:
380 | if dir == .left {
381 | startPoint = CGPoint(x: contView.frame.midX + contView.frame.midX / 4, y: contView.frame.minY - 10)
382 | endPoint = CGPoint(x: helpView.frame.minX + 5, y: helpView.frame.maxY + offsetTop)
383 |
384 | let cp = calcCenterPoint(startPoint, end: endPoint)
385 | controlPoint = CGPoint(x: cp.x - 50, y: cp.y)
386 | } else {
387 | startPoint = CGPoint(x: contView.frame.midX + contView.frame.midX / 4, y: contView.frame.minY - 10)
388 | endPoint = CGPoint(x: helpView.frame.midX + 5, y: helpView.frame.maxY + offsetTop)
389 |
390 | let cp = calcCenterPoint(startPoint, end: endPoint)
391 | controlPoint = CGPoint(x: cp.x + 50, y: cp.y)
392 | }
393 | case 3:
394 | if dir == .left {
395 | startPoint = CGPoint(x: contView.frame.midX - contView.frame.midX / 4, y: contView.frame.maxY + 10)
396 | endPoint = CGPoint(x: helpView.frame.midX, y: helpView.frame.minY + offsetBottom)
397 |
398 | let cp = calcCenterPoint(startPoint, end: endPoint)
399 | controlPoint = CGPoint(x: cp.x - 50, y: cp.y)
400 | } else {
401 | startPoint = CGPoint(x: contView.frame.midX - contView.frame.midX / 4, y: contView.frame.maxY + 10)
402 | endPoint = CGPoint(x: helpView.frame.maxX, y: helpView.frame.minY + offsetBottom)
403 |
404 | let cp = calcCenterPoint(startPoint, end: endPoint)
405 | controlPoint = CGPoint(x: cp.x + 50, y: cp.y)
406 | }
407 | case 4:
408 | if dir == .left {
409 | startPoint = CGPoint(x: contView.frame.maxX - 50, y: contView.frame.maxY + 8)
410 | endPoint = CGPoint(x: helpView.frame.maxX - 50, y: helpView.frame.minY + offsetBottom)
411 |
412 | let cp = calcCenterPoint(startPoint, end: endPoint)
413 | controlPoint = CGPoint(x: cp.x + 50, y: cp.y)
414 | } else {
415 | startPoint = CGPoint(x: contView.frame.midX, y: contView.frame.maxY + 10)
416 | endPoint = CGPoint(x: helpView.frame.midX - 25, y: helpView.frame.minY + offsetBottom)
417 |
418 | let cp = calcCenterPoint(startPoint, end: endPoint)
419 | controlPoint = CGPoint(x: cp.x - 50, y: cp.y)
420 | }
421 | default:
422 | break
423 | }
424 | let lineShape: CAShapeLayer!
425 | var bubbleShape: CAShapeLayer?
426 |
427 | switch _lineType {
428 | case .dash_bubble:
429 | lineShape = drawLine(startPoint: startPoint, endPoint: endPoint, controlPoint: controlPoint)
430 | lineShape.lineDashPattern = [3, 6]
431 | bubbleShape = drawHead(endPoint)
432 | case .line_arrow:
433 | lineShape = drawArrow(startPoint: startPoint, endPoint: endPoint, controlPoint: controlPoint)
434 | lineShape.lineDashPattern = nil
435 | case .line_bubble:
436 | lineShape = drawLine(startPoint: startPoint, endPoint: endPoint, controlPoint: controlPoint)
437 | lineShape.lineDashPattern = nil
438 | bubbleShape = drawHead(endPoint)
439 | }
440 |
441 | self.backgroundView.layer.addSublayer(lineShape)
442 | if let bs = bubbleShape{
443 | self.backgroundView.layer.addSublayer(bs)
444 | }
445 | animateArrow(lineShape)
446 | }
447 |
448 | fileprivate func setSection(_ targetPoint: CGPoint) -> Int {
449 | guard let topView = topView else { return 0 }
450 |
451 | let centerPoint: CGPoint = topView.center
452 | if targetPoint == centerPoint {
453 | return 0
454 | } else if targetPoint.x <= centerPoint.x && targetPoint.y < centerPoint.y {
455 | return 1
456 | } else if targetPoint.x < centerPoint.x && targetPoint.y > centerPoint.y {
457 | return 3
458 | } else if targetPoint.x > centerPoint.x && targetPoint.y < centerPoint.y {
459 | return 2
460 | } else if targetPoint.x >= centerPoint.x && targetPoint.y > centerPoint.y {
461 | return 4
462 | } else {
463 | return 1
464 | }
465 | }
466 |
467 | fileprivate func setSectionPoint(_ section: Int) -> [NSLayoutConstraint] {
468 | guard let topView = topView else { return [] }
469 |
470 | let dynamicSpace = CGFloat(arc4random_uniform(20) + 100)
471 | switch section {
472 | case 0, 1, 2:
473 | let x = contView.centerXAnchor.constraint(equalTo: topView.centerXAnchor, constant: 0.0)
474 | x.isActive = true
475 | let y = contView.topAnchor.constraint(equalTo: helpView.bottomAnchor, constant: dynamicSpace)
476 | y.isActive = true
477 |
478 | let right = contView.rightAnchor.constraint(equalTo: rightAnchor, constant: -16)
479 | right.isActive = true
480 | let left = contView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16)
481 | left.isActive = true
482 |
483 | return [x, y, left, right]
484 | case 3, 4:
485 | let x = contView.centerXAnchor.constraint(equalTo: topView.centerXAnchor, constant: 0.0)
486 | x.isActive = true
487 | let y = contView.bottomAnchor.constraint(equalTo: helpView.topAnchor, constant: -dynamicSpace)
488 | y.isActive = true
489 |
490 | let right = contView.rightAnchor.constraint(equalTo: rightAnchor, constant: -16)
491 | right.isActive = true
492 | let left = contView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16)
493 | left.isActive = true
494 |
495 | return [x, y, left, right]
496 | default:
497 | return []
498 | }
499 | }
500 | }
501 |
502 | //MARK: - Drawing lines
503 | extension GDOverlay {
504 | fileprivate func drawArrow(startPoint: CGPoint, endPoint: CGPoint, controlPoint: CGPoint) -> CAShapeLayer {
505 | let shapeLayer = CAShapeLayer()
506 | shapeLayer.fillColor = nil
507 | shapeLayer.strokeColor = _arrowColor.cgColor
508 | shapeLayer.lineWidth = _arrowWidth
509 | shapeLayer.lineJoin = CAShapeLayerLineJoin.round
510 | shapeLayer.lineCap = CAShapeLayerLineCap.round
511 |
512 | let path = UIBezierPath()
513 | path.addArrowForm(point: endPoint, controlPoint: controlPoint, width: 5, height: 10)
514 | path.addQuadCurve(to: startPoint, controlPoint: controlPoint)
515 | shapeLayer.path = path.cgPath
516 |
517 | return shapeLayer
518 | }
519 |
520 | fileprivate func drawLine(startPoint: CGPoint, endPoint: CGPoint, controlPoint: CGPoint) -> CAShapeLayer {
521 | let bez = UIBezierPath()
522 | bez.move(to: CGPoint(x: startPoint.x, y: startPoint.y))
523 | bez.addQuadCurve(to: CGPoint(x: endPoint.x, y: endPoint.y), controlPoint: controlPoint)
524 |
525 | let shape = CAShapeLayer()
526 | shape.path = bez.cgPath
527 | shape.strokeColor = _arrowColor.cgColor
528 | shape.fillColor = nil
529 | shape.lineWidth = _arrowWidth
530 | shape.lineCap = CAShapeLayerLineCap.round
531 | shape.lineJoin = CAShapeLayerLineJoin.miter
532 | shape.strokeStart = 0.0
533 | shape.strokeEnd = 0.0
534 |
535 | return shape
536 | }
537 |
538 | fileprivate func drawHead(_ endPoint: CGPoint) -> CAShapeLayer {
539 | let circlePath: UIBezierPath = UIBezierPath(arcCenter: CGPoint(x: endPoint.x, y: endPoint.y), radius: _headRadius, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
540 | let circleShape = CAShapeLayer()
541 | circleShape.path = circlePath.cgPath
542 | circleShape.fillColor = _headColor.cgColor
543 |
544 | return circleShape
545 | }
546 |
547 | fileprivate func animateArrow(_ shape1: CAShapeLayer) {
548 | let arrowAnim = CABasicAnimation(keyPath: "strokeEnd")
549 | arrowAnim.fromValue = 0.0
550 | arrowAnim.toValue = 1.0
551 | arrowAnim.duration = 0.5
552 | arrowAnim.autoreverses = false
553 | arrowAnim.fillMode = CAMediaTimingFillMode.forwards
554 | arrowAnim.isRemovedOnCompletion = false
555 |
556 | shape1.add(arrowAnim, forKey: nil)
557 | }
558 | }
559 |
560 |
--------------------------------------------------------------------------------