Skip to content
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
14 changes: 7 additions & 7 deletions Demo/KSGuideControllerDemo/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
Expand Down Expand Up @@ -41,7 +41,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xYL-Z6-QZt">
<rect key="frame" x="191" y="339.5" width="80.5" height="71.5"/>
<rect key="frame" x="191" y="339.5" width="80" height="72"/>
<color key="backgroundColor" red="0.40000000600000002" green="1" blue="0.40000000600000002" alpha="1" colorSpace="calibratedRGB"/>
<state key="normal">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
Expand All @@ -60,8 +60,8 @@
<action selector="guideButtonPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="gwI-LU-QJ4"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vR7-V3-jUM">
<rect key="frame" x="279.5" y="339.5" width="79.5" height="152"/>
<button opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vR7-V3-jUM">
<rect key="frame" x="279" y="339.5" width="80" height="152"/>
<color key="backgroundColor" red="0.80000001190000003" green="1" blue="0.40000000600000002" alpha="1" colorSpace="calibratedRGB"/>
<state key="normal">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
Expand All @@ -71,7 +71,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wfy-hv-LGW">
<rect key="frame" x="191" y="419" width="36" height="72.5"/>
<rect key="frame" x="191" y="419.5" width="36" height="72"/>
<color key="backgroundColor" red="0.40000000600000002" green="1" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
<state key="normal">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
Expand All @@ -81,7 +81,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xCa-Q3-aQ7">
<rect key="frame" x="235" y="454.5" width="36.5" height="36.5"/>
<rect key="frame" x="235" y="455" width="36" height="36"/>
<color key="backgroundColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<state key="normal">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
Expand Down
18 changes: 15 additions & 3 deletions Demo/KSGuideControllerDemo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ class ViewController: UIViewController {
super.viewDidAppear(animated)
showGuides()
}


override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for button in buttons {
if abs(button.frame.size.width - button.frame.size.height) < 10 {
button.layer.cornerRadius = button.frame.size.height / 2
button.tag = 1
} else {
button.tag = 0
}
}
}

@IBAction func guideButtonPressed(_ sender: UIButton) {
showGuides()
}
Expand All @@ -43,11 +55,11 @@ class ViewController: UIViewController {
let text = string[..<index]
if n % 2 == 0 {
// Use custom arrow image for every item, you can also set global arrow image for all items by setting the arrowImage property for a KSGuideController instance.
let item = KSGuideItem(sourceView: button, arrowImage: #imageLiteral(resourceName: "arrow"), text: String(text))
let item = KSGuideItem(sourceView: button, arrowImage: #imageLiteral(resourceName: "arrow"), text: String(text), isCircle: (button.tag == 1))
items.append(item)
} else {
// Use default arrow image
let item = KSGuideItem(sourceView: button, text: String(text))
let item = KSGuideItem(sourceView: button, text: String(text), font: UIFont.boldSystemFont(ofSize: 20), isCircle: (button.tag == 1))
items.append(item)
}
}
Expand Down
81 changes: 44 additions & 37 deletions KSGuideController/KSGuideController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
import UIKit

@objc public class KSGuideController: UIViewController {

enum Region {
case upperLeft
case upperRight
case lowerLeft
case lowerRight
}

public typealias CompletionBlock = (() -> Void)
public typealias IndexChangeBlock = ((_ index: Int, _ item: KSGuideItem) -> Void)

private var items = [KSGuideItem]()
@objc public var currentIndex: Int = -1 {
didSet {
Expand All @@ -40,12 +40,12 @@ import UIKit
private var indexWillChangeBlock: IndexChangeBlock?
private var indexDidChangeBlock: IndexChangeBlock?
private var guideKey: String?

@objc public var maskCornerRadius: CGFloat = 5
@objc public var backgroundAlpha: CGFloat = 0.7
@objc public var spacing: CGFloat = 20
@objc public var padding: CGFloat = 50
@objc public var maskInsets = UIEdgeInsets(top: -8, left: -8, bottom: -8, right: -8)
@objc public var maskInsets = UIEdgeInsets(top: -4, left: -4, bottom: -4, right: -4)
@objc public var font = UIFont.systemFont(ofSize: 14)
@objc public var textColor = UIColor.white
@objc public var arrowColor = UIColor.white
Expand All @@ -55,15 +55,15 @@ import UIKit
@objc public var animatedMask = true
@objc public var animatedText = true
@objc public var animatedArrow = true

@objc public var statusBarHidden = false

private var maskCenter: CGPoint {
get {
return CGPoint(x: hollowFrame.midX, y: hollowFrame.midY)
}
}

private var region: Region {
get {
let center = maskCenter
Expand All @@ -79,7 +79,7 @@ import UIKit
}
}
}

private var hollowFrame: CGRect {
get {
var rect: CGRect = .zero
Expand All @@ -106,22 +106,24 @@ import UIKit
return rect
}
}


// Give a nil key to ignore the cache logic.
@objc public convenience init(item: KSGuideItem, key: String?) {
self.init(items: [item], key: key)
}

// Give a nil key to ignore the cache logic.
@objc public init(items: [KSGuideItem], key: String?) {
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .custom
providesPresentationContextTransitionStyle = true
definesPresentationContext = true
modalPresentationStyle = .overCurrentContext
modalTransitionStyle = .crossDissolve
self.items.append(contentsOf: items)
self.guideKey = key
}

@objc public func show(from vc: UIViewController, completion:CompletionBlock?) {
self.completion = completion
if let key = guideKey {
Expand All @@ -132,44 +134,44 @@ import UIKit
vc.present(self, animated: true, completion: nil)
}
}

@objc public func setIndexWillChangeBlock(_ block: IndexChangeBlock?) {
indexWillChangeBlock = block
}

@objc public func setIndexDidChangeBlock(_ block: IndexChangeBlock?) {
indexDidChangeBlock = block;
}

@objc required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

@objc override public func viewDidLoad() {
super.viewDidLoad()

currentIndex = 0
}

@objc public override var prefersStatusBarHidden: Bool {
return statusBarHidden
}

@objc public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: { (ctx) in
self.configMask()
self.configViewFrames()
}, completion: nil)
}

@objc override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

private func configViews() {
view.backgroundColor = UIColor(white: 0, alpha: backgroundAlpha)

if let image = currentItem.arrowImage {
arrowImageView.image = image
} else if let image = arrowImage {
Expand All @@ -180,32 +182,36 @@ import UIKit
arrowImageView.image = arrowImageView.image?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
arrowImageView.tintColor = arrowColor
view.addSubview(arrowImageView)

self.font = currentItem.font
textLabel.textColor = textColor
textLabel.font = font
textLabel.textAlignment = .left
textLabel.text = currentItem.text
textLabel.numberOfLines = 0
view.addSubview(textLabel)

configMask()
configViewFrames()
}

private func configMask() {
let fromPath = maskLayer.path

maskLayer.fillColor = UIColor.black.cgColor
var radius = maskCornerRadius
let frame = hollowFrame
radius = min(radius, min(frame.width / 2.0, frame.height / 2.0))
if currentItem.isCircle {
radius = max(frame.width / 2.0, frame.height / 2.0)
} else {
radius = min(radius, min(frame.width / 2.0, frame.height / 2.0))
}
let highlightedPath = UIBezierPath(roundedRect: hollowFrame, cornerRadius: radius)
let toPath = UIBezierPath(rect: view.bounds)
toPath.append(highlightedPath)
maskLayer.path = toPath.cgPath
maskLayer.fillRule = kCAFillRuleEvenOdd
view.layer.mask = maskLayer

if animatedMask {
let animation = CABasicAnimation(keyPath: "path")
animation.duration = animationDuration
Expand All @@ -214,10 +220,10 @@ import UIKit
maskLayer.add(animation, forKey: nil)
}
}

private func configViewFrames() {
maskLayer.frame = view.bounds

var textRect: CGRect!
var arrowRect: CGRect!
var transform: CGAffineTransform = .identity
Expand All @@ -226,7 +232,7 @@ import UIKit
let size = currentItem.text.ks_size(of: font, maxWidth: maxWidth)
let maxX = padding + maxWidth - size.width
switch region {

case .upperLeft:
transform = CGAffineTransform(scaleX: -1, y: 1)
arrowRect = CGRect(x: hollowFrame.midX - imageSize.width / 2,
Expand All @@ -238,7 +244,7 @@ import UIKit
y: arrowRect.maxY + spacing,
width: size.width,
height: size.height)

case .upperRight:
arrowRect = CGRect(x: hollowFrame.midX - imageSize.width / 2,
y: hollowFrame.maxY + spacing,
Expand All @@ -249,7 +255,7 @@ import UIKit
y: arrowRect.maxY + spacing,
width: size.width,
height: size.height)

case .lowerLeft:
transform = CGAffineTransform(scaleX: -1, y: -1)
arrowRect = CGRect(x: hollowFrame.midX - imageSize.width / 2,
Expand All @@ -261,7 +267,7 @@ import UIKit
y: arrowRect.minY - spacing - size.height,
width: size.width,
height: size.height)

case .lowerRight:
transform = CGAffineTransform(scaleX: 1, y: -1)
arrowRect = CGRect(x: hollowFrame.midX - imageSize.width / 2,
Expand Down Expand Up @@ -302,7 +308,7 @@ import UIKit
arrowImageView.frame = arrowRect
textLabel.frame = textRect
}

@objc public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if currentIndex < items.count - 1 {
currentIndex += 1
Expand All @@ -311,3 +317,4 @@ import UIKit
}
}
}

11 changes: 10 additions & 1 deletion KSGuideController/KSGuideDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ import Foundation
@objc public static func resetAll() {
userDefaults.set(nil, forKey: dataKey)
}


@objc public static func wasShowedGuide(with key: String) -> Bool {
if var data = userDefaults.object(forKey: dataKey) as? [String: Bool] {
if let value = data[key] {
return value
}
}
return false
}

static func shouldShowGuide(with key: String) -> Bool {
if var data = userDefaults.object(forKey: dataKey) as? [String: Bool] {
if let _ = data[key] {
Expand Down
15 changes: 11 additions & 4 deletions KSGuideController/KSGuideItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@ import UIKit
@objc public class KSGuideItem: NSObject {
@objc public var sourceView: UIView?
@objc public var rect: CGRect = .zero
@objc public var isCircle = false
// arrow image for this item
@objc public var arrowImage: UIImage?
@objc public var text: String!

@objc public init(sourceView: UIView, arrowImage: UIImage? = nil, text: String) {
@objc public var font = UIFont.systemFont(ofSize: 14)

@objc public init(sourceView: UIView, arrowImage: UIImage? = nil, text: String, font: UIFont = UIFont.systemFont(ofSize: 14), isCircle: Bool = false) {
self.isCircle = isCircle
self.sourceView = sourceView
self.arrowImage = arrowImage
self.font = font
self.text = text
}
@objc public init(rect: CGRect, arrowImage: UIImage? = nil, text: String) {

@objc public init(rect: CGRect, arrowImage: UIImage? = nil, text: String, font: UIFont = UIFont.systemFont(ofSize: 14), isCircle: Bool = false) {
self.rect = rect
self.isCircle = isCircle
self.arrowImage = arrowImage
self.font = font
self.text = text
}
}