User Defaults Swift - ios

I have hint view (tooltip). And I want it display in my app 1 time per download app. When user downloading app this tooltip is showing and then dismiss. When user delete app and again downloading tooltip should work again.
let options: AMTooltipViewOptions = .init(textColor: Color.guideSubTitle,
textBoxBackgroundColor: Color.guideScreenBackground,
textBoxCornerRadius: 8,
lineColor: Color.guideScreenBackground,
lineHeight: 15,
dotSize: 0,
focusViewRadius: 15,
focustViewVerticalPadding: 0,
focustViewHorizontalPadding: 0)
AMTooltipView(options: options,
message: Localizable.scan_open_from_gallery + "\n" + Localizable.scan_clear,
focusView: content.openGalleryBtn, target: self)
and I have key
public var hintView: Bool {
get {
return setting.bool(forKey: Key.hintView)
}
set {
setting.set(false, forKey: Key.hintView)
}
}
How can I control when user deletes app and again download it

Store a bool in UserDefaults. Once the user uninstalls the app, the data will be deleted.
in your AppDelegate.swift
let DEFAULTS = UserDefaults.standard
var isUserFirstTime = !DEFAULTS.bool(forKey: "isUserFirstLogin") // by default it will store false, so when the user opens the app for first time, isUserFirstTime = true.
then inside your didFinishLaunchingWithOptions function
if isUserFirstTime {
// your code here to show toolbar
} else {
// dont show toolbar
}
// once you have completed the operation, set the key to true.
DEFAULTS.set(true, forKey: "isUserFirstLogin")

Change your getter and setter for hintView like below
public var hintView: Bool {
get {
return setting.bool(forKey: Key.hintView)
}
set {
setting.set(true, forKey: Key.hintView)
setting.synchronize()
}
}
And now use your hintView variable like below for showing and hiding the toolbar.
//it will always returns false for first time when you install new app.
if hintView {
print("Hide Toolbar")
}
else {
//set flag to true for first time install application.
hintView = true
print("Show Toolbar")
}
I hope it will more clear to you

import Foundation
import AMTooltip
class HintViewController {
let userDefaults: UserDefaults = .standard
let wasLaunchedBefore: Bool
var isFirstLaunch: Bool {
return !wasLaunchedBefore
}
init() {
let key = "wasLaunchBefore"
let wasLaunchedBefore = userDefaults.bool(forKey: key)
self.wasLaunchedBefore = wasLaunchedBefore
if !wasLaunchedBefore {
userDefaults.set(true, forKey: key)
}
}
func showHintView(message: String!, focusView: UIView, target: UIViewController) {
let options: AMTooltipViewOptions = .init(textColor: Color.guideSubTitle,
textBoxBackgroundColor: Color.guideScreenBackground,
textBoxCornerRadius: 8,
lineColor: Color.guideScreenBackground,
lineHeight: 15,
dotSize: 0,
focusViewRadius: 15,
focustViewVerticalPadding: 0,
focustViewHorizontalPadding: 0)
AMTooltipView(options: options, message: message, focusView: focusView, target: target)
}
}

Related

Swift UIKit label text doesn't update / view doesn't update

I have a problem:
I have a list of items this is controller A, and when I click on any item I go to controller B (item info), I then execute the ledLightingButton_Tapped function by pressing the corresponding button that activates the LED indicator for the animal.
#IBAction func ledLightingButton_Tapped(_ sender: Any) {
if !GlobalData.shared.led_animals.contains(GlobalData.shared.selectedAnimalId) {
GlobalData.shared.led_animals.append(GlobalData.shared.selectedAnimalId)
}
activateLED(at: GlobalData.shared.selectedAnimalId)
}
func activateLED(at animalId: String) {
ServerSocket.shared?.perform(
op: "ActivateLED",
with: [
"light_duration": "180",
"led_color": "White",
"client_data": "",
"led_animals": [animalId]
]
) { err, data in
guard err == nil else { return }
print(data)
let ledStatus = data[0]["led_request_status"].stringValue
self.ledStatusLabel.text = ledStatus
GlobalData.shared.isActiveLED = true
self.startTimer()
}
}
Upon successful activation, the animal number is added to the array, and the startTimer is called which every 10 seconds requests checkLEDStatus for all animals in the array.
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(updateCowStatus), userInfo: nil, repeats: true)
}
#objc func updateCowStatus() {
self.checkLEDStatus()
}
func checkLEDStatus() {
ServerSocket.shared?.perform(
op: "CheckStatusLED",
with: [
"light_duration": "180",
"led_color": "White",
"client_data": "",
"led_animals": GlobalData.shared.led_animals
]
) { err, data in
guard err == nil else {
GlobalData.shared.isActiveLED = false
self.stopTimer()
return
}
DispatchQueue.global(qos: .background).async {
for i in 0..<data.count {
if GlobalData.shared.selectedAnimalId == data[i]["animal_id"].stringValue {
let ledStatus = data[i]["led_request_status"].stringValue
if ledStatus.contains("Fail") {
guard let index = GlobalData.shared.led_animals.firstIndex(of: GlobalData.shared.selectedAnimalId) else { return }
GlobalData.shared.led_animals.remove(at: index)
}
DispatchQueue.main.async {
self.ledStatusLabel.text = ledStatus
}
}
}
}
}
}
The current status of the animal is displayed on the label. If you go in the controller A and activate the status + get a result from checkedLEDstatus - it is work for one animal - everything works, but if you go to controller B, activate for animal number 1, go out and open animal number 2 - perform activation, return to animal number 1 - then the label is no longer is updated, it does not display the new value, but I check it from debugging and property self.ledStatuslabel.text contains new value but UI didn't update. self.ledStatuslabel.text show old value.
Please help me, thanks!

How to show dialogue box of rate app after X times?

i made app with swift in Xcode and i have implemented Rate App Modal in my app but the problem is this its shown when users install the app and open for very first time but i want to show it after 2 or 3 days , so users can view my app and if he likes then he can rate my app. this is my code
let reviewService = ReviewService.shared
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let deadline = DispatchTime.now() + .seconds(120)
DispatchQueue.main.asyncAfter(deadline: deadline) { [weak self] in
self?.reviewService.requestReview()
}
}
and this is my ReviewService.swift file
private init() {}
static let shared = ReviewService()
private let defaults = UserDefaults.standard
private let app = UIApplication.shared
private var lastRequest: Date? {
get {
return defaults.value(forKey: "ReviewService.lastRequest") as? Date
}
set {
defaults.set(newValue, forKey: "ReviewService.lastRequest")
}
}
private var oneWeekAgo: Date {
return Calendar.current.date(byAdding: .day, value: -7, to: Date())!
}
private var shouldRequestReview: Bool {
if lastRequest == nil {
return true
} else if let lastRequest = self.lastRequest, lastRequest < oneWeekAgo {
return true
}
return false
}
func requestReview(isWrittenReview: Bool = false) {
guard shouldRequestReview else { return }
if isWrittenReview {
let appStoreUrl = URL(string: "https://itunes.apple.com/app/idxxxxxxx?action=write-review")!
app.open(appStoreUrl)
} else {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
} else {
// Fallback on earlier versions
}
}
lastRequest = Date()
}
}
When the code finds that no previous request was saved don’t show the request. Instead save that the last request happened 4 or 5 days ago. Then in a few days the first review request will be shown.
One way to do this would be in the shouldRequestReview getter. If lastRequset is nil make a new post dated request time and save that, then return false.

textField Editing Changed not reacting fast enough (Asynchronous calls)

I have a textfield that queries a firebase database for existing users and then display a UIImage according to if the user is available or not. The problem is that once the async code loads, the textfield doesn't react on changed value.
example. If i type 12345 as a username, i don't query the database. Everything ok. If i add a 6 it queries firebase and it shows me the user is free. if i press backspace and have 12345 the textFieldChanged is triggered again, and database is not queried. All OK.
but the problem is, when i have 12345, and i type 6 and very fast back so i have 12345, the query is running and shows me the available icon (because the back was pressed very fast). Is this because of the Simulator or is it a real problem and can i be fixed easily ?
my code:
#IBAction func textFieldChanged(_ sender: UITextField) {
if let username = usernameInputText.text, username.count > 5 {
checkIfUserExists(username: username) { doesExist in //(2)
if doesExist! {
self.completeSignupButton.isEnabled = false
self.ifAvailableImageView.image = UIImage(named: "Close")
} else {
self.completeSignupButton.isEnabled = true
self.ifAvailableImageView.image = UIImage(named: "Check")
}
}
} else {
ifAvailableImageView.image = UIImage(named: "Close")
self.completeSignupButton.isEnabled = false
}
}
func checkIfUserExists(username: String, completion: #escaping (Bool?) -> Void) {
spinner.startAnimating()
self.ifAvailableImageView.image = nil
let docRef = db.collection("users").document(username)
docRef.getDocument { (document, error) in
if error != nil {
self.spinner.stopAnimating()
completion(nil)
} else {
self.spinner.stopAnimating()
if let document = document {
if document.exists {
completion(true)
} else {
completion(false)
}
}
}
}
}
You can just compare the username being processed with the current text in the text field and not process the result if it not the same because you only want to process the latest one.
#IBAction func textFieldChanged(_ sender: UITextField) {
if let username = usernameInputText.text, username.count > 5 {
checkIfUserExists(username: username) { doesExist in //(2)
// Check if current text and the completion being processed are for the same username
if username != sender.text {
return
}
if doesExist! {
self.completeSignupButton.isEnabled = false
self.ifAvailableImageView.image = UIImage(named: "Close")
} else {
self.completeSignupButton.isEnabled = true
self.ifAvailableImageView.image = UIImage(named: "Check")
}
}
} else {
ifAvailableImageView.image = UIImage(named: "Close")
self.completeSignupButton.isEnabled = false
}
}

Using protocols with services in swift

I have implemented an Audio Capturing service in my swift project that is supposed to handle audio recording and translates it into text.
AudioCaptureService
class AudioCaptureService:
// EXLCUDED A BUNCH OF SETUP CODE
func record(textView: UITextView, microphoneButton: UIButton) {
if audioEngine.isRunning {
audioEngine.stop()
recognitionRequest?.endAudio()
microphoneButton.setImage(#imageLiteral(resourceName: "microphone-full-white").withRenderingMode(.alwaysOriginal), for: .normal)
print("stopped recording...")
} else if !audioEngine.isRunning, isRecordingEnabled{
startRecording(textView: textView)
print("start recording...")
microphoneButton.setImage(#imageLiteral(resourceName: "microphone-red").withRenderingMode(.alwaysOriginal), for: .normal)
}
}
fileprivate func startRecording(textView: UITextView) {
if recognitionTask != nil {
recognitionTask?.cancel()
recognitionTask = nil
}
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryRecord)
try audioSession.setMode(AVAudioSessionModeMeasurement)
try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
} catch {
print("audioSession properties weren't set because of an error.")
}
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
let inputNode = audioEngine.inputNode
guard let recognitionRequest = recognitionRequest else {
fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
}
recognitionRequest.shouldReportPartialResults = true
recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in
var isFinal = false
if result != nil {
textView.text = result?.bestTranscription.formattedString
isFinal = (result?.isFinal)!
}
if error != nil || isFinal {
self.audioEngine.stop()
inputNode.removeTap(onBus: 0)
self.recognitionRequest = nil
self.recognitionTask = nil
//self.microphoneButton.isEnabled = true
}
})
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
self.recognitionRequest?.append(buffer)
}
audioEngine.prepare()
do {
try audioEngine.start()
} catch {
print("audioEngine couldn't start because of an error.")
}
textView.text = ""
}
}
ViewController
In a separate view controller, I have a microphone button that is supposed to trigger this recording functionality. When the user clicks the microphone button, the button should turn red to indicate its recording, and then the audio that the user inputs will display in the textview of that view controller:
class PreviewController: UIViewController {
var notesOpen = false
let audioCaptureService = AudioCaptureService()
let microphoneButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(handleRecord), for: .touchUpInside)
button.setImage(#imageLiteral(resourceName: "muted-white").withRenderingMode(.alwaysOriginal), for: .normal)
button.isEnabled = false
return button
}()
lazy var notesView: UITextView = {
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let tv = UITextView()
tv.layer.cornerRadius = 8
tv.font = UIFont.boldSystemFont(ofSize: 12)
tv.backgroundColor = .white
tv.keyboardDismissMode = .onDrag
return tv
}()
//EXCLUDED SOME BASIC SETUP CODE
#objc func handleRecord() {
print("record button pressed")
toggleNotesView()
audioCaptureService.record(textView: notesView, microphoneButton: microphoneButton)
}
Issue
Right now my implementation works, but I'm suspicious that it could be improved. I don't think I should be worried about passing in the textView and microphone button into my AudioCaptureService? Ideally, I'd like to have these things separate without AudioCaptureService depending on having a textView and button passed to it to work.
I was reading about protocols and think this may be a solution, but I can't seem to wrap my head around how I would implement this.
I was thinking I could do something like :
protocol AudioCaptureServiceDelegate {
func record(textView: UITextView)
}
But then who would the PreviewController class be the delegate? Im just a bit confused on how to better implement my code and any suggestions would help.
KISS would say conform in UITextView, if its just an output delegate, see below. If you want to handle more in the delegate it should definitely go to into the PreviewViewController cause UITextView is only to display text.
Some would recommend it should go into some kind of controller since audio != View, that is up for debate. Controller would mean the thing that controls the PreviewViewController which then sets text on PreviewViewController to display every time the text changes. The recordButton then leads through PreviewViewControllers' delegate to the controller which then handles recording, maybe storing the recordings somewhere, etc.
All in all a ViewController which lets its parent controller handle recording and displays text and changes the recordButton depending on the state the parent controller sets on the ViewController.
// Variant 1
protocol AudioCaptureServiceOutputDelegate: class {
func audioCaptureServiceOutputDelegate(outputChanged: String)
}
extension UITextView: AudioCaptureServiceOutputDelegate {
func audioCaptureServiceOutputDelegate(outputChanged: String) {
self.text = outputChanged
}
}
// Variant 2
protocol AudioCaptureServiceOutputDelegate2: class {
var text: String! { get set }
}
extension UITextView: AudioCaptureServiceOutputDelegate2 {}
// Variant 3
protocol AudioCaptureServiceOutputDelegate3: class {
var audioCaptureServiceOutputText: String { get set }
}
extension UITextView: AudioCaptureServiceOutputDelegate3 {
var audioCaptureServiceOutputText: String {
get { return text }
set { text = newValue }
}
}
All three work just as good. Variant 2 is just a little bit worse since a Controller/ViewController implementing a variable with the random name text may be a little too generic.
A controller would probably prefer 1, since what should it return on get? It has maybe several recordings stored already.
Calling example
func record(outputDelegate: AudioCaptureServiceOutputDelegate, microphoneButton: UIButton) {
// ....
startRecording(outputDelegate: outputDelegate)
// ....
}
fileprivate func startRecording(outputDelegate: AudioCaptureServiceOutputDelegate) {
// ....
outputDelegate.audioCaptureServiceOutputDelegate(outputChanged: result?.bestTranscription.formattedString ?? "Error: result is nil.")
// ....
outputDelegate.audioCaptureServiceOutputDelegate(outputChanged: "")
}

Navigation UI not updating with Xcode 9

I tried to update my navigationItem titleView when I finished downloading data model from server.
Something like this :
private func loadNews() {
self.newsModelManager.sendRequest(inBackground: false, preSendHandler: { (isReachable) in
if isReachable {
} else {
appDelegate.showInternetFailedAlertView()
}
}, successHandler: { (response) in
print("loadNewsModel: successHandler")
}, errorHandler: { (response) in
print("loadNewsModel: errorHandler")
}, reloginFailHandler: { (response) in
appDelegate.showReloginFailedAlertView()
}) { (isReachable) in
let array = self.newsModelManager.loadFirstFiveNews()!
DispatchQueue.main.async {
self.setupTitle_ViewWith(array: array)
}
}
}
and in seupTitle_ViewWith(array:)
func setupTitle_ViewWith(array: [NewsModel]?) {
guard array != nil else { return }
let frame = CGRect(x: 0, y: 0, width: 200, height: 40)
let newsTitle_View = NewsTitleView(newsModelArray: array!, frame: frame)
newsTitle_View.newsTitleViewDelegate = self
self.title_View = newsTitle_View
}
this all works fine, until the last line self.title_View = newsTitle_View
I thought this would update my titleView to my CustomView,
But it's not....
What might went wrong there?
I first save model from server with realm and read at let array = self.newsModelManager.loadFirstFiveNews()!
But, say if I have data in device already and read directly without loading first, it works totally fine...
Hi here is the brief summary of what solved this issue for reference for others who might face the similar kind of issue,
The codes which #Ian has shared in the question has been added in the NavigationController subclass. The issue is caused here. Because you cannot able to update the navigation title nor the titleView from the navigation controller for little explanation refer this answer(https://stackoverflow.com/a/20923010/4510873)
So we tried updating the titleView from viewController and it solved our issue then.
I think you can solve this by using local notifications,
1) Change your loadNews function like this,
private func loadNews() {
self.newsModelManager.sendRequest(inBackground: false, preSendHandler: { (isReachable) in
if isReachable {
} else {
appDelegate.showInternetFailedAlertView()
}
}, successHandler: { (response) in
print("loadNewsModel: successHandler")
}, errorHandler: { (response) in
print("loadNewsModel: errorHandler")
}, reloginFailHandler: { (response) in
appDelegate.showReloginFailedAlertView()
}) { (isReachable) in
let array = self.newsModelManager.loadFirstFiveNews()!
DispatchQueue.main.async {
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object:array)
}
}
}
2) Then in your ViewController paste this in viewDidLoad
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.setupTitle_ViewWith), name: "refresh", object: array)
3)
func setupTitle_ViewWith(notification: NSNotification){
let array = notification.object as! NSArray
guard array != nil else { return }
let frame = CGRect(x: 0, y: 0, width: 200, height: 40)
let newsTitle_View = NewsTitleView(newsModelArray: array!, frame: frame)
newsTitle_View.newsTitleViewDelegate = self
self.title_View = newsTitle_View
}

Resources