Skip to content

epifanycx/Epifany-iOS-Release

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


Maintainability CircleCI Version License Platform

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Table of Contents

  1. Requirements
  2. How it Works
  3. Installation
  4. Usage
    1. Survey
      1. Check for Survey
      2. Start a Survey
      3. Finish a Question
      4. Check Question Type
      5. Product Question Tool
      6. Open Question Tool
    2. Beacons
      1. Start Beacon Monitoring
      2. Beacon Options
      3. Adding a Delegate for UI Updates
      4. Adding Background Delegate for Beacon Hits
      5. Error Handling

Requirements

  • Location Permission is required to range beacons
  • Plist Permissions
    • Privacy - Bluetooth Peripheral Usage
    • Privacy - Location Always and When In Use Usage Description

How it Works

Epifany listens for beacons (using bluetooth) in the background to verify the user is at a venue. Once a beacon is seen, Epifany will notify your app through a EpifanyBackgroundDelegate in order to produce a notification. This will allow the user to take a survey. Once a survey is started you will use EpifanySurvey to navigate through it.

What if a user’s Bluetooth is turned off? Don't Sweat it, you can use the user's current location as a fall back.

Installation

Epifany is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Epifany'

Usage

Initialize SDK from your AppDelegate. We require an appToken and appId that you can find on your dashboard or get from us.

EpifanyController.shared.setUp(token: "yourAppToken", id: yourAppId)

Survey

Check for Survey

This will be used to communicate with our server to see if there is a survey available. The user's email is required to determine their survey status. The user's location is used as a fallback if we have not seen a beacon. The mobileOrder flag is used to let us know the user used mobile ordering. The completion block allows you to update your UI when there is a survey.

EpifanyController.shared.checkForSurvey(email: "userEmail", 
location: CLLocation?, mobileOrder: Bool?, completion: { (available) in
    // Change start of start survey button
    }, failure: { (error) in
    // Handle error message
})

Start a Survey

This is used to get a new survey from our server. The user's location is used as a fallback if we have not seen a beacon. The mobileOrder flag is used to let us know the user used mobile ordering. The completion will return an EpifanySurvey object that helps you navigate and interact with the survey.

EpifanyController.shared.newSurvey(email: "userEmail", 
location: CLLocation?, mobileOrder: Bool? completion: { (surveyController) in
    let viewController = ViewController()
    viewController.surveyController = surveyController
    self.present(viewController, animated: true, completion: nil)
    }, failure : { (error) in
    // Handle error message
})

Once you get a survey object you must first call survey.getFirstQuestion() to get the first question.

let question = survey.firstQuestion()

Question Types and Flow

When you get a question make sure you check the question.type to make sure you are present the right UI design. There are 3 Question.Type groups and a total of 7 `Question.Type:

  1. Multiple Choice Questions

  2. .multipleChoice - A simple UITableView with single selection.

  3. .mood - A multiple choice question with some emojis or somethign with more character.

  4. .slider - A simple slider view where each value represents an answer.

  5. Open Answer Questions

  6. .openAnswer - A simple EditTextView that the user interacts.

  7. .date - Uses a date picker that submits the answer as a string in ISO8601 format.

  8. Multi-select Questions

  9. .multiselect - A simple UITableView with multi-selection

  10. .product - Preferably a Multi-select Dropdown menu

The best way to check for this is use a switch statement like this:

switch question.type! {
case .multipleChoice:
// Get Multiple Choice View Controller
case .openAnswer:
// Get Open Answer View Controller
case .mood:
// Get Mood View Controller
case .slider:
// Get Slider View Controller
case .date:
// Get Date View Controller
case .product:
// Get Product View Controller
case .multiselect:
// Get Multi-select View Controller
}

Once the user selects/types and answer you must call surveyController.nextQuestion() to move to the next question. There are three different surveyController.nextQuestion() functions that each take a different parameter (String, Answer, [Answer]. The Question types are broken up that each group uses the same surveyController.nextQuestion() method:

  1. Multiple Choice Questions
// Example that grabs first answer from Answers list
let answer = questions.answers[0]
let nextQuestion = surveyController.nextQuestion(answer)
  1. Open Answer Questions
// Example with some text
let answer = "Example Answer"
let nextQuestion = surveyController.nextQuestion(answer)
  1. Multi-select Questions
// Example that grabs first two answers from Answers list
let answers = [Answer]()
answers.append(question.answers[0])
answers.append(question.answers[1])
let nextQuestion = surveyController.nextQuestion(answers)

When surveyController.nextQuestion() returns nil this means the user has reached the end of the survey. At this time you should call surveyController.finishSurvey(). The SurveyFinishCallback will let you know when we finished uploading the survey to our server.

    // answer is either a String, Answer, or [Answer]
    if let question = surveyContorller.nextQuestion(answer: answer) {
    // Start next question
    } else {
    // Finish survey flow
    surveyController?.finishSurvey() { (done) in
        print("FINISHED SURVEY")
        self.dismiss(animated: true, completion: nil)
    }
    }

Product Question Tool

This tool is used to do three jobs:

  1. Hints - Some .openAnswer questions will have hints that are fired based on a word or set of words a user types. This should be display in a way to prompt the user to keep typing. (See below)

  2. Gibberish - When a user types a sentence that is considered not English this will be fired This should be displayed in a way to let the user know they need to fix their answer. (See Below)

  3. Character Limit - Every .openAnswer question requires a minimum amount of characters. This will fire when the user's answer passes the minimum threshold or goes below the threshold. You can also get the minimum character limit using question.characterLimit and may be a good idea to display this to the user.

The EpifanyTextDelegate is only fired when the state of gibberish, hints, or character limit reached has changed.

// Whenever you select an answer pass it though our 
// check to make sure it is not a bundle/group of 
// answers
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // Check answer may return an array of answers, this is for handling
    // value meals or other combo meals, or returns the current selection
    // if not a bundle.
    let answers = surveyController.checkAnswer(answer: answer)
    if answers.count > 1 {
        // Select the various answers
    } else {
        // Select the current answer
    }
}

Open Question Tool

This tool is used to simplify updating gibberish, hints, & character limit reached. The listener is only fired when the state of gibberish, hints, or character limit reached has changed.

 
 class OpenViewController: UITextDelegate, EpifanyTextDelegate {
    //...
    func textViewDidChange(_ textView: UITextView) {
        // This will fire EpifanyTextDelegate if any
        // of the states change
        surveyController?.textUpdated(string: textView.text)
    }
    
    // ...
    func onGibberish(gibberish: Bool) {
        // Handle gibberish
    }
    
    func onHintChanged(hint: String) {
        // Handle hint
    }
    
    func onCharacterLimit(reached: Bool) {
        // Hanlde Character limit reached
    }
 }

You can choose to not use the EpifanyTextDelegate but you need to compare the length of the user's answer to question.characterLimit. If the answer doesn't reach the minimum character limit it will cause an error when trying to post the survey.

Beacons

Start Beacon Monitoring

This will allow the user to range beacons. Call this after you gain location permissons.

Beacon Options

You can also pass a [BeaconOptions: Int] to modify how beacon monitoring will work. Current there are only two usable BeaconOptions:

  1. .visits - allows you to set the minimum number of visits before we notify the beacon delegates. Note: visits are based on days so when visits are set to 2 the delegates will be notified on the 2nd day they visit.
  2. .sinceVisit - allows you to set a delay for when the delegates get fired until x seconds after the user leaves the range of the beacon. This is useful if you would prefer the user to finish their meal or time at the venue before getting the survey. These BeaconOptions can be used together as well.
    EpifanyController.shared.startMonitoringBeacons()
    // with options
    EpifanyController.shared.startMonitoringBeacons([.visits: 2, .sinceVisit: 60])

Adding a Delegate for UI Updates

This delegate is used to udate the UI when the user see's a beacon.

class ViewContoller: EpifanyUiDelegate {
   // ...

    func didSeeBeacon(available: Bool) {
        // Update UI to reflect survey available
    }
    
    func beaconError(error: String) {
        // Handle beacon error
        if error == "No User Email" {
            EpifanyController.checkForSurvey(email: "userEmail", ...)
            ...
        }
    }
}

}

Adding Background Delegate for Beacon Hits

This delegate is used in the background to allow you to send the user a local notification when the user see's a beacon.

class AppDelegate: EpifanyBackgroundDelegate {
    // ...
    func didSeeBeacon(available: Bool) {
        // Send notification to user
    }
    
    func beaconError(error: String) {
        // Handle beacon error
        if error == "No User Email" {
            EpifanyController.checkForSurvey(email: "userEmail", ...)
    //        ...
        }
    }
}

Error Handling

We provided a simple EpifanyError object that is returned whenever we receive an error from our servers. This object contains the status code and the error message for you to look at. Here are a few of the common errors that you may want to handle:

// ...
} failure: { (error) in
if error.message == EpifanyError.noUser {
// This mainly happens when we see a beacon before you provide us with the 
//user's email. The simplest solution here is to just hit the checkForSurvey
// with the user's email
EpifanyController.shared.checkForSurvey(email: "userEmail", location: nil, mobileOrder: false, completion: { (available) in
// Handle success
}, failure: { (error) in
//Handle error
})
}
// ...
if error.message == EpifanyError.noHost {
// This is usually caused by the user having wifi / mobile data off.
// Please prompt the user to turn on their wifi / mobile data.
}
// ...
if error.message == EpifanyError.invaildAppToken {
// This error only occurs if the app token you were provided is no longer
// working. Contact us immediately if this is happening in a production 
// build since the SDK will not work without a valid AppToken.
}

}

Author

Shawn Murphy, shawn.murphy@epifany.com

License

Epifany is available under the MIT license. See the LICENSE file for more info.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages