From e542487230f2c2e61c9e15e84806c444e447ffbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fre=CC=81de=CC=81ric=20Maquin?= Date: Sun, 6 Jun 2021 22:37:38 +0200 Subject: [PATCH] Update helper methods --- CHANGELOG.md | 4 +- .../project.pbxproj | 12 ++--- .../contents.xcworkspacedata | 9 ++++ README.md | 21 ++++++--- .../Public/CoachMarkCoordinateConverter.swift | 30 ++++++++---- .../Helpers/Public/CoachMarkHelper.swift | 47 ++++--------------- 6 files changed, 65 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c8c3b5..2a708540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ Instructions follows [Semantic Versioning](http://semver.org/). Released on 2021-06-XX. ### Added -- New helper methods to deal with cutout paths that aren't anchored to a specific view. +- New helper methods to: + - deal with cutout paths that aren't anchored to a specific view; + - update all coach mark properties at once. ## [2.0.1](https://github.com/ephread/Instructions/releases/tag/2.0.1) Released on 2021-02-07. diff --git a/Examples/Instructions Example.xcodeproj/project.pbxproj b/Examples/Instructions Example.xcodeproj/project.pbxproj index 7c774d13..080a2d81 100644 --- a/Examples/Instructions Example.xcodeproj/project.pbxproj +++ b/Examples/Instructions Example.xcodeproj/project.pbxproj @@ -947,7 +947,7 @@ CODE_SIGN_STYLE = Automatic; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Example/Snapshot Tests/Supporting Files/Info.plist"; @@ -981,7 +981,7 @@ CODE_SIGN_STYLE = Automatic; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Example/Snapshot Tests/Supporting Files/Info.plist"; @@ -1014,7 +1014,7 @@ CODE_SIGN_STYLE = Automatic; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build/", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Example/Unit Tests/Supporting Files/Info.plist"; @@ -1047,7 +1047,7 @@ CODE_SIGN_STYLE = Automatic; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build/", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Example/Unit Tests/Supporting Files/Info.plist"; @@ -1078,7 +1078,7 @@ CODE_SIGN_STYLE = Automatic; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Example/UI Tests/Supporting Files/Info.plist"; @@ -1111,7 +1111,7 @@ CODE_SIGN_STYLE = Automatic; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Example/UI Tests/Supporting Files/Info.plist"; diff --git a/Instructions.xcworkspace/contents.xcworkspacedata b/Instructions.xcworkspace/contents.xcworkspacedata index 6b88a3f8..67661af2 100644 --- a/Instructions.xcworkspace/contents.xcworkspacedata +++ b/Instructions.xcworkspace/contents.xcworkspacedata @@ -59,4 +59,13 @@ + + + + + + diff --git a/README.md b/README.md index 4be13e3a..4357b595 100644 --- a/README.md +++ b/README.md @@ -294,9 +294,11 @@ var coachMark = coachMarksController.helper.makeCoachMark( ) ``` -`frame` will be the frame of `customView` converted in the `coachMarksController.view` referential, so don't have to worry about making sure the coordinates are in the appropriate referential. You can provide any kind of shape, from a simple rectangle to a complex star. +`frame` is the frame of `customView` expressed in the coordinate space of `coachMarksController.view`. +The conversion between this coordinate space and Instructions' coordinate space is handled automatically. +Any kind of shape can be provided, from a simple rectangle to a complex star. -You can also pass a frame rectangle directly if you supply the _superview_ to which it relates. +You can also pass a frame rectangle directly if you supply its coordinate space. ```swift var coachMark = coachMarksController.helper.makeCoachMark( @@ -508,15 +510,22 @@ func coachMarksController( // Once the animation is completed, we update the coach mark, // and start the display again. Since inout parameters cannot be // captured by the closure, you can use the following method to update - // the coachmark. It will only work if you paused the flow. - // - // Note: it's also possible to update the coach mark by providing a - // a frame rectangle. + // the coach mark. It will only work if you paused the flow. coachMarksController.helper.updateCurrentCoachMark(using: myView) coachMarksController.flow.resume() }) } ``` +If you need to update multiple properties on the coach mark, you may prefer using the block-based method. +When updating points of interest and cutout paths, make sure to express them in Instructions' coordinate +space, by using the provided converter. + +``` +coachMarksController.helper.updateCurrentCoachMark { coachMark, converter in + coachMark.pointOfInterest = converter.convert(point: myPoint, from: myPointSuperview) + coachMark.gapBetweenCoachMarkAndCutoutPath = 6 +} +``` ⚠️ Since the blurring overlay snapshots the view during coach mark appearance/disappearance, you should make sure that animations targeting your own view don't occur while a coach mark diff --git a/Sources/Instructions/Helpers/Public/CoachMarkCoordinateConverter.swift b/Sources/Instructions/Helpers/Public/CoachMarkCoordinateConverter.swift index 5d88f6ad..e32d0b06 100644 --- a/Sources/Instructions/Helpers/Public/CoachMarkCoordinateConverter.swift +++ b/Sources/Instructions/Helpers/Public/CoachMarkCoordinateConverter.swift @@ -10,11 +10,18 @@ public class CoachMarkCoordinateConverter { self.rootView = rootView } - public func convert(frame: CGRect, from superview: UIView?) -> CGRect { + /// Converts a rectangle from the specified coordinate space + /// to the coordinate space of Instructions. + /// + /// - Parameters: + /// - frame: A rectangle in the specified coordinate space. + /// - superview: The coordinate space in which `rect` is specified. + /// - Returns: A rectangle specified in the coordinate space of Instructions. + public func convert(rect: CGRect, from superview: UIView?) -> CGRect { // No superview, assuming frame in `instructionsRootView`'s coordinate system. guard let superview = superview else { print(ErrorMessage.Warning.anchorViewIsNotInTheViewHierarchy) - return frame + return rect } // Either `superview` and `instructionsRootView` is not in the hierarchy, @@ -22,7 +29,7 @@ public class CoachMarkCoordinateConverter { guard let superviewWindow = superview.window, let instructionsWindow = rootView.window else { print(ErrorMessage.Warning.anchorViewIsNotInTheViewHierarchy) - return rootView.convert(frame, from: superview) + return rootView.convert(rect, from: superview) } // If both windows are the same, we can directly convert, because @@ -31,21 +38,28 @@ public class CoachMarkCoordinateConverter { // This is the case when showing Instructions either in the parent // view controller or the parent window. guard superviewWindow != instructionsWindow else { - return rootView.convert(frame, from: superview) + return rootView.convert(rect, from: superview) } // 1. Converts the coordinates of the frame from its superview to its window. - let frameInWindow = superviewWindow.convert(frame, from: superview) + let rectInWindow = superviewWindow.convert(rect, from: superview) // 2. Converts the coordinates of the frame from its window to Instructions' window. - let frameInInstructionsWindow = instructionsWindow.convert(frameInWindow, - from: superviewWindow) + let rectInInstructionsWindow = instructionsWindow.convert(rectInWindow, + from: superviewWindow) // 3. Converts the coordinates of the frame from Instructions' window to // `instructionsRootView`. - return rootView.convert(frameInInstructionsWindow, from: instructionsWindow) + return rootView.convert(rectInInstructionsWindow, from: instructionsWindow) } + /// Converts a point from the specified coordinate space + /// to the coordinate space of Instructions. + /// + /// - Parameters: + /// - frame: A point in the specified coordinate space. + /// - superview: The coordinate space in which `point` is specified. + /// - Returns: A point specified in the coordinate space of Instructions. public func convert(point: CGPoint, from superview: UIView?) -> CGPoint { // No superview, assuming frame in `instructionsRootView`'s coordinate system. guard let superview = superview else { diff --git a/Sources/Instructions/Helpers/Public/CoachMarkHelper.swift b/Sources/Instructions/Helpers/Public/CoachMarkHelper.swift index 26636905..4a009f25 100644 --- a/Sources/Instructions/Helpers/Public/CoachMarkHelper.swift +++ b/Sources/Instructions/Helpers/Public/CoachMarkHelper.swift @@ -235,47 +235,20 @@ public class CoachMarkHelper { cutoutPathMaker: cutoutPathMaker) } - /// Updates the currently stored coach mark with a cutout path set to be - /// around the provided frame in the given view. The cutout path will be - /// slightly larger than the view and have rounded corners, however you can - /// bypass the default creator by providing a block. - /// - /// The point of interest (defining where the arrow will sit, horizontally) - /// will be the one provided. + /// Updates the current coach mark, using a configuration block. /// /// This method is expected to be used in the delegate, after pausing the display. /// Otherwise, there might not be such a thing as a "current coach mark". /// - /// - Parameter view: the view around which create the cutoutPath - /// - Parameter pointOfInterest: the point of interest toward which the arrow - /// should point - /// - Parameter bezierPathBlock: a block customizing the cutoutPath - @available( - swift, - deprecated: 2.1.0, - message: "Use updateCurrentCoachMark(_:) instead." - ) - public func updateCurrentCoachMark(usingFrame frame: CGRect? = nil, - pointOfInterest: CGPoint? = nil, - superview: UIView? = nil, - cutoutPathMaker: CutoutPathMaker? = nil) { - // `currentCoachMark` is inout, so binding it conditionally doesn't - // make sense, we'll have to force unwrap it later anyway since it's - // a value type. - guard flowManager.isPaused, flowManager.currentCoachMark != nil else { - print(ErrorMessage.Error.updateWentWrong) - return - } - - update(coachMark: &flowManager.currentCoachMark!, - usingFrame: frame, - pointOfInterest: pointOfInterest, - superview: superview, - cutoutPathMaker: cutoutPathMaker) - } - + /// - Parameter configure: A configuration updating the current coach mark. + /// - Parameter coachMark: The coach mark to update. + /// - Parameter frameConverter: Since the cutout path and the point of interest need + /// to be expressed in Instruction's coordinate system, you + /// can used this instance of `CoachMarkCoordinateConverter` + /// to convert rectangles and points. public func updateCurrentCoachMark( - _ configure: (inout CoachMark, CoachMarkCoordinateConverter) -> Void + _ configure: (_ coachMark: inout CoachMark, + _ frameConverter: CoachMarkCoordinateConverter) -> Void ) { // `currentCoachMark` is inout, so binding it conditionally doesn't // make sense, we'll have to force unwrap it later anyway since it's @@ -299,7 +272,7 @@ internal extension CoachMarkHelper { cutoutPathMaker: CutoutPathMaker? = nil ) { if let frame = frame { - let convertedFrame = coordinateConverter.convert(frame: frame, from: superview) + let convertedFrame = coordinateConverter.convert(rect: frame, from: superview) let bezierPath: UIBezierPath