Giter Club home page Giter Club logo

instructions's Introduction

Instructions

Build status Maintainability Coverage CocoaPods Shield Carthage compatible

Add customisable coach marks to your iOS project. Available for both iPhone and iPad.

Important

MESSAGE FROM THE MAINTAINER

Instructions is now considered deprecated. I will still fix issues, maintain compatibility with newer versions of Xcode/iOS and accept maintenance-oriented Pull Requests, but no new features should be expected. If you can, migrate to SwiftUI and take advantage of TipKit.

Table of contents

Overview

Instructions Demo

Features

Requirements

  • Xcode 13 / Swift 5+
  • iOS 14.0+

Asking Questions / Contributing

Asking questions

If you need help with something, ask a question in the Gitter room.

Contributing

If you want to contribute, look at the contributing guide.

Installation

CocoaPods

Add Instructions to your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
# Instructions is only supported for iOS 13+, but it
# can be used on older versions at your own risk,
# going as far back as iOS 9.
platform :ios, '9.0'
use_frameworks!

pod 'Instructions', '~> 2.3.0'

Then, run the following command:

$ pod install

Carthage

Add Instructions to your Cartfile:

github "ephread/Instructions" ~> 2.3.0

You can then update, build and drag the generated framework into your project:

$ carthage update
$ carthage build

Swift Package Manager

In Xcode, use File > Swift Packages > Add Package Dependency and use https://github.com/ephread/Instructions.

Manually

If you would rather stay away from both CocoaPods and Carthage, you can install Instructions manually, with the cost of managing updates yourself.

Embedded Framework

  1. Drag the Instructions.xcodeproj into the project navigator of your application's Xcode project.
  2. Still in the project navigator, select your application project. The target configuration panel should show up.
  3. Select the appropriate target and in the "General" panel, scroll down to the "Embedded Binaries" section.
  4. Click on the + button and select the "Instructions.framework" under the "Product" directory.

Usage

Getting started

Open up the controller for which you wish to display coach marks and instantiate a new CoachMarksController. You should also provide a dataSource, an object conforming to the CoachMarksControllerDataSource protocol.

class DefaultViewController: UIViewController,
                             CoachMarksControllerDataSource,
                             CoachMarksControllerDelegate {
    let coachMarksController = CoachMarksController()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.coachMarksController.dataSource = self
    }
}

Data Source

CoachMarksControllerDataSource declares three mandatory methods.

The first one asks for the number of coach marks to display. Let's pretend that you want to show only one coach mark. Note that the CoachMarksController requesting the information is supplied, allowing you to provide data for multiple CoachMarksController, within a single data source.

func numberOfCoachMarks(for coachMarksController: CoachMarksController) -> Int {
    return 1
}

The second one asks for metadata. This allows you to customise how a coach mark will position and appear but won't let you define its look (more on this later). Metadata is packaged in a struct named CoachMark. Note the parameter coachMarkAt that gives you the coach mark logical position, much like an IndexPath would do. coachMarksController provides you with an easy way to create a default CoachMark object from a given view.

let pointOfInterest = UIView()

func coachMarksController(_ coachMarksController: CoachMarksController,
                          coachMarkAt index: Int) -> CoachMark {
    return coachMarksController.helper.makeCoachMark(for: pointOfInterest)
}

The third one supplies two views (much like cellForRowAtIndexPath) in the form of a Tuple. The body view is mandatory, as it's the core of the coach mark. The arrow view is optional.

But for now, let's just return the default views provided by Instructions.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    coachMarkViewsAt index: Int,
    madeFrom coachMark: CoachMark
) -> (bodyView: UIView & CoachMarkBodyView, arrowView: (UIView & CoachMarkArrowView)?) {
    let coachViews = coachMarksController.helper.makeDefaultCoachViews(
        withArrow: true,
        arrowOrientation: coachMark.arrowOrientation
    )

    coachViews.bodyView.hintLabel.text = "Hello! I'm a Coach Mark!"
    coachViews.bodyView.nextLabel.text = "Ok!"

    return (bodyView: coachViews.bodyView, arrowView: coachViews.arrowView)
}

Starting the coach marks flow

Once the dataSource is set up, you can start displaying the coach marks. You will most likely supply self to start. While the overlay adds itself as a child of the current window (to be on top of everything), the CoachMarksController will add itself as a child of the view controller you provide. The CoachMarksController will receive size change events and react accordingly. Be careful; you can't call start in the viewDidLoad method since the view hierarchy has to be set up and ready for Instructions to work correctly.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    self.coachMarksController.start(in: .window(over: self))
}

Stopping the coach marks flow

You should always stop the flow once the view disappears. To avoid animation artefacts and timing issues, don't forget to add the following code to your viewWillDisappear method. Calling stop(immediately: true) will ensure that the flow is stopped immediately upon the disappearance of the view.

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    self.coachMarksController.stop(immediately: true)
}

You're all set. You can check the Examples/ directory provided with the library for more examples.

Advanced Usage

Customizing the overlay

You can customise the background colour of the overlay using this property:

  • overlay.backgroundColor

You can also make the overlay blur the content sitting behind it. Setting this property to anything else than nil will disable the overlay.backgroundColor:

  • overlay.blurEffectStyle: UIBlurEffectStyle?

You can make the overlay tappable. A tap on the overlay will hide the current coach mark and display the next one.

  • overlay.isUserInteractionEnabled: Bool

You can also allow touch events to be forwarded to the UIView underneath if they happen inside the cutout path…

  • overlay.isUserInteractionEnabledInsideCutoutPath: Bool

…or you can ask the entire overlay to forward touch events to the views under.

  • overlay.areTouchEventsForwarded: Bool

Warning The blurring overlay is not supported in app extensions.

Customizing default coach marks

The default coach marks provide minimum customisation options.

Available in both CoachMarkBodyDefaultView and CoachMarkArrowDefaultView:

  • background.innerColor: UIColor: the background color of the coachmark.
  • background.borderColor: UIColor: the border color of the coachmark.
  • background.highlightedInnerColor: UIColor: the background colour of the coach mark when the coach mark is highlighted.
  • background.highlightedBorderColor: UIColor: the border colour of the coach mark when the coach mark is highlighted.

Available only on CoachMarkArrowDefaultView:

  • background.cornerRadius: UIColor: the corner radius of the coach mark.

You can also customise properties on CoachMarkBodyDefaultView.hintLabel and CoachMarkBodyDefaultView.nextLabel. For instance, you can change the position of nextLabel in the coach mark:

let coachViews = coachMarksController.helper.makeDefaultCoachViews(
    withArrow: true,
    arrowOrientation: coachMark.arrowOrientation
    nextLabelPosition: .topTrailing
)

coachViews.bodyView.hintLabel.text = "Hello! I'm a Coach Mark!"
coachViews.bodyView.nextLabel.text = "Ok!"

Refer to MixedCoachMarksViewsViewController.swift and NextPositionViewController.swift for a practical example.

Providing custom views

If the default customisation options are not enough, you can provide your custom views. A coach mark comprises a body view and an arrow view. Note that the term arrow might be misleading. It doesn't have to be an actual arrow; it can be anything you want.

A body view must conform to the CoachMarkBodyView protocol. An arrow view must conform to the CoachMarkArrowView protocol. Both of them must also be subclasses of UIView.

Returning a CoachMarkBodyView view is mandatory, while returning a CoachMarkArrowView is optional.

CoachMarkBodyView Protocol

This protocol defines two properties.

  • nextControl: UIControl? { get } you must implement a getter method for this property in your view; this will let the CoachMarkController know which control should be tapped to display the next coach mark. Note that it doesn't have to be a subview; you can return the view itself.

  • highlightArrowDelegate: CoachMarkBodyHighlightArrowDelegate? If the view itself is the control receiving taps, you might want to forward its highlight state to the arrow view (so they can look like the same component). The CoachMarkController will automatically set an appropriate delegate to this property. You'll then be able to do this:

override var highlighted: Bool {
    didSet {
        self.highlightArrowDelegate?.highlightArrow(self.highlighted)
    }
}
Taking orientation into account

Remember the following method from the dataSource?

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    coachMarkViewsAt index: Int,
    madeFrom coachMark: CoachMark
) -> (bodyView: UIView & CoachMarkBodyView, arrowView: (UIView & CoachMarkArrowView)?) {
    let coachViews = coachMarksController.helper.makeDefaultCoachViews(
        withArrow: true,
        arrowOrientation: coachMark.arrowOrientation
    )
}

When providing a customised view, you need to give an arrow view with the appropriate orientation (i. e. in the case of an actual arrow, pointing upward or downward). The CoachMarkController will tell you which orientation it expects through the following property: CoachMark.arrowOrientation.

Browse the Example/ directory for more details.

Providing a custom cutout path

If you dislike how the default cutout path looks like, you can customise it by providing a block to makeCoachMark(for:). The cutout path will automatically be stored in the cutoutPath property of the returning CoachMark object:

var coachMark = coachMarksController.helper.makeCoachMark(
    for: customView,
    cutoutPathMaker: { (frame: CGRect) -> UIBezierPath in
        // This will create an oval cutout a bit larger than the view.
        return UIBezierPath(ovalIn: frame.insetBy(dx: -4, dy: -4))
    }
)

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 shape can be provided, from a simple rectangle to a complex star.

You can also pass a frame rectangle directly if you supply its coordinate space.

var coachMark = coachMarksController.helper.makeCoachMark(
    forFrame: frame,
    in: superview,
    cutoutPathMaker: { (frame: CGRect) -> UIBezierPath in
        // This will create an oval cutout a bit larger than the view.
        return UIBezierPath(ovalIn: frame.insetBy(dx: -4, dy: -4))
    }
)

Presentation Context

You can choose in which context the coach marks will be displayed, by passing it to `start(in: PresentationContext). The available contexts are:

  • .newWindow(over: UIViewController, at: UIWindowLevel?) – A new window created at the given UIWindowLevel (not available in app extensions);
  • .currentWindow(of: UIViewController) – The window displaying the given UIViewController;
  • .viewController(_: UIViewController) – In the view of the given UIViewController.

Additionally, you can also provide use window(over: UIViewController), which is a convenience static method equivalent to calling .newWindow(over: UIViewController, at: UIWindowLevelNormal + 1).

Warning Setting the window level to anything above UIWindowLevelStatusBar is not supported on iOS 13+ or when adding a blur effect on the overlay.

When the coach marks are displayed in a . newWindow context, the custom window is exposed by CoachMarkController through the rootWindow property.

Customizing how the coach mark will show

You can customise the following properties:

  • gapBetweenBodyAndArrow: CGFloat: the vertical gap between the body and the arrow in a given coach mark.

  • pointOfInterest: CGPoint?: the point toward which the arrow will face. At the moment, it's only used to shift the arrow horizontally and make it sits above or below the point of interest.

  • gapBetweenCoachMarkAndCutoutPath: CGFloat: the gap between the coach mark and the cutout path.

  • maxWidth: CGFloat: the maximum width a coach mark can take. You don't want your coach marks to be too wide, especially on iPads.

  • horizontalMargin: CGFloat is the margin (both leading and trailing) between the edges of the overlay view and the coach mark. Note that if the max-width of your coach mark is less than the width of the overlay view, your view will either stack on the left or the right, leaving space on the other side.

  • arrowOrientation: CoachMarkArrowOrientation? is the orientation of the arrow (not the coach mark, meaning setting this property to .Top will display the coach mark below the point of interest). Although it's usually pre-computed by the library, you can override it in coachMarksForIndex: or in coachMarkWillShow:.

  • isDisplayedOverCutoutPath: Bool enables the coach mark to be displayed over the cutout path; please note that arrows won't be visible if you set this property to true.

  • isOverlayInteractionEnabled: Bool is used to disable the ability to tap on the overlay to show the next coach mark on a case-by-case basis; it defaults to true.

  • isUserInteractionEnabledInsideCutoutPath: Bool is used to allow touch forwarding inside the cutout path. Take a look at TransitionFromCodeViewController, in the Example/ directory, for more information.

Animating coach marks

To animates coach marks, you will need to implement the CoachMarksControllerAnimationDelegate protocol.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    fetchAppearanceTransitionOfCoachMark coachMarkView: UIView,
    at index: Int,
    using manager: CoachMarkTransitionManager
)

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    fetchDisappearanceTransitionOfCoachMark coachMarkView: UIView,
    at index: Int,
    using manager: CoachMarkTransitionManager
)

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    fetchIdleAnimationOfCoachMark coachMarkView: UIView,
    at index: Int,
    using manager: CoachMarkAnimationManager
)

All methods from this delegate work in similar ways. First, you will need to specify the general parameters of the animation via manager.parameters properties. These properties match the configuration parameters you can provide to UIView.animate.

  • duration: TimeInterval: the total duration of the animation.

  • delay: TimeInterval: the amount of time to wait before beginning the animations

  • options: UIViewAnimationOptions: a mask of options indicating how you want to perform the animations (for regular animations).

  • keyframeOptions: UIViewKeyframeAnimationOptions: a mask of options indicating how you want to perform the animations (for keyframe animations).

Once you've set the parameters, you should provide your animations by calling manager.animate. The method signature is different, whether you are animating the idle state of coach marks or making them appear/disappear.

You should provide your animations in a block passed to the animate parameter, similarly to UIView.animate. If you need to access the animation parameters or the coach mark metadata, a CoachMarkAnimationManagementContext containing these will be provided to your animation block. You shouldn't capture a reference to the manager from the animation block.

For an implementation example, you can also look at the DelegateViewController class found in the Example directory.

Appearance and disappearance specifics

If you need to define an initial state, you should provide a block to the fromInitialState property. While directly setting values on coachMarkView in the method before calling manager.animate() might work, it's not guaranteed to.

Let users skip the tour

Control

You can provide the user with a means to skip the coach marks. First, you will need to set skipView with a UIView conforming to the CoachMarkSkipView protocol. This protocol defines a single property:

public protocol CoachMarkSkipView: AnyObject {
    var skipControl: UIControl? { get }
}

You must implement a getter method for this property in your view. This will let the CoachMarkController know which control should be tapped to skip the tour. Again, it doesn't have to be a subview; you can return the view itself.

As usual, Instructions provides a default implementation of CoachMarkSkipView named CoachMarkSkipDefaultView.

dataSource

To define how the view will position itself, you can use a method from the CoachMarkControllerDataSource protocol. This method is optional.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    constraintsForSkipView skipView: UIView,
    inParent parentView: UIView
) -> [NSLayoutConstraint]?

This method will be called by the CoachMarksController before starting the tour and whenever there is a size change. It gives you the skip button and the view in which it will be positioned and expects an array of NSLayoutConstraints in return. These constraints will define how the skip button will be placed in its parent. You should not add the constraints yourself; just return them.

Returning nil will tell the CoachMarksController to use the default constraints, which will position the skip button at the top of the screen. Returning an empty array is discouraged, as it will probably lead to an awkward positioning.

You can check the Example/ directory for more information about the skip mechanism.

Piloting the flow from the code

Should you ever need to programmatically show the coach mark, CoachMarkController.flow also provides the following methods:

func showNext(numberOfCoachMarksToSkip numberToSkip: Int = 0)
func showPrevious(numberOfCoachMarksToSkip numberToSkip: Int = 0)

You can specify the number of coach marks to skip (jumping forward or backwards to a different index).

Take a look at TransitionFromCodeViewController in the Example/ directory to see how you can leverage this method to ask the user to perform specific actions.

Using a delegate

The CoachMarkController will notify the delegate on multiple occasions. All those methods are optional.

First, when a coach mark shows. You might want to change something about the view. For that reason, the CoachMark metadata structure is passed as an inout object so that you can update it with new parameters.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    willShow coachMark: inout CoachMark,
    at index: Int
)

Second, when a coach mark disappears.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    willHide coachMark: CoachMark,
    at index: Int
)

Third, when all coach marks have been displayed. didEndShowingBySkipping specify whether the flow was completed because the user requested it to end.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    didEndShowingBySkipping skipped: Bool
)
React when the user tap the overlay

Whenever the user will tap the overlay, you will get notified through:

func shouldHandleOverlayTap(
    in coachMarksController: CoachMarksController,
    at index: Int
) -> Bool

Returning true will let Instructions continue the flow typically while returning false will interrupt it. If you choose to interrupt the flow, you're responsible for either stopping or pausing it or manually showing the next coach marks (see Piloting the flow from the code).

index is the index of the coach mark currently displayed.

Pausing and resuming the flow

It's as simple as calling coachMarksController.flow.pause() and coachMarksController.flow.resume(). While pausing, you can also choose to hide Instructions's overlay altogether (.pause(and: hideInstructions)), or only hide the overlay and retain its touch blocking capabilities (.pause(and: hideOverlay)).

Performing animations before showing coach marks

You can perform animations on views before or after showing a coach mark. For instance, you might want to collapse a table view and show only its header before referring to those headers with a coach mark. Instructions offers a simple way to insert your animations into the flow.

For instance, let's say you want to perform an animation before a coach mark shows. You'll implement some logic into the coachMarkWillShow delegate method. To ensure you don't have to hack something up and turn asynchronous animation blocks into synchronous ones, you can pause the flow, perform the animation and then start the flow again. This will ensure your UI never gets stalled.

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    willShow coachMark: inout CoachMark,
    at index: Int
) {
    // Pause to be able to play the animation and then show the coach mark.
    coachMarksController.flow.pause()

    // Run the animation
    UIView.animateWithDuration(1, animations: { () -> Void in
        
    }, completion: { (finished: Bool) -> Void in
        // 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 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 the block-based method. When updating points of interest and cutout paths, express them in Instructions' coordinate space using the provided converter.

coachMarksController.helper.updateCurrentCoachMark { coachMark, converter in
    coachMark.pointOfInterest = converter.convert(point: myPoint, from: myPointSuperview)
    coachMark.gapBetweenCoachMarkAndCutoutPath = 6
}

Warning Since the blurring overlay snapshots the view during coach mark appearance/disappearance, you should make sure that animations targeting your view don't occur while a coach mark appears or disappears. Otherwise, the animation won't be visible.

You may also want to customise the classic transparency overlay, as Instructions will fall back to using the traditional type if UIAccessibility.isReduceTransparencyEnabled returns true.

Skipping a coach mark

You can skip a given coach mark by implementing the following method defined in CoachMarksControllerDelegate:

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    coachMarkWillLoadAt index: Int
) -> Bool

coachMarkWillLoadAt: is called right before a given coach mark will show. To prevent a CoachMark from showing, you can return false from this method.

Customizing ornaments of the overlay

It's possible to add custom views which will be displayed over the overlay by implementing the following method of CoachMarksControllerDelegate:

func coachMarksController(
    _ coachMarksController: CoachMarksController,
    configureOrnamentsOfOverlay overlay: UIView
)

Just add the ornaments to the provided view (overlay), and Instructions should take care of the rest. Please note, however, that these ornaments will be displayed over the cutout but under the coach marks.

Dealing with frame changes

Since Instructions doesn't hold any reference to the views of interest, it cannot respond to their change of frame automatically.

Instructions provide two methods to deal with frame changes.

  • CoachMarkController.prepareForChange(), called before a frame change, to hide the coach mark and the cutout path.
  • CoachMarkController.restoreAfterChangeDidComplete(), called after a change of frame to show the coach mark and the cutout again.

Although you can call these methods at any time while Instructions is idle, the result will not look smooth if the coach mark is already displayed. It's better to perform the changes between two coach marks by pausing and resuming the flow. KeyboardViewController shows an example of this technique.

Usage within App Extensions

If you wish to add Instructions within App Extensions, there's additional work you need to perform. An example is available in the App Extensions Example/ directory.

Dependencies

Instructions comes with two shared schemes, Instructions and InstructionsAppExtensions. The only difference between the two is that InstructionsAppExtensions does not depend upon the UIApplication.sharedApplication(), making it suitable for App Extensions.

In the following examples, let's consider a project with two targets, one for a regular application (Instructions App Extensions Example) and another for an app extension (Keyboard Extension).

CocoaPods

If you're importing Instructions with CocoaPods, you'll need to edit your Podfile to make it look like this:

target 'Instructions App Extensions Example' do
  pod 'Instructions', '~> 2.3.0'
end

target 'Keyboard Extension' do
  pod 'InstructionsAppExtensions', '~> 2.3.0'
end

If Instructions is only imported from within the App Extension target, you don't need the first block.

When compiling either target, CocoaPods will make sure the appropriate flags are set, thus allowing/forbidding calls to UIApplication.sharedApplication(). You don't need to change your code.

Frameworks (Carthage / Manual management)

If you're importing Instructions through frameworks, you'll notice the two shared schemes. (Instructions and InstructionsAppExtensions) both result in different frameworks.

You need to embed both frameworks and link them to the proper targets. Make sure they look like these:

Instructions App Extensions Example Imgur

Keyboard Extension Imgur

If you plan to add Instructions only to the App Extension target, you don't need to add Instructions.frameworks.

Import statements

When importing Instructions from files within Instructions App Extensions Example, you should use the regular import statement:

import Instructions

However, when importing Instructions from files within Keyboard Extension, you should use the specific statement:

import InstructionsAppExtensions

Warning it's possible to import Instructions in an app extension. However, you're at a high risk of rejection from the Apple Store. Uses of UIApplication.sharedApplication() are statically checked during compilation, but nothing prevents you from performing the calls at runtime. Fortunately, Xcode should warn you if you've mistakenly linked with a framework not suited for App Extensions.

License

Instructions is released under the MIT license. See LICENSE for details.

instructions's People

Contributors

beetlab avatar bogren avatar dbasedow avatar dependabot[bot] avatar djsomen avatar eliekarouz avatar ephread avatar estebansotoara avatar florianpfisterer avatar galijot avatar gitter-badger avatar halolee avatar jasonlagaac avatar jesster2k10 avatar larsschwegmann avatar lmbcosta avatar mrezk avatar ogantopkaya avatar ong-yue-huei avatar rimckenn avatar rivera-ernesto avatar robert-cronin avatar sairamkotha avatar snq-2001 avatar tony-yun avatar toshi0383 avatar twomedia avatar weakfl avatar yalishanda42 avatar yas375 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

instructions's Issues

Rotating device duplicates marks

Rotating the device during instructions from portrait to landscape (or the other way round) duplicates the coach marks and messes up the instructions. Reproduced by running the example code.

Maybe the easiest solution is to disable device rotation when instructions are ongoing but I wonder if there is a better solution.

carthage install error

Hi,

I run carthage update based on the entry

github "ephead/Instructions" ~> 0.4

and get the following error:

*** Cloning Instructions
A shell task failed with exit code 128:
fatal: could not read Username for 'https://github.com': terminal prompts disabled

Any ideas?

Show message-only coachmarks

Sometimes it is preferable to display just a text, without a next button or a separator. It seems Instructions does not support this at the moment. But I think it would be a good addition.

Help in adding Instructions Manually

Hi
Would love for some help...
I'm trying to add Instructions manually, as I'm getting some issues when trying add any Swift project to my Objective-C project (setting my pods to "use_frameworks!" is what does the problem - not related specifically to Instructions).

So I follow the instructions, but:

  1. When adding the "Instructions.xcodeproj" it only adds this file - shouldn't I add the entire source files library?
  2. After doing this, I was following steps 2-4, but on 4, I couldn't find the file "Instructions.xcodeproj".

What am I doing wrong?

Question regarding the usage of hyphenationFactor

Hi!
Two questions please:
1.I'm interested to know what is design reason behind using hyphen word break on CoachMarkBodyDefaultView?
2. I see that hyphenationFactor's documentation states that its value should be between 0.0 to 1.0. But I see it is set to 2.0... What is the reason for this?

Use with SpriteKit??

Is it possible to use this with sprite-kit? I think you can cast and skView to a UIVIew. I'll try it out but it would be great if it were a new feature!

CochMarkView remains on screen if i touch inside cutout view

If I press back button inside cutout view, or for matter of fact any button or action. View still remains on screen. Can we add anything that can skip to next coachmarkview.?

For example, If I have small expandable cell with "see more" and I am instructing user to see more but i want next cutout to appear upon clicking on "see more" rather than on ok.

Skip certain coachMark

Hi there. Great work on Instructions!

Is there a way to skip a certain coachMark programatically? E.g. getting access to showNextCoachMark().

My use case is:

  1. Display keyboard during a coachMark
  2. Wait for the user's input
  3. Progress to the next coachMark after user has selected 'return' from the keyboard

I would like to skip the current coachMark in step 3, so the next coachMark is displayed after user 'returns'.

Restarting the marks

I'm having an issue where I run through my coach marks and then try to do it again and nothing loads. Is there an easy way to rewind to the start?

delay function is never used

the function

public func delay(delay:Double, closure:()->())

in Instructions.swift is never used within the project.

Error when add Instructions into Objective C project manually

I have a problem when i import Instructions into my project manually :
screen shot 2016-03-05 at 11 12 44 am

and this if i create a new file swift and copy all code in instructionmanagement to new file.
screen shot 2016-03-05 at 11 16 22 am

I dont know how to install Instrction by cocoapods with my current podfile.Can you help me.
screen shot 2016-03-05 at 11 17 38 am

CoachMarkBodyView shows on the end of the window

Hey guys,

I'm trying to create a coachmark to a view that has this hierarchy:

UIScollView -> UICollectionView -> UICollectionViewCell -> UIView

When coach marks tries to show it shows below the window, when it should show up on my view.

I needed to use the "gapBetweenCoachMarkAndCutoutPath" to workaround this problem.

Objective-C

Are there any plans to release this control for objective-c?

Looks fantastic, by the way!

Ambiguous use of 'coachMarkForView(_:bezierPathBlock:)'

in the method:

func coachMarksController(coachMarksController: CoachMarksController, coachMarksForIndex index: Int)
    -> CoachMark {
      var coachMark : CoachMark
      switch index {
      case 0:
        coachMark = coachMarksController.coachMarkForView(self.collectionView)
      case 1:
        coachMark = coachMarksController.coachMarkForView(self.imageButton)
      default:
        break
      }
      coachMark.gapBetweenCoachMarkAndCutoutPath = 6.0
      return coachMark
  }

when I try to assign the point of interest (at case 0)

 coachMark = coachMarksController.coachMarkForView(self.collectionView)

I get the error: Ambiguous use of 'coachMarkForView(:bezierPathBlock:)_
The case 1 works fine.

Crashing in CoachMarkDisplayManager

Thread : Crashed: com.apple.main-thread
0 Instructions 0x1006d0a58 CoachMarkDisplayManager.(positionCoachMarkView in _E6181A788EC6911C68FBD3A4D57722C9)() -> () (CoachMarkDisplayManager.swift:261)
1 Instructions 0x1006cfbe8 CoachMarkDisplayManager.(prepareCoachMarkForDisplay in _E6181A788EC6911C68FBD3A4D57722C9)() -> () (CoachMarkDisplayManager.swift:154)
2 Instructions 0x1006cf488 CoachMarkDisplayManager.displayCoachMarkView(CoachMarkView, coachMark : CoachMark, completion : () -> ()?) -> () (CoachMarkDisplayManager.swift:91)
3 Instructions 0x1006d4bb8 CoachMarksController.(createAndShowCoachMark in _BC66BB5D8A069CE78924A87F8EC01AC4)(shouldCallDelegate : Bool) -> () (CoachMarksController.swift:530)
4 Instructions 0x1006d8514 CoachMarksController.(startOn(CoachMarksController) -> (UIViewController) -> ()).(closure #2) (CoachMarksController.swift:486)
5 UIKit 0x1878d055c -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 408
6 UIKit 0x1878d00c4 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 188
7 UIKit 0x1878cffcc -[UIViewAnimationState animationDidStop:finished:] + 104
8 QuartzCore 0x1871d962c CA::Layer::run_animation_callbacks(void*) + 296
9 libdispatch.dylib 0x19510d954 _dispatch_client_callout + 16
10 libdispatch.dylib 0x19511220c _dispatch_main_queue_callback_4CF + 1608
11 CoreFoundation 0x182dd3544 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE + 12
12 CoreFoundation 0x182dd15ec __CFRunLoopRun + 1492
13 CoreFoundation 0x182cfcf74 CFRunLoopRunSpecific + 396
14 GraphicsServices 0x18c75f6fc GSEventRunModal + 168
15 UIKit 0x1878fed94 UIApplicationMain + 1488
16 Tether 0x100030ef8 main (AppDelegate.swift:15)
17 libdyld.dylib 0x19513aa08 start + 4

Sample Objective-C

Hi,

Have you a little sample of implementation in Objective-C ?

Thanks

Customizing view cutout

Hey,

Thanks a lot for this library. I was wondering if you could tell me how to customize the cutout path. Here's a screenshot of what I'm getting.

http://imgur.com/q26iERN

I would really like to "cut" the three edges that are visible in my cutout. On the bottom and on the right side of my visible view, I have black bars. And at the top, I have a yellow bar. I've also got a small yellow portion at the bottom-right portion of the visible view. I would like to customize the visible view cutout so that all I see is the center yellow part. I just need to cut the little bits that are on the sides, to make it look cleaner, and uni-coloured. How can I do that ?

Merci beaucoup de nous fournir cette ressource géniale. Cela me fait économiser énormément de temps pour mon premier project sérieux avec Swift et iOS.

Merci,
Un concitoyen originaire du 92 :)

showNextCoachMark has bad logic causing crash

I am conditionally hiding my first coach mark. When I do hide it, my app crashes because of this line of code. It looks like self.currentIndex is increment before it is checked for 0, so the if statement will run 100% of the time.

private func showNextCoachMark(hidePrevious hidePrevious: Bool = true) {
        self.currentIndex += 1

        // if `currentIndex` is above 0, that means a previous coach mark
        // is displayed. We call the delegate to notify that the current coach
        // mark will disappear, and only then, we hide the coach mark.
        if self.currentIndex > 0 {
            self.delegate?.coachMarksController(self, coachMarkWillDisappear: self.currentCoachMark!, forIndex: self.currentIndex - 1)

            if hidePrevious {
                self.coachMarkDisplayManager.hideCoachMarkView(self.currentCoachMarkView, animationDuration: self.currentCoachMark!.animationDuration) {
                    self.removeTargetFromCurrentCoachView()

                    if self.currentIndex < self.numberOfCoachMarks {
                        self.createAndShowCoachMark()
                    } else {
                        self.stop()
                    }
                }
            } else {
                if self.currentIndex < self.numberOfCoachMarks {
                    self.createAndShowCoachMark()
                } else {
                    self.stop()
                }
            }
        } else {
            self.createAndShowCoachMark()
        }
    }

Skip View Location

How do I change the location of CoachMarkSkipDefaultView from right top corner to left top corner? Or use the frame of another object as my skip view?

Any help would be greatly appreciated!

Thanks!

Multiple coaches?

This project looks awesome, but I was looking for something that supports multiple instructions on the screen at the same time. Any plans for this? Thanks in advance!

Prevent tap on custom CustomCoachMarkBodyView and coachMark?

Hi,

is is possible to prevent the tap events on my coach marks? I just want to show the walkthrough and it shouldn't be possible to tap on the highlighted button for example.

I also wonder why my overlay tap is not working properly. When I tap on the hint text on my custom body view I can't go a step further. I already set up the property "allowOverlayTap" to true and disabled user interaction in my custom body view class on any view. It only works when I tap on an empty area but not if tap on the hint text?

Thank you in advance!

Best

Crash when first displayed.

Randomly crashes on startup.

Thread : Crashed: com.apple.main-thread
0  Instructions                   0x100fcca60 CoachMarkDisplayManager.(positionCoachMarkView in _E6181A788EC6911C68FBD3A4D57722C9)() -> () (CoachMarkDisplayManager.swift:261)
1  Instructions                   0x100fcbbf0 CoachMarkDisplayManager.(prepareCoachMarkForDisplay in _E6181A788EC6911C68FBD3A4D57722C9)() -> () (CoachMarkDisplayManager.swift:154)
2  Instructions                   0x100fcb490 CoachMarkDisplayManager.displayCoachMarkView(CoachMarkView, coachMark : CoachMark, completion : () -> ()?) -> () (CoachMarkDisplayManager.swift:91)
3  Instructions                   0x100fd0bc0 CoachMarksController.(createAndShowCoachMark in _BC66BB5D8A069CE78924A87F8EC01AC4)(shouldCallDelegate : Bool) -> () (CoachMarksController.swift:530)
4  Instructions                   0x100fd451c CoachMarksController.(startOn(CoachMarksController) -> (UIViewController) -> ()).(closure #2) (CoachMarksController.swift:486)
5  UIKit                          0x187de7394 <redacted> + 628
6  UIKit                          0x187de6e90 <redacted> + 312
7  UIKit                          0x187de6d18 <redacted> + 108
8  QuartzCore                     0x1857bdc00 <redacted> + 284
9  libdispatch.dylib              0x182b0d5f0 <redacted> + 16
10 libdispatch.dylib              0x182b12cf8 _dispatch_main_queue_callback_4CF + 1844
11 CoreFoundation                 0x183070bb0 <redacted> + 12
12 CoreFoundation                 0x18306ea18 <redacted> + 1628
13 CoreFoundation                 0x182f9d680 CFRunLoopRunSpecific + 384
14 GraphicsServices               0x1844ac088 GSEventRunModal + 180
15 UIKit                          0x187e14d90 UIApplicationMain + 204
16 loko                           0x10025fedc main (AppDelegate.swift:16)
17 libdyld.dylib                  0x182b3e8b8 <redacted> + 4

Changing orientation causes multiple marks or causes mark to not be erased.

Hello!

Thanks so much for the work on this library -- it really looks great.

My app requires an orientation change mid-tour and it gets a little screwy when changing orientation while a mark is showing. In my app it causes whatever mark was showing to never disappear.

In the sample app if you go to the 'TransitionFromCode' screen and change orientation while a mark is showing, this is what you get:

screen shot 2016-08-02 at 4 03 03 pm

Thanks again!

When creating a new coachMark for navigation bar button item CoachMarkArrowView is not showing correctly

I have created four coachMarkView's which are pointing to a navigationBarTitle, leftNavigationBarButtonItem, rightNavigationBarButtonItem, and a simple view. I have done something like:

func numberOfCoachMarksForCoachMarksController(coachMarksController: CoachMarksController) -> Int {
        return 4
    }

    func coachMarksController(coachMarksController: CoachMarksController, coachMarksForIndex index: Int) -> CoachMark {
        switch(index) {
        case 0:
            return coachMarksController.coachMarkForView(self.navigationController?.navigationBar) { (frame: CGRect) -> UIBezierPath in
                return UIBezierPath(rect: frame)
            }
        case 1:
            return coachMarksController.coachMarkForView(self.viewAddButton)
        case 2:
            let leftBarButton = self.navigationItem.leftBarButtonItem! as UIBarButtonItem
            let viewLeft = leftBarButton.valueForKey("view") as! UIView
            return coachMarksController.coachMarkForView(viewLeft)
        case 3:
            let rightBarButton = self.navigationItem.rightBarButtonItem! as UIBarButtonItem
            let viewRight = rightBarButton.valueForKey("view") as! UIView
            return coachMarksController.coachMarkForView(viewRight)
        default:
            return coachMarksController.coachMarkForView()
        }
    }

    func coachMarksController(coachMarksController: CoachMarksController, coachMarkViewsForIndex index: Int, coachMark: CoachMark) -> (bodyView: CoachMarkBodyView, arrowView: CoachMarkArrowView?) {

        let coachViews = coachMarksController.defaultCoachViewsWithArrow(true, arrowOrientation: coachMark.arrowOrientation)

        switch(index) {
        case 0:
            coachViews.bodyView.hintLabel.text =  Constants.InstructionNavigation
            coachViews.bodyView.nextLabel.text = self.nextButtonText
        case 1:
            coachViews.bodyView.hintLabel.text = Constants.InstructionAddButton
            coachViews.bodyView.nextLabel.text = self.nextButtonText
        case 2:
            coachViews.bodyView.hintLabel.text = Constants.InstructionLeftBarButton
            coachViews.bodyView.nextLabel.text = self.nextButtonText
        case 3:
            coachViews.bodyView.hintLabel.text = Constants.InstructionsRightBarButton
            coachViews.bodyView.nextLabel.text = self.nextButtonText
        default: break
        }

        return (bodyView: coachViews.bodyView, arrowView: coachViews.arrowView)
    }

What I am getting in output is:

Navigation title is all good coz is in center i think please look here http://tinyurl.com/j2kv86t

But when showing coachMark for left or right navigation button item it looks like,
LeftBarButtonItem: http://tinyurl.com/ztoswd3
RightBarButtonItem: http://tinyurl.com/jgabyj7

Rotating device causing index out of range.

The following is my implementation of the coachMarksForIndex data source function.

func coachMarksController(coachMarksController: CoachMarksController, coachMarksForIndex index: Int) -> CoachMark {
            let instruction = self.instructions[index]
            return coachMarksController.coachMarkForView(instruction.pointOfInterest)
}

As you can see I am accessing an array to determine the point of interest for each CoachMark.

This works as expected the first time you start the coachMarksController. However, during a rotation of the device (iPad Pro) the data source method fires, passing '-1' as the index. Obviously causing the index out of range exception.

Please note I am rotating the device after the coachMarksController finishes the animation; there are no coachMarks on screen.

I originally thought I was doing something wrong, however I am slightly confused as to why the data source function is firing when there are no coachMarks on screen.

I then realised that after running the coachMarkController once, I was not able to run it again. (I soon saw that you have fixed this #58 but not released it?)

Any ideas?

Make coachmark appear above the view

Hi, I'm trying to get the coachmark to appear above the view I'm trying to point to, but am having a hard time figuring out how to do so. I realize that for some views the coachmark automatically appears at the top, but in my case it is not and is being presented off the screen.

Fatal error when using coachMarkWillLoadForIndex

Hi,

I'm implementing func coachMarksController(coachMarksController: CoachMarksController, coachMarkWillLoadForIndex index: Int) -> Bool {}

however if I return false i get:

fatal error: unexpectedly found nil while unwrapping an Optional value

/// Show the next coach mark and hide the current one.
private func showNextCoachMark(hidePrevious hidePrevious: Bool = true) {
self.currentIndex++

    // if `currentIndex` is above 0, that means a previous coach mark
    // is displayed. We call the delegate to notify that the current coach
    // mark will disappear, and only then, we hide the coach mark.
    if self.currentIndex > 0 {
        self.delegate?.coachMarksController(self, coachMarkWillDisappear: self.currentCoachMark!, forIndex: self.currentIndex - 1)

currentCoachMark Instructions.CoachMark? nil None

I only have one CoachMark.

Any ideas?

reset in completion from stop() is a problem.

First of all thank your for this nice component. It's very useful.

I had a problem when starting immediately a new set of coaches after stoping the first stop() function.
I added a closure and now I call startOn() again inside only when I receive a callback from this new stop. Maybe you have a better solution.

/// Stop displaying the coach marks and perform some cleanup.
public func stop(onComplete: (() -> Void)? = nil) {
    UIView.animateWithDuration(self.overlayFadeAnimationDuration, animations: { () -> Void in
        self.overlayView.alpha = 0.0
        self.skipViewAsView?.alpha = 0.0
        self.currentCoachMarkView?.alpha = 0.0
    }, completion: {(finished: Bool) -> Void in
        self.skipView?.skipControl?.removeTarget(self, action: "skipCoachMarksTour:", forControlEvents: .TouchUpInside)
        self.reset()
        self.detachFromViewController()

        // Calling the delegate, maybe the user wants to do something?
        self.delegate?.didFinishShowingFromCoachMarksController(self)

        onComplete?()

    })
}

Crash when force unwrapping in positionCoachMarkView

I'm getting a lot of crash in my app with the following stack trace.

CoachMarkDisplayManager.positionCoachMarkView

Crashed: com.apple.main-thread
EXC_BREAKPOINT 0x0000000100f58fd0

Crashed: com.apple.main-thread
0  Instructions                   0x100f58fd0 CoachMarkDisplayManager.(positionCoachMarkView in _E6181A788EC6911C68FBD3A4D57722C9)() -> () (CoachMarkDisplayManager.swift:267)
1  Instructions                   0x100f580e4 CoachMarkDisplayManager.(prepareCoachMarkForDisplay in _E6181A788EC6911C68FBD3A4D57722C9)() -> () (CoachMarkDisplayManager.swift:162)
2  Instructions                   0x100f5791c CoachMarkDisplayManager.displayCoachMarkView(CoachMarkView, coachMark : CoachMark, noAnimation : Bool, completion : () -> ()?) -> () (CoachMarkDisplayManager.swift:92)
3  Instructions                   0x100f5d304 CoachMarksController.(createAndShowCoachMark in _BC66BB5D8A069CE78924A87F8EC01AC4)(shouldCallDelegate : Bool, noAnimation : Bool) -> () (CoachMarksController.swift)
4  Instructions                   0x100f610b8 CoachMarksController.(startOn(UIViewController) -> ()).(closure #2) (CoachMarksController.swift:571)
5  UIKit                          0x186d4c12c -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 628
6  UIKit                          0x186d4bc28 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
7  UIKit                          0x186d4bab0 -[UIViewAnimationState animationDidStop:finished:] + 108
8  QuartzCore                     0x1846a59a0 CA::Layer::run_animation_callbacks(void*) + 284
9  libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
10 libdispatch.dylib              0x181602b84 _dispatch_main_queue_callback_4CF + 1844
11 CoreFoundation                 0x181b68d50 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
12 CoreFoundation                 0x181b66bb8 __CFRunLoopRun + 1628
13 CoreFoundation                 0x181a90c50 CFRunLoopRunSpecific + 384
14 GraphicsServices               0x183378088 GSEventRunModal + 180
15 UIKit                          0x186d7a088 UIApplicationMain + 204
16 rondevu                        0x100178cb0 main (AppDelegate.swift:19)
17 libdispatch.dylib              0x18162e8b8 (Missing)

#0. operation queue of: LYRInboundReconcilingAggregateOperation :: NSOperation 0x16002ead0 (QOS: LEGACY)
0  libsystem_kernel.dylib         0x181731014 semaphore_wait_trap + 8
1  libsystem_platform.dylib       0x18180e97c _os_semaphore_wait + 24
2  libdispatch.dylib              0x181608378 _dispatch_barrier_sync_f_slow + 560
3  LayerKit                       0x101110d0c -[LYRSynchronizationManager guardCollectionMutationInBlock:] + 88
4  LayerKit                       0x10110bd38 -[LYRSynchronizationManager unregisterIdentifiableAggregateOperation:] + 108
5  LayerKit                       0x10110cf30 -[LYRSynchronizationManager aggregateOperation:didFinishWithCountOfProcessedItems:countOfUnprocessedItems:] + 2812
6  LayerKit                       0x100fcd45c __32-[LYRAggregateOperation execute]_block_invoke + 1412
7  Foundation                     0x18256c540 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 16
8  Foundation                     0x1824be870 -[NSBlockOperation main] + 96
9  Foundation                     0x1824aee48 -[__NSOperationInternal _start:] + 604
10 Foundation                     0x18256e934 __NSOQSchedule_f + 224
11 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
12 libdispatch.dylib              0x1816094c0 _dispatch_queue_drain + 864
13 libdispatch.dylib              0x181600f80 _dispatch_queue_invoke + 464
14 libdispatch.dylib              0x18160b390 _dispatch_root_queue_drain + 728
15 libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
16 libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
17 libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#1. com.apple.libdispatch-manager
0  libsystem_kernel.dylib         0x18174d4d8 kevent_qos + 8
1  libdispatch.dylib              0x1816107d8 _dispatch_mgr_invoke + 232
2  libdispatch.dylib              0x1815ff648 _dispatch_source_invoke + 50

#2. com.layer.LYRProgress:0x15fd3d1f0
0  libsystem_kernel.dylib         0x181731014 semaphore_wait_trap + 8
1  libsystem_platform.dylib       0x18180e97c _os_semaphore_wait + 24
2  libdispatch.dylib              0x181608378 _dispatch_barrier_sync_f_slow + 560
3  LayerKit                       0x1010c8b18 -[LYRAggregateProgress childDidChange:] + 112
4  CoreFoundation                 0x181abc6ec -[NSArray makeObjectsPerformSelector:withObject:] + 240
5  LayerKit                       0x1010c79f0 __40-[LYRProgress didChangeCompletionCounts]_block_invoke_2 + 96
6  LayerKit                       0x1010c7ee4 -[LYRProgress guardCollectionMutationInBlock:] + 124
7  LayerKit                       0x1010c7970 __40-[LYRProgress didChangeCompletionCounts]_block_invoke + 216
8  libdispatch.dylib              0x1815fd4bc _dispatch_call_block_and_release + 24
9  libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
10 libdispatch.dylib              0x181607d50 _dispatch_async_redirect_invoke + 1920
11 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
12 libdispatch.dylib              0x18160b914 _dispatch_root_queue_drain + 2140
13 libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
14 libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
15 libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#3. Thread
0  libsystem_kernel.dylib         0x18174cb48 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x181815530 _pthread_wqthread + 1284
2  libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#4. operation queue of: LYRInboundReconcilingAggregateOperation :: NSOperation 0x15fff2c10 (QOS: LEGACY)
0  libsqlite3.dylib               0x18211dcd4 (null) + 128836
1  libsqlite3.dylib               0x18211d804 (null) + 127604
2  libsqlite3.dylib               0x18211e6d4 (null) + 131396
3  libsqlite3.dylib               0x18211cd60 (null) + 124880
4  libsqlite3.dylib               0x1820aa434 (null) + 48888
5  libsqlite3.dylib               0x1820a2240 (null) + 15620
6  libsqlite3.dylib               0x1820856fc (null) + 13080
7  libsqlite3.dylib               0x18208459c (null) + 8632
8  libsqlite3.dylib               0x182083730 (null) + 4940
9  libsqlite3.dylib               0x182082f0c (null) + 2856
10 libsqlite3.dylib               0x182082bc4 (null) + 2016
11 LayerKit                       0x10115302c -[PodLayerKit_FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:] + 408
12 LayerKit                       0x101153644 -[PodLayerKit_FMDatabase executeQuery:] + 44
13 LayerKit                       0x101100f68 __78-[LYRSyncedChangeRepository reconcilableSyncedChangesForStreamClientID:error:]_block_invoke + 576
14 LayerKit                       0x1011001ac -[LYRSyncedChangeRepository attemptBlock:] + 24
15 LayerKit                       0x101100c10 -[LYRSyncedChangeRepository reconcilableSyncedChangesForStreamClientID:error:] + 328
16 LayerKit                       0x10106bef8 __41-[LYRInboundReconciliationOperation main]_block_invoke + 520
17 LayerKit                       0x10101e4f8 -[LYRDatabaseTransaction executeTransactionUsingBlock:completion:] + 116
18 LayerKit                       0x10106b9e0 -[LYRInboundReconciliationOperation main] + 1056
19 Foundation                     0x1824aee48 -[__NSOperationInternal _start:] + 604
20 Foundation                     0x18256e934 __NSOQSchedule_f + 224
21 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
22 libdispatch.dylib              0x1816094c0 _dispatch_queue_drain + 864
23 libdispatch.dylib              0x181600f80 _dispatch_queue_invoke + 464
24 libdispatch.dylib              0x18160b390 _dispatch_root_queue_drain + 728
25 libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
26 libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
27 libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#5. Thread
0  libsystem_kernel.dylib         0x18174cb48 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x181815530 _pthread_wqthread + 1284
2  libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#6. com.twitter.crashlytics.ios.MachExceptionServer
0  rondevu                        0x1001b25a8 CLSProcessRecordAllThreads + 4296451496
1  rondevu                        0x1001b25a8 CLSProcessRecordAllThreads + 4296451496
2  rondevu                        0x1001b29c8 CLSProcessRecordAllThreads + 4296452552
3  rondevu                        0x1001a3688 CLSHandler + 4296390280
4  rondevu                        0x10019e80c CLSMachExceptionServer + 4296370188
5  libsystem_pthread.dylib        0x181817b28 _pthread_body + 156
6  libsystem_pthread.dylib        0x181817a8c _pthread_body + 154
7  libsystem_pthread.dylib        0x181815028 thread_start + 4

#7. GAIThread
0  libsystem_kernel.dylib         0x181730fd8 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x181730e54 mach_msg + 72
2  CoreFoundation                 0x181b68c60 __CFRunLoopServiceMachPort + 196
3  CoreFoundation                 0x181b66964 __CFRunLoopRun + 1032
4  CoreFoundation                 0x181a90c50 CFRunLoopRunSpecific + 384
5  Foundation                     0x1824a0cfc -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 308
6  Foundation                     0x1824f6030 -[NSRunLoop(NSRunLoop) run] + 88
7  rondevu                        0x10017ebc8 +[GAI threadMain:] + 4296240072
8  Foundation                     0x182587e4c __NSThread__start__ + 1000
9  libsystem_pthread.dylib        0x181817b28 _pthread_body + 156
10 libsystem_pthread.dylib        0x181817a8c _pthread_body + 154
11 libsystem_pthread.dylib        0x181815028 thread_start + 4

#8. com.apple.NSURLConnectionLoader
0  libsystem_kernel.dylib         0x181730fd8 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x181730e54 mach_msg + 72
2  CoreFoundation                 0x181b68c60 __CFRunLoopServiceMachPort + 196
3  CoreFoundation                 0x181b66964 __CFRunLoopRun + 1032
4  CoreFoundation                 0x181a90c50 CFRunLoopRunSpecific + 384
5  CFNetwork                      0x182211c68 +[NSURLConnection(Loader) _resourceLoadLoop:] + 412
6  Foundation                     0x182587e4c __NSThread__start__ + 1000
7  libsystem_pthread.dylib        0x181817b28 _pthread_body + 156
8  libsystem_pthread.dylib        0x181817a8c _pthread_body + 154
9  libsystem_pthread.dylib        0x181815028 thread_start + 4

#9. com.layer.LYRSynchronizationManager.operationQueueingSerialQueue
0  libsystem_kernel.dylib         0x181731014 semaphore_wait_trap + 8
1  libsystem_platform.dylib       0x18180e97c _os_semaphore_wait + 24
2  libdispatch.dylib              0x181608378 _dispatch_barrier_sync_f_slow + 560
3  LayerKit                       0x1010c77b4 -[LYRProgress setCompletedUnitCount:] + 120
4  LayerKit                       0x10110503c -[LYRSynchronizationContext removeOperation:] + 84
5  LayerKit                       0x10110bdbc __70-[LYRSynchronizationManager unregisterIdentifiableAggregateOperation:]_block_invoke + 92
6  libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
7  libdispatch.dylib              0x181608480 _dispatch_barrier_sync_f_slow + 824
8  LayerKit                       0x101110d0c -[LYRSynchronizationManager guardCollectionMutationInBlock:] + 88
9  LayerKit                       0x10110bd38 -[LYRSynchronizationManager unregisterIdentifiableAggregateOperation:] + 108
10 LayerKit                       0x10110cf30 -[LYRSynchronizationManager aggregateOperation:didFinishWithCountOfProcessedItems:countOfUnprocessedItems:] + 2812
11 LayerKit                       0x100fcd45c __32-[LYRAggregateOperation execute]_block_invoke + 1412
12 Foundation                     0x18256c540 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 16
13 Foundation                     0x1824be870 -[NSBlockOperation main] + 96
14 Foundation                     0x1824aee48 -[__NSOperationInternal _start:] + 604
15 Foundation                     0x18256e934 __NSOQSchedule_f + 224
16 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
17 libdispatch.dylib              0x1816094c0 _dispatch_queue_drain + 864
18 libdispatch.dylib              0x181600f80 _dispatch_queue_invoke + 464
19 libdispatch.dylib              0x18160b390 _dispatch_root_queue_drain + 728
20 libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
21 libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
22 libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#10. operation queue of: LYRInboundReconcilingAggregateOperation :: NSOperation 0x15f947ad0 (QOS: LEGACY)
0  libsystem_kernel.dylib         0x181731014 semaphore_wait_trap + 8
1  libsystem_platform.dylib       0x18180e97c _os_semaphore_wait + 24
2  libdispatch.dylib              0x181608378 _dispatch_barrier_sync_f_slow + 560
3  LayerKit                       0x101110d0c -[LYRSynchronizationManager guardCollectionMutationInBlock:] + 88
4  LayerKit                       0x10110bf10 -[LYRSynchronizationManager synchronizationContextForStreamClientID:] + 176
5  LayerKit                       0x10110c16c -[LYRSynchronizationManager synchronizationContextForAggregateOperation:] + 92
6  LayerKit                       0x10110d830 -[LYRSynchronizationManager aggregateOperation:didSaveChanges:] + 244
7  LayerKit                       0x1010785a0 __64-[LYRInboundReconcilingAggregateOperation operationsForQueueing]_block_invoke + 216
8  LayerKit                       0x10101edac -[LYRDatabaseTransaction signalCompletionWithSuccess:error:] + 120
9  LayerKit                       0x10101ea2c -[LYRDatabaseTransaction commit:] + 284
10 LayerKit                       0x10101e544 -[LYRDatabaseTransaction executeTransactionUsingBlock:completion:] + 192
11 LayerKit                       0x10106b9e0 -[LYRInboundReconciliationOperation main] + 1056
12 Foundation                     0x1824aee48 -[__NSOperationInternal _start:] + 604
13 Foundation                     0x18256e934 __NSOQSchedule_f + 224
14 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
15 libdispatch.dylib              0x1816094c0 _dispatch_queue_drain + 864
16 libdispatch.dylib              0x181600f80 _dispatch_queue_invoke + 464
17 libdispatch.dylib              0x18160b390 _dispatch_root_queue_drain + 728
18 libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
19 libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
20 libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#11. com.apple.root.default-qos
0  libsystem_kernel.dylib         0x181731014 semaphore_wait_trap + 8
1  libdispatch.dylib              0x18160e3e8 _dispatch_semaphore_wait_slow + 244
2  BrightFutures                  0x100cbf104 _TFC13BrightFutures9Semaphore7executefFT_T_T_ + 112
3  BrightFutures                  0x100cae780 _TFFFC13BrightFutures5Async10onCompleteFTFFT_T_T_8callbackFxT__DGS0_x_U_FQ_T_U_FT_T_ + 160
4  libdispatch.dylib              0x1815fd4bc _dispatch_call_block_and_release + 24
5  libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
6  libdispatch.dylib              0x18160b914 _dispatch_root_queue_drain + 2140
7  libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
8  libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
9  libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#12. com.apple.CFSocket.private
0  libsystem_kernel.dylib         0x18174c344 __select + 8
1  CoreFoundation                 0x181b6f1c8 __CFSocketManager + 648
2  libsystem_pthread.dylib        0x181817b28 _pthread_body + 156
3  libsystem_pthread.dylib        0x181817a8c _pthread_body + 154
4  libsystem_pthread.dylib        0x181815028 thread_start + 4

#13. GCDAsyncSocket-CFStream
0  libsystem_kernel.dylib         0x181730fd8 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x181730e54 mach_msg + 72
2  CoreFoundation                 0x181b68c60 __CFRunLoopServiceMachPort + 196
3  CoreFoundation                 0x181b66964 __CFRunLoopRun + 1032
4  CoreFoundation                 0x181a90c50 CFRunLoopRunSpecific + 384
5  Foundation                     0x1824a0cfc -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 308
6  CocoaAsyncSocket               0x100d3a848 +[GCDAsyncSocket cfstreamThread] (GCDAsyncSocket.m:7414)
7  Foundation                     0x182587e4c __NSThread__start__ + 1000
8  libsystem_pthread.dylib        0x181817b28 _pthread_body + 156
9  libsystem_pthread.dylib        0x181817a8c _pthread_body + 154
10 libsystem_pthread.dylib        0x181815028 thread_start + 4

#14. com.layer.LYRProgress:0x15fe6e310
0  libsystem_kernel.dylib         0x1817310d4 syscall_thread_switch + 8
1  libsystem_platform.dylib       0x181811534 _os_lock_handoff_lock_slow + 120
2  libsystem_malloc.dylib         0x181775514 szone_malloc_should_clear + 116
3  libsystem_malloc.dylib         0x181779930 malloc_zone_calloc + 124
4  libsystem_malloc.dylib         0x181779890 calloc + 60
5  libobjc.A.dylib                0x181221cf8 class_createInstance + 76
6  Foundation                     0x182497308 NSAllocateObject + 28
7  Foundation                     0x1824bf8f8 +[NSScanner scannerWithString:] + 40
8  Foundation                     0x1825a5d74 -[NSNumber(NSDecimalNumberExtensions) decimalValue] + 84
9  Foundation                     0x18254ec78 -[NSOrderedSet(NSKeyValueCoding) _sumForKeyPath:] + 168
10 Foundation                     0x18254ed18 -[NSOrderedSet(NSKeyValueCoding) _avgForKeyPath:] + 64
11 Foundation                     0x18254f3d4 -[NSOrderedSet(NSKeyValueCoding) valueForKeyPath:] + 368
12 Foundation                     0x1824a0478 -[NSObject(NSKeyValueCoding) valueForKeyPath:] + 272
13 LayerKit                       0x1010c8920 -[LYRAggregateProgress updateUnitCounts] + 164
14 LayerKit                       0x1010c8b58 __39-[LYRAggregateProgress childDidChange:]_block_invoke + 32
15 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
16 libdispatch.dylib              0x181608728 _dispatch_barrier_sync_f_invoke + 100
17 LayerKit                       0x1010c8b18 -[LYRAggregateProgress childDidChange:] + 112
18 CoreFoundation                 0x181abc6ec -[NSArray makeObjectsPerformSelector:withObject:] + 240
19 LayerKit                       0x1010c79f0 __40-[LYRProgress didChangeCompletionCounts]_block_invoke_2 + 96
20 LayerKit                       0x1010c7ee4 -[LYRProgress guardCollectionMutationInBlock:] + 124
21 LayerKit                       0x1010c7970 __40-[LYRProgress didChangeCompletionCounts]_block_invoke + 216
22 libdispatch.dylib              0x1815fd4bc _dispatch_call_block_and_release + 24
23 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
24 libdispatch.dylib              0x181607d50 _dispatch_async_redirect_invoke + 1920
25 libdispatch.dylib              0x1815fd47c _dispatch_client_callout + 16
26 libdispatch.dylib              0x18160b914 _dispatch_root_queue_drain + 2140
27 libdispatch.dylib              0x18160b0b0 _dispatch_worker_thread3 + 112
28 libsystem_pthread.dylib        0x181815470 _pthread_wqthread + 1092
29 libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#15. Thread
0  libsystem_kernel.dylib         0x18174cb48 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x181815530 _pthread_wqthread + 1284
2  libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#16. Thread
0  libsystem_kernel.dylib         0x18174cb48 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x181815530 _pthread_wqthread + 1284
2  libsystem_pthread.dylib        0x181815020 start_wqthread + 4

#17. Thread
0  libsystem_pthread.dylib        0x18181501c start_wqthread + 14

#18. Thread
0  libsystem_kernel.dylib         0x18174cb48 __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x181815530 _pthread_wqthread + 1284
2  libsystem_pthread.dylib        0x181815020 start_wqthread + 4


Multiple coachmarks when tapping overlayview rapidly.

First off, great project!
Second: When the user taps the overlayview quickly, showNextCoachMark gets called instantly multiple times and one or more coachmarkviews get stuck on the screen.
I also did a quick fix, feel free to use it if You want to.

  1. Add a second gesture recognizer to the overlayView that detects double taps: (no need for it to have an action)
private lazy var doubleTapGestureRecognizer: UITapGestureRecognizer = {
        let gestureRecognizer = UITapGestureRecognizer(target: self, action: "handleDoubleTap:")
        gestureRecognizer.numberOfTapsRequired = 2

        return gestureRecognizer
    }()
  1. Make sure singe tap gesture recognizer only detects tap when double tap fails:
private lazy var singleTapGestureRecognizer: UITapGestureRecognizer = {
        let gestureRecognizer = UITapGestureRecognizer(target: self, action: "handleSingleTap:")
        gestureRecognizer.requireGestureRecognizerToFail(self.doubleTapGestureRecognizer)

        return gestureRecognizer
    }()
  1. Add double tap gesture recognizer where the single tap recognizer is added:
var allowOverlayTap: Bool {
        get {
            return self.singleTapGestureRecognizer.view != nil
        }

        set {
            if newValue == true {
                self.addGestureRecognizer(self.singleTapGestureRecognizer)
                self.addGestureRecognizer(self.doubleTapGestureRecognizer)
            } else {
                self.removeGestureRecognizer(self.singleTapGestureRecognizer)
            }
        }
    }

Bug fixed.

Wrap or remove access to `UIApplication.sharedApplication()`

The reason is:

+ (UIApplication *)sharedApplication NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropriate instead.");

Access to UIApplication.sharedApplication() unavailable inside iOS extensions and because of this library cannot be used for instance for guide inside custom keyboard.

Skipping already shown CoachMarks

I wanted to show a coachMark, leave my current controller, come back to it and skip the coachMark that I have already shown to my user. I wanted to make a good use of this delegate function:
func coachMarksController(coachMarksController: CoachMarksController, coachMarkWillLoadForIndex index: Int) -> Bool { if index <= defaults.integerForKey(GlobalConstants.skipProfileWalkthrough) { return false } return true }
However, it was crashing on this line in your library:
self.delegate?.coachMarksController(self, coachMarkWillDisappear: self.currentCoachMark!, forIndex: self.currentIndex - 1)
with the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
It was pretty much trying to wrap self.currentCoachMark that was nil for some reason.
I fixed it myself on your library and took some notes. Attached you can see the notes I took and the fixes I made on your library:
note_1
note_2
note_3
Also I am not using your delegate function coachMarkWillDissappear anymore, I do not think that it is working fine, instead I am doing it like this:
screen shot 2016-08-02 at 9 22 37 pm

Fatal Error: Instructions could not be properly attached to the window, did you call `startOn` inside `viewDidLoad` instead of `ViewDidAppear`

attachToViewController: Instructions could not be properly attached to the window, did you call startOn inside viewDidLoad instead of ViewDidAppear?

The message is shown from
private func computeSegmentIndexForLayoutDirection(layoutDirection: UIUserInterfaceLayoutDirection) -> Int {
let pointOfInterest = coachMark.pointOfInterest!
var segmentIndex = 3 * pointOfInterest.x / instructionsTopView.bounds.size.width

    if layoutDirection == .RightToLeft {
        segmentIndex = 3 - segmentIndex
    }

    return Int(ceil(segmentIndex))
}

Unbalanced calls toBug: begin/end appearance transitions for <Instructions.CoachMarksController: 0x7f8e9af09af0>

step 1.create a new project(iOS8 or iOS 9).

step 2.pod install.

step 3.implement most simplest code,like below:

step 4.Log show:
Camera[42391:588974] Unbalanced calls to begin/end appearance transitions for <Instructions.CoachMarksController: 0x7f8e9af09af0>.

import UIKit
import Instructions
class ViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate , CoachMarksControllerDataSource{
@IBOutlet weak var imageView: UIImageView!

var coachMarksController: CoachMarksController?
let avatarText = "That's your profile picture. You look gorgeous!"
let profileSectionText = "You are in the profile section, where you can review all your informations."
let handleText = "That, here, is your name. Sounds a bit generic, don't you think?"
let emailText = "This is your email address. Nothing too fancy."
let postsText = "Here, is the number of posts you made. You are just starting up!"
let reputationText = "That's your reputation around here, that's actually quite good."

let nextButtonText = "Ok!"


//MARK: - View lifecycle
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    self.coachMarksController = CoachMarksController()
    self.coachMarksController?.allowOverlayTap = true
    self.coachMarksController?.datasource = self
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    self.coachMarksController?.startOn(self)
}

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {

    let image = info[UIImagePickerControllerOriginalImage] as! UIImage
    imageView.image = image
    picker.dismissViewControllerAnimated(true, completion: nil)

}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
    picker.dismissViewControllerAnimated(true, completion: nil)

}



///
//MARK: - Protocol Conformance | CoachMarksControllerDataSource
func numberOfCoachMarksForCoachMarksController(coachMarksController: CoachMarksController) -> Int {
    return 5
}

func coachMarksController(coachMarksController: CoachMarksController, coachMarksForIndex index: Int) -> CoachMark {
    switch(index) {
    case 0:
        return coachMarksController.coachMarkForView(self.imageView)
    case 1:
        return coachMarksController.coachMarkForView(self.imageView)
    case 2:
        return coachMarksController.coachMarkForView(self.imageView)
    case 3:
        return coachMarksController.coachMarkForView(self.imageView)
    case 4:
        return coachMarksController.coachMarkForView(self.imageView)
    default:
        return coachMarksController.coachMarkForView()
    }
}

func coachMarksController(coachMarksController: CoachMarksController, coachMarkViewsForIndex index: Int, coachMark: CoachMark) -> (bodyView: CoachMarkBodyView, arrowView: CoachMarkArrowView?) {

    let coachViews = coachMarksController.defaultCoachViewsWithArrow(true, arrowOrientation: coachMark.arrowOrientation)

    switch(index) {
    case 0:
        coachViews.bodyView.hintLabel.text = self.profileSectionText
        coachViews.bodyView.nextLabel.text = self.nextButtonText
    case 1:
        coachViews.bodyView.hintLabel.text = self.handleText
        coachViews.bodyView.nextLabel.text = self.nextButtonText
    case 2:
        coachViews.bodyView.hintLabel.text = self.emailText
        coachViews.bodyView.nextLabel.text = self.nextButtonText
    case 3:
        coachViews.bodyView.hintLabel.text = self.postsText
        coachViews.bodyView.nextLabel.text = self.nextButtonText
    case 4:
        coachViews.bodyView.hintLabel.text = self.reputationText
        coachViews.bodyView.nextLabel.text = self.nextButtonText
    default: break
    }

    return (bodyView: coachViews.bodyView, arrowView: coachViews.arrowView)
}


@IBAction func addPhoto(sender: AnyObject) {
    let picker = UIImagePickerController()
    picker.sourceType = .SavedPhotosAlbum

    picker.delegate = self
    picker.allowsEditing = false
    picker.navigationBarHidden = true
    picker.modalPresentationStyle = .CurrentContext
    self.presentViewController(picker, animated: true, completion: nil)

}

}

Bubble color?

Hello!

Please tell me how to set the color of the bubble?

coachViews.bodyView.backgroundColor = UIColor.redColor()

The above code is wrong, what is the right solution?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.