Trigger UIAlertViewController Based on Time - ios

I have UITable to display different animals. When you select a cell in the table, a new view controller with a large UIImage is pushed. Currently, when you zoom in on the image, a UIAlertView is triggered that asks the user if they would like to download hi res images. If they click yes, the "hi-res-flag" is set to "yes" in user defaults and they no longer see the pop up. However, if they select no, the hi-res-flag will continue to pop up each time they zoom in on a photo.
Instead, if they answer no, I would like to have this flag pop up occasionally. Not every time the click a cell in the species table, nor every time they open the app. Something more like once or twice a month. Is there a way to use time in the logic of an iOS app? For instance, erase the value set for "high-res-flag" (if already equals 'no') in user defaults, once a month?

Store the time you showed the alert last in the user preferences, and then check that value every time before you present the alert whether a certain time has passed.

I have written a time checker class that does the job. The code is in Swift. You can use it from your Objective-C code as well. You can find this code in gist here.
Solution
Below, you use the viewWillAppear delegate method to see if the hiResFlag is existing. If it is present and false, then you check to see if you can display the popup:
import UIKit
class ImageViewController: UIViewController {
//Whenever you enter the Image View Controller, you check whether to show popup or not
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let hiResFlag = hiResFlag {
if hiResFlag == false {
if PopUpTimeChecker.shouldShowPopUp() {
self.presentAlert()
}
}
}
}
func presentAlert() {
let alert = UIAlertController.init(title: nil, message: "Show Pop up", preferredStyle: .alert)
let action = UIAlertAction.init(title: "Yeahh!", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
The following code implements the time-checking algorithm. Edit popUpTimeInterval below for setting your minimum time. Right now, it is set to be 15 days (in seconds). Once in every 15 days the pop-up will be shown when you call the shouldShowPopUp method.
import UIKit
//Below 4 variables, I have made them Global. No need to make them global in your case
#objc var popUpTimeInterval: UInt64 = 1296000 //15 days in seconds
#objc var hiResFlag: Bool? {
get {
return UserDefaults.standard.object(forKey: "HiResFlag") as? Bool
}
set {
UserDefaults.standard.setValue(newValue, forKey: "HiResFlag")
}
}
#objc var isFirstTimePopUp: Bool {
get {
let value = UserDefaults.standard.object(forKey: "IsFirstTimePopUp")
return value == nil ? true : value as! Bool
}
set {
UserDefaults.standard.setValue(newValue, forKey: "IsFirstTimePopUp")
}
}
#objc var lastDateOfPopUp: Date? {
get {
return UserDefaults.standard.object(forKey: "LastDateOfPopUp") as? Date
}
set {
UserDefaults.standard.setValue(newValue, forKey: "LastDateOfPopUp")
}
}
#objc class PopUpTimeChecker {
#objc static fileprivate func setLastPopUpDate() {
//Setting current date to last shown pop up date
lastDateOfPopUp = Date()
}
#objc static fileprivate func timeIntervalSinceLastPopUp() -> UInt64 {
//Returning how much time (in seconds) has passed from last popup date until now
return UInt64(Date().timeIntervalSince(lastDateOfPopUp!))
}
#objc static func shouldShowPopUp() -> Bool {
//We proceed further only if we have the last date when pop up was displayed, else we create and set it as the current date
if let _ = lastDateOfPopUp {
let timeInterval = timeIntervalSinceLastPopUp()
if timeInterval > popUpTimeInterval {
self.setLastPopUpDate()
return true //Show pop up
} else {
if isFirstTimePopUp {
//If this is the first time, you just allow the pop up to show, don't allow otherwise
isFirstTimePopUp = false
return true
} else {
return false
}
}
} else {
self.setLastPopUpDate() //Since we don't have a last date, we set it here for starting off
return self.shouldShowPopUp() //Recursively call method
}
}
}

Related

Microblink: getting values after scanning

I've set up the Microblink card reader to read only one side, and not give the user the ability to edit the scan results:
func didTapScan() {
/** Create BlinkCard recognizer */
blinkCardRecognizer = MBCBlinkCardRecognizer()
blinkCardRecognizer?.extractCvv = false
blinkCardRecognizer?.extractIban = false
blinkCardRecognizer?.extractExpiryDate = false
/** Create BlinkCard settings */
let settings : MBCBlinkCardOverlaySettings = MBCBlinkCardOverlaySettings()
settings.enableEditScreen = false
/** Crate recognizer collection */
let recognizerList = [blinkCardRecognizer!]
let recognizerCollection : MBCRecognizerCollection = MBCRecognizerCollection(recognizers: recognizerList)
/** Create your overlay view controller */
let blinkCardOverlayViewController = MBCBlinkCardOverlayViewController(settings: settings, recognizerCollection: recognizerCollection, delegate: self)
/** Create recognizer view controller with wanted overlay view controller */
// NOTE that I put a bang on the end of this - not good
let recognizerRunneViewController : UIViewController = MBCViewControllerFactory.recognizerRunnerViewController(withOverlayViewController: blinkCardOverlayViewController)!
/** Present the recognizer runner view controller. You can use other presentation methods as well (instead of presentViewController) */
self.present(recognizerRunneViewController, animated: true, completion: nil)
}
After scanning, I check in the delegate callback for a valid state, and then I try to retrieve the values from the cardRecognizer, but I'm getting nothing but crashes:
func blinkCardOverlayViewControllerDidFinishScanning(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, state: MBCRecognizerResultState) {
// this is done on background thread
// check for valid state
if state == .valid {
CRASHES HERE
guard let result = blinkCardRecognizer?.combinedResult else {
return
}
// CRASHES HERE
print (result)
// first, pause scanning until we process all the results
blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
DispatchQueue.main.async(execute: {() -> Void in
print(self.blinkCardRecognizer)
// self.dismiss(animated: true, completion: nil)
})
}
}
What am I missing here?
You've done everything correctly regarding the first block of code.
Regarding the second part (in the blinkCardOverlayViewControllerDidFinishScanning method), the .combinedResult is the parent of the result object, so you can use the blinkCardRecognizer.result instead.
Also, there seems to be an issue with the recognizer's description method (blinkCardRecognizer.result), so you would need to specify which information you want to extract.
An example code would be:
extension ViewController: MBCBlinkCardOverlayViewControllerDelegate {
func blinkCardOverlayViewControllerDidFinishScanning(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, state: MBCRecognizerResultState) {
/** This is done on background thread */
if state == .valid {
guard let result = blinkCardRecognizer?.result else {
return
}
blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
DispatchQueue.main.async(execute: {() -> Void in
print(result.cardNumber)
})
}
}

Create new event in CalendarKit

In my app, I'm trying to add ability to create a new event with CalendarKit when I press an empty space, but I can't find how to do that.
I know it's possible because you can do it in the demo app:
I've tried adding this to my app's code:
override func create(event: EventDescriptor, animated: Bool = false) {
self.events.append(event) // self.events is my events data source
}
But it didn't worked, in fact, it doesn't even get called when I long press an empty space.
I also tried to look in the source code, but I found nothing. How can I do that? thanks in advance
override func dayView(dayView: DayView, didLongPressTimelineAt date: Date) {
let newEvent = Event()
newEvent.startDate = date
newEvent.endDate = date.addingTimeInterval(3600)
// Customize your event...
newEvent.text = randomName() // A function that generates a new random name that haven't been used before.
self.create(event: newEvent)
}
override func create(event: EventDescriptor, animated: Bool = false) {
super.create(event: event, animated: animated)
self.events.append(event)
}
override func dayView(dayView: DayView, didUpdate event: EventDescriptor) {
for (index, eventFromList) in events.enumerated() {
if eventFromList.text == event.text {
events[index] = event
}
}
self.endEventEditing()
self.reloadData()
}
Please make sure that every Event have it's own unique name, otherwise it won't work

home page jump over login feature in Swift

I am pretty new to the swift, I am trying to implement the feature that when the user logged in, they will get directly to the home page rather than the login page every time they reopen the app.
I took the reference to the tutorial: https://www.youtube.com/watch?v=gjYAIXjpIS8&t=146s. and I implemented the is logged in boolean checking as he did, but I somehow encounter the trouble reopen the homepage while logged in. I have an error message:[Presentation] Attempt to present <UITabBarController: 0x7fa68102ea00> on <IFTTT.ViewController: 0x7fa67fe0c150> (from <IFTTT.ViewController: 0x7fa67fe0c150>) whose view is not in the window hierarchy.
This is how my login page controller class:(which is the entry point when opening the app) I tried present as the tutorial and performsegue and both shows up the same error message above
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if isLoggedIn() {
performSegue(withIdentifier: "logInJump", sender: nil)
}
}
fileprivate func isLoggedIn() -> Bool {
print("logged in status: \(UserDefaults.standard.bool(forKey: "isLoggedIn"))")
return UserDefaults.standard.bool(forKey: "isLoggedIn")
}
#IBAction func signInButton(_ sender: Any) {
print("sign in tapped")
if let appURL = URL(string: "http://vocation.cs.umd.edu/flask/register") {
UIApplication.shared.open(appURL) { success in
if success {
print("The URL was delivered successfully.")
} else {
print("The URL failed to open.")
}
}
} else {
print("Invalid URL specified.")
}
}
}
// button broder width
#IBDesignable extension UIButton {
#IBInspectable var borderWidth: CGFloat {
set {
layer.borderWidth = newValue
}
get {
return layer.borderWidth
}
}
#IBInspectable var cornerRadius: CGFloat {
set {
layer.cornerRadius = newValue
}
get {
return layer.cornerRadius
}
}
#IBInspectable var borderColor: UIColor? {
set {
guard let uiColor = newValue else { return }
layer.borderColor = uiColor.cgColor
}
get {
guard let color = layer.borderColor else { return nil }
return UIColor(cgColor: color)
}
}
}
and this is my messy story board and segues :
story board
I tried adding a navigation controller that the app entry point gets in there and it performs the isLoggedIn the same as the view controller class did, but it also has the same error.
Can someone walk me through how to fix it or any other better techniques? I felt like I am blind since I just get into the study of swift. Thank you!
You need to thread your request to perform the segue. Because your calling performSegue in the viewDidLoad() what happens is that your call is being called before everything is loaded, so you need to introduce some lag.
Threads are sometimes called lightweight processes because they have
their own stack but can access shared data. Because threads share the
same address space as the process and other threads within the
process, the operational cost of communication between the threads is
low, which is an advantage
An asynchronous function will await the execution of a promise, and an
asynchronous function will always return a promise. The promise
returned by an asynchronous function will resolve with whatever value
is returned by the function
Long story short, you need to wait for everything to be loaded into memory and if you're calling functions from the main stack/thread e.g. viewDidLoad() then there is a good chance that it hasn't been loaded into memory yet. Meaning, logInJump segue doesn't exist at that point in that view controller, thus your error.
The other possibility is you don't have the right view/segue ID but that should've thrown a different error.
Also, change the sender from nil to self. Actually this isn't necessary but I've always used self over nil
// change to desired number of seconds should be higher then 0
let when = DispatchTime.now() + 0.2
if isLoggedIn() {
DispatchQueue.main.asyncAfter(deadline: when) {
self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "logInJump", sender: nil)
}
}

How to make a button modify another button functions

I have two UIViewController. In the first one, I have a button that adds some views, one at a time, to the main view. In the second one, I set up a store, so that when I press on a button I unlock some features of my app. Now, I perfectly know (I hope) how to handle the part where I make the VCs comunicate and I trigger some other easy functions, what I don't know is how to make the store button increase the button's functions.
WHAT I NEED:
Right now the button adds a maximum of 10 views (complete version). I want that before the user buys my app, he gets to add a maximum of 3 views and then, when he buys it, the function I already have (the one to add 10 views)starts to work and replaces the other one.
MAIN VIEW CONTROLLER
var messageArray = [UIView] ()
I attached all of my UIView from the storyboard and I appended them to my array in the viewDid load like this: messageArray.append(View1)
#IBAction func addMessageViewButton(_ sender: Any) {
let hiddenViews = messageArray.filter { $0.isHidden }
guard !hiddenViews.isEmpty else {
let sheet = UIAlertController(title: "max reached", message: nil, preferredStyle: .actionSheet)
let ok = UIAlertAction(title: "OK", style: .cancel, handler: nil)
let closeAll = UIAlertAction(title: "Close all", style: .destructive) { (addMessage) in
view1.isHidden = true
view2.isHidden = true
view3.isHidden = true
view4.isHidden = true
view5.isHidden = true
view6.isHidden = true
view7.isHidden = true
view8.isHidden = true
view9.isHidden = true
view10.isHidden = true
}
sheet.addAction(ok)
sheet.addAction(closeAll)
present(sheet, animated: true, completion: nil)
return
}
let randomHiddenView = hiddenViews.randomElement()
randomHiddenView?.isHidden = false
}
SHOP VIEW CONTROLLER
Here I won't post all of the code because it would be too much and of course unnecessary, since the important thing to know here is that there's a button and if the user presses it and he proceeds with the purchase, he will get the function I posted up here working instead of the one that allows him to have just 3 views.
func unlock() {
let appdelegate = UIApplication.shared.delegate
as! AppDelegate
appdelegate.viewController!.functionToHave10Views()
//viewControlled is declared in the app delegate like this ` var viewController: ViewController?`
//I know I don't physically have `functionToHave10Views()`, but I guess I'll turn the code of my button into a function, so just to be clear, I'm referring to that function.
buyButton.isEnabled = false
}
In your main view controller:
var isLocked = true
#IBAction func addMessageViewButton(_ sender: Any) {
if isLocked {
// Do something for when is locked
} else {
// Do something for when is unlocked
}
}
Then in your shop view controller:
func unlock() {
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.viewController!.isLocked = false
buyButton.isEnabled = false
}

UIMenuController doesn't update menu for first time

I have UITextView on which I want to add highlight as custom menu item. I have registered to following notification UIMenuControllerWillShowMenuNotification.
The method for the notification is something like this:
if textIsHighlighted {
let highlightMenuItem = UIMenuItem(title: "Highlight", action: Selector("highlightText"))
UIMenuController.sharedMenuController().menuItems = [highlightMenuItem]
}
else {
let highlightMenuItem = UIMenuItem(title: "Dehighlight", action: Selector("highlightText"))
UIMenuController.sharedMenuController().menuItems = [highlightMenuItem]
}
Although the first time the menucontroller fails to update even though it executes the part of code. It shows the last value. Where should I write this part of code as I feel that during willShow menuController it's already created and thus fails to update.
Hopefully you've solved this by now, but I've just figured this one out myself:
Other answers have said you can update the menu items by adding it when the UIMenuControllerWillShowMenuNotification is called, but this wasn't working for me (iOS 9, Swift 2).
Instead I implemented the UITextView delegate method: textViewDidChangeSelection and set the relevant menu items there:
func textViewDidChangeSelection(textView: UITextView) {
if self.currentSelectionIsInHighlightedRange() {
self.setUpUnhighlightMenuItem()
} else {
self.setUpHighlightMenuItem()
}
}
private func currentSelectionIsInHighlightedRange() -> Bool {
let allHighlightedRanges = self.document.highlightedRanges()
let selectedTextRange = self.documentView.textView.selectedRange
for range in allHighlightedRanges {
let intersectionRange = NSIntersectionRange(range, selectedTextRange)
if intersectionRange.length > 0 {
return true
}
}
return false
}

Resources