From b9310f34653b2a7b1fd2e2b086a6aab66ddbe3bb Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 11:22:07 +0100 Subject: [PATCH 01/15] adds TipViewHighlightingBackground --- EasyTipView.xcodeproj/project.pbxproj | 4 + .../TipViewHighlightingBackground.swift | 78 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 Sources/EasyTipView/TipViewHighlightingBackground.swift diff --git a/EasyTipView.xcodeproj/project.pbxproj b/EasyTipView.xcodeproj/project.pbxproj index e2c1abd..82e7558 100644 --- a/EasyTipView.xcodeproj/project.pbxproj +++ b/EasyTipView.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1310EC961D0C53800000E71E /* EasyTipView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1310EC8B1D0C537F0000E71E /* EasyTipView.framework */; }; 13FB32A91D0C53E5001ACE20 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FB32A61D0C53E2001ACE20 /* Tests.swift */; }; + 3D0E3EA525FA2CC600899BB7 /* TipViewHighlightingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0E3EA425FA2CC600899BB7 /* TipViewHighlightingBackground.swift */; }; 3DEF6DAB23A39E4F007B8C3C /* EasyTipView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DEF6DA823A39E4F007B8C3C /* EasyTipView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DEF6DAC23A39E4F007B8C3C /* UIKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEF6DA923A39E4F007B8C3C /* UIKitExtensions.swift */; }; 3DEF6DAD23A39E4F007B8C3C /* EasyTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEF6DAA23A39E4F007B8C3C /* EasyTipView.swift */; }; @@ -30,6 +31,7 @@ 13FB32A11D0C53CB001ACE20 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/EasyTipView/info.plist; sourceTree = SOURCE_ROOT; }; 13FB32A51D0C53E2001ACE20 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; }; 13FB32A61D0C53E2001ACE20 /* Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Tests.swift; path = Tests/EasyTipViewTests/Tests.swift; sourceTree = SOURCE_ROOT; }; + 3D0E3EA425FA2CC600899BB7 /* TipViewHighlightingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipViewHighlightingBackground.swift; sourceTree = ""; }; 3DEF6DA823A39E4F007B8C3C /* EasyTipView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EasyTipView.h; sourceTree = ""; }; 3DEF6DA923A39E4F007B8C3C /* UIKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitExtensions.swift; sourceTree = ""; }; 3DEF6DAA23A39E4F007B8C3C /* EasyTipView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyTipView.swift; sourceTree = ""; }; @@ -96,6 +98,7 @@ 3DEF6DA823A39E4F007B8C3C /* EasyTipView.h */, 3DEF6DAA23A39E4F007B8C3C /* EasyTipView.swift */, 3DEF6DA923A39E4F007B8C3C /* UIKitExtensions.swift */, + 3D0E3EA425FA2CC600899BB7 /* TipViewHighlightingBackground.swift */, ); path = EasyTipView; sourceTree = ""; @@ -220,6 +223,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3D0E3EA525FA2CC600899BB7 /* TipViewHighlightingBackground.swift in Sources */, 3DEF6DAD23A39E4F007B8C3C /* EasyTipView.swift in Sources */, 3DEF6DAC23A39E4F007B8C3C /* UIKitExtensions.swift in Sources */, ); diff --git a/Sources/EasyTipView/TipViewHighlightingBackground.swift b/Sources/EasyTipView/TipViewHighlightingBackground.swift new file mode 100644 index 0000000..87c3630 --- /dev/null +++ b/Sources/EasyTipView/TipViewHighlightingBackground.swift @@ -0,0 +1,78 @@ +// +// TipViewHighlightingBackground.swift +// EasyTipView +// +// Created by Jan Lottermoser on 11.03.21. +// Copyright © 2021 teodorpatras. All rights reserved. +// + +import UIKit + +public final class TipViewHighlightingBackground: UIView { + + // MARK: - Public interface + + /// The view around which the highlighting will be shown + public var viewToHighlight: UIView? + + /// A closure to execute when the view is tapped + public var tapAction: (() -> Void)? + + /// The default margin of the highlighting circle to the frame of `viewToHighlight. + /// This property only takes effect if `circleRadius` is nil. + public var circleMargin: CGFloat = 4 + + /// The radius of the highlighting circle. + /// If this property has a non-nil value the `circleMargin` property is ignored. + public var circleRadius: CGFloat? + + // MARK: - Initialization + + public override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + private func setup() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + addGestureRecognizer(tapGesture) + } + + // MARK: - User input + @objc private func handleTap() { + tapAction?() + } + + // MARK: - Drawing and layout + + public override func draw(_ rect: CGRect) { + super.draw(rect) + + guard let viewToHighlight = viewToHighlight else { return } + + // add a mask with a cicle hole in the position of the viewToHighlight + let mask = CAShapeLayer() + let path = CGMutablePath() + + let viewFrame = viewToHighlight.superview?.convert(viewToHighlight.frame, to: self) ?? .zero + let size = max(viewFrame.width, viewFrame.height) + let radius = circleRadius ?? size / 2 + circleMargin + + path.addArc(center: viewFrame.center, radius: radius, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true) + path.addRect(bounds) + + mask.path = path + mask.fillRule = .evenOdd + self.layer.mask = mask + } + + public override func layoutSubviews() { + super.layoutSubviews() + self.setNeedsDisplay() + } +} From 359fc0796fb1be9205bce264493cbc214fc761e5 Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 12:21:27 +0100 Subject: [PATCH 02/15] better radius calculation in TipViewHighlightingBackground --- .../EasyTipView/TipViewHighlightingBackground.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/EasyTipView/TipViewHighlightingBackground.swift b/Sources/EasyTipView/TipViewHighlightingBackground.swift index 87c3630..8517c67 100644 --- a/Sources/EasyTipView/TipViewHighlightingBackground.swift +++ b/Sources/EasyTipView/TipViewHighlightingBackground.swift @@ -60,10 +60,16 @@ public final class TipViewHighlightingBackground: UIView { let path = CGMutablePath() let viewFrame = viewToHighlight.superview?.convert(viewToHighlight.frame, to: self) ?? .zero - let size = max(viewFrame.width, viewFrame.height) - let radius = circleRadius ?? size / 2 + circleMargin + let width = viewFrame.width / 2 + let height = viewFrame.height / 2 + let radius = ((width * width) + (height * height)).squareRoot() - path.addArc(center: viewFrame.center, radius: radius, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true) + path.addArc(center: viewFrame.center, + radius: circleRadius ?? radius + circleMargin, + startAngle: 0, + endAngle: 2 * CGFloat.pi, + clockwise: true) + path.addRect(bounds) mask.path = path From e779cbc78d97013dab2b00cdf0722f8b1082fb29 Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 13:24:28 +0100 Subject: [PATCH 03/15] integrates overlay into EasyTipView --- Sources/EasyTipView/EasyTipView.swift | 39 +++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Sources/EasyTipView/EasyTipView.swift b/Sources/EasyTipView/EasyTipView.swift index 3d3e1be..69b8ba2 100644 --- a/Sources/EasyTipView/EasyTipView.swift +++ b/Sources/EasyTipView/EasyTipView.swift @@ -163,6 +163,11 @@ public extension EasyTipView { transform = initialTransform alpha = initialAlpha + if preferences.highlighting.showsOverlay { + overlay.viewToHighlight = view + superview.addSubview(overlay) + } + let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap)) addGestureRecognizer(tap) @@ -174,8 +179,18 @@ public extension EasyTipView { } if animated { - UIView.animate(withDuration: preferences.animating.showDuration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: [.curveEaseInOut], animations: animations, completion: nil) - }else{ + UIView.animate(withDuration: preferences.animating.showDuration, + delay: 0, + usingSpringWithDamping: damping, + initialSpringVelocity: velocity, + options: [.curveEaseInOut], + animations: animations, + completion: nil) + + UIView.animate(withDuration: preferences.animating.showDuration * 0.2) { + self.overlay.alpha = 1 + } + } else { animations() } } @@ -256,9 +271,17 @@ open class EasyTipView: UIView { public var dismissOnTap = true } + public struct Highlighting { + public var showsOverlay = false + public var backgroundColor = UIColor.black.withAlphaComponent(0.7) + public var circleMargin = CGFloat(4) + public var circleRadius: CGFloat? = nil + } + public var drawing = Drawing() public var positioning = Positioning() public var animating = Animating() + public var highlighting = Highlighting() public var hasBorder : Bool { return drawing.borderWidth > 0 && drawing.borderColor != UIColor.clear } @@ -313,6 +336,16 @@ open class EasyTipView: UIView { fileprivate(set) open var preferences: Preferences private let content: Content + fileprivate lazy var overlay: TipViewHighlightingBackground = { + let background = TipViewHighlightingBackground(frame: UIScreen.main.bounds) + background.backgroundColor = preferences.highlighting.backgroundColor + background.alpha = 0 + background.circleRadius = preferences.highlighting.circleRadius + background.circleMargin = preferences.highlighting.circleMargin + background.tapAction = { [weak self] in self?.handleTap() } + return background + }() + // MARK: - Lazy variables - fileprivate lazy var contentSize: CGSize = { @@ -428,6 +461,7 @@ open class EasyTipView: UIView { UIView.animate(withDuration: 0.3) { self.arrange(withinSuperview: sview) + self.overlay.frame = UIScreen.main.bounds self.setNeedsDisplay() } } @@ -545,6 +579,7 @@ open class EasyTipView: UIView { @objc func handleTap() { self.delegate?.easyTipViewDidTap(self) guard preferences.animating.dismissOnTap else { return } + overlay.removeFromSuperview() dismiss() } From aebc108befcb650fef6c2566613128c606b00caa Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 14:04:51 +0100 Subject: [PATCH 04/15] animate removal of overlay --- Sources/EasyTipView/EasyTipView.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/EasyTipView/EasyTipView.swift b/Sources/EasyTipView/EasyTipView.swift index 69b8ba2..9b40951 100644 --- a/Sources/EasyTipView/EasyTipView.swift +++ b/Sources/EasyTipView/EasyTipView.swift @@ -214,6 +214,13 @@ public extension EasyTipView { self.removeFromSuperview() self.transform = CGAffineTransform.identity } + + UIView.animate(withDuration: preferences.animating.dismissDuration * 0.2) { + self.overlay.alpha = 0 + } completion: { _ in + self.overlay.removeFromSuperview() + } + } } @@ -579,7 +586,6 @@ open class EasyTipView: UIView { @objc func handleTap() { self.delegate?.easyTipViewDidTap(self) guard preferences.animating.dismissOnTap else { return } - overlay.removeFromSuperview() dismiss() } From 4f4782367283985c4463f775f5dd9d7b7cc7f73c Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 14:05:18 +0100 Subject: [PATCH 05/15] integrate highlighting overlay into demo project --- .../EasyTipView/Base.lproj/Main.storyboard | 32 +++++++++++++++++-- Example/EasyTipView/ViewController.swift | 22 +++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Example/EasyTipView/Base.lproj/Main.storyboard b/Example/EasyTipView/Base.lproj/Main.storyboard index a361efc..39ff04c 100644 --- a/Example/EasyTipView/Base.lproj/Main.storyboard +++ b/Example/EasyTipView/Base.lproj/Main.storyboard @@ -141,7 +141,7 @@ + + @@ -218,11 +240,12 @@ + - + @@ -236,6 +259,7 @@ + @@ -265,4 +289,8 @@ + + + + diff --git a/Example/EasyTipView/ViewController.swift b/Example/EasyTipView/ViewController.swift index 9aeebff..4edc7e9 100644 --- a/Example/EasyTipView/ViewController.swift +++ b/Example/EasyTipView/ViewController.swift @@ -37,6 +37,7 @@ class ViewController: UIViewController, EasyTipViewDelegate { @IBOutlet weak var buttonE: UIButton! @IBOutlet weak var buttonF: UIButton! @IBOutlet weak var buttonG: UIButton! + @IBOutlet weak var buttonH: UIButton! weak var tipView: EasyTipView? @@ -211,6 +212,27 @@ class ViewController: UIViewController, EasyTipViewDelegate { EasyTipView.show(forView: self.buttonG, contentView: contentView, preferences: preferences) + + case buttonH: + + var preferences = EasyTipView.Preferences() + preferences.drawing.backgroundColor = buttonH.backgroundColor! + preferences.drawing.foregroundColor = UIColor.white + preferences.drawing.textAlignment = NSTextAlignment.center + + preferences.drawing.arrowPosition = .top + preferences.animating.showInitialAlpha = 0 + preferences.animating.showDuration = 0.7 + preferences.animating.dismissDuration = 0.7 + preferences.animating.dismissOnTap = true + + preferences.positioning.maxWidth = 150 + preferences.positioning.bubbleInsets = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) + + preferences.highlighting.showsOverlay = true + + let view = EasyTipView(text: "Tip view with highlighting overlay", preferences: preferences) + view.show(forView: buttonH, withinSuperview: self.navigationController?.view!) default: From c4f86d3532f4d2b3c021c89a0f7306a452d9bafc Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 14:31:29 +0100 Subject: [PATCH 06/15] updates README with highlighting stuff --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30040b8..2644bc1 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Description - [x] Automatic orientation change adjustments. - [x] Fully customizable appearance (custom content view or simply just text - including `NSAttributedString` - see the Example app). - [x] Fully customizable presentation and dismissal animations. +- [x] Optional highlighting overlay. Installation @@ -134,10 +135,11 @@ tipView.dismiss() ``` Customizing the appearance -------------- -In order to customize the `EasyTipView` appearance and behavior, you can play with the `Preferences` structure which encapsulates all the customizable properties of the ``EasyTipView``. These preferences have been split into three structures: +In order to customize the `EasyTipView` appearance and behavior, you can play with the `Preferences` structure which encapsulates all the customizable properties of the ``EasyTipView``. These preferences have been split into four structures: * ```Drawing``` - encapsulates customizable properties specifying how ```EastTipView``` will be drawn on screen. * ```Positioning``` - encapsulates customizable properties specifying where ```EasyTipView``` will be drawn within its own bounds. * ```Animating``` - encapsulates customizable properties specifying how ```EasyTipView``` will animate on and off screen. +* ```Highlighting``` - encapsulates customizable properties specifying if and how ```EasyTipView``` will show a highlighting overlay. | `Drawing ` attribute | Description | |----------|-------------| @@ -177,6 +179,13 @@ In order to customize the `EasyTipView` appearance and behavior, you can play wi |`dismissDuration`|Dismiss animation duration.| |`dismissOnTap`|Prevents view from dismissing on tap if it is set to false. (Default value is true.)| +| `Highlighting ` attribute | Description | +|----------|-------------| +|`showsOverlay`| Wether or not to display a highlighting overlay. (Default value is false.) | +|`backgroundColor`| The color of the highlighting background. | +|`circleMargin`| The margin of the highlighting circle to the view the tip view is attached to.This property only takes effect if `circleRadius` is nil. | +|`circleRadius`| The radius of the highlighting circle. If this property has a non-nil value the `circleMargin` property is ignored.| + Customising the presentation or dismissal animations -------------- From c151f6a020db4e03df7e54ce51d3a2eaaf2ecf1f Mon Sep 17 00:00:00 2001 From: Jan Lottermoser Date: Tue, 16 Mar 2021 14:41:51 +0100 Subject: [PATCH 07/15] repositions button for tip view with overlay in demo project --- Example/EasyTipView/Base.lproj/Main.storyboard | 6 +++--- Example/EasyTipView/ViewController.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Example/EasyTipView/Base.lproj/Main.storyboard b/Example/EasyTipView/Base.lproj/Main.storyboard index 39ff04c..5765f41 100644 --- a/Example/EasyTipView/Base.lproj/Main.storyboard +++ b/Example/EasyTipView/Base.lproj/Main.storyboard @@ -200,7 +200,7 @@