How to stop viewWillAppear from completing until a function has finished - ios

I'm pretty new to IOS Application Development.
I'm trying to stop viewWillAppear from finishing until after my function has finished working. How do I do that?
Here's viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
checkFacts()
if reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
func checkFacts() {
let date = getDate()
var x: Bool = true
var ind: Int = 0
print("count is ", birdFacts.count)
while ind < birdFacts.count {
print("accessing each bird fact in checkFacts")
let imageAsset: CKAsset = birdFacts[ind].valueForKey("birdPicture") as! CKAsset
let image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
print(image)
if image == nil {
if (birdFacts[ind].valueForKey("sortingDate") != nil){
print("replacing fact")
print("accessing the sortingDate of current fact in checkFacts")
let sdate = birdFacts[ind].valueForKey("sortingDate") as! NSNumber
replaceFact(sdate, index: ind)
}
/*else {
birdFacts.removeAll()
print("removing all bird facts")
}*/
}
ind = ind + 1
print(ind)
}
self.saveFacts()
let y = checkRepeatingFacts()
if y {
print("removing all facts")
birdFacts.removeAll()
//allprevFacts(date, olddate: 0)
}
}
checkFacts references 2 others functions, but I'm not sure they're relevant here (but I will add them in if they are and I'm mistaken)

Instead of trying to alter or halt the application's actual lifecycle, why don't you try using a closure?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
checkFacts(){ Void in
if self.reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
}
func checkFacts(block: (()->Void)? = nil) {
let date = getDate()
var x: Bool = true
var ind: Int = 0
print("count is ", birdFacts.count)
while ind < birdFacts.count {
print("accessing each bird fact in checkFacts")
let imageAsset: CKAsset = birdFacts[ind].valueForKey("birdPicture") as! CKAsset
let image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
print(image)
if image == nil {
if (birdFacts[ind].valueForKey("sortingDate") != nil){
print("replacing fact")
print("accessing the sortingDate of current fact in checkFacts")
let sdate = birdFacts[ind].valueForKey("sortingDate") as! NSNumber
replaceFact(sdate, index: ind)
}
/*else {
birdFacts.removeAll()
print("removing all bird facts")
}*/
}
ind = ind + 1
print(ind)
}
self.saveFacts()
let y = checkRepeatingFacts()
if y {
print("removing all facts")
birdFacts.removeAll()
//allprevFacts(date, olddate: 0)
}
// CALL CODE IN CLOSURE LAST //
if let block = block {
block()
}
}
According to Apple Documentation:
Closures are self-contained blocks of functionality that can be passed around and used in your code.
Closures can capture and store references to any constants and variables from the context in which they are defined.
So by defining checkFacts() as: func checkFacts(block: (()->Void)? = nil){...} we can optionally pass in a block of code to be executed within the checkFacts() function.
The syntax block: (()->Void)? = nil means that we can take in a block of code that will return void, but if nothing is passed in, block will simply be nil. This allows us to call the function with or without the use of a closure.
By using:
if let block = block {
block()
}
we can safely call block(). If block comes back as nil, we pass over it and pretend like nothing happened. If block is not nil, we can execute the code contained within it, and go on our way.
One way we can pass our closure code into checkFacts() is by means of a trailing closure. A trailing closure looks like this:
checkFacts(){ Void in
if self.reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
Edit: Added syntax explanation.

So based on the comments, checkFacts is calling asynchronous iCloud operations that if they are not complete will result in null data that your view cannot manage.
Holding up viewWillAppear is not the way to manage this - that will just result in a user interface delay that will irritate your users.
Firstly, your view should be able to manage null data without crashing. Even when you solve this problem there may be other occasions when the data becomes bad and users hate crashes. So I recommend you fix that.
To fix the original problem: allow the view to load with unchecked data. Then trigger the checkData process and when it completes post an NSNotification. Make your view watch for that notification and redraw its contents when it occurs. Optionally, if you don't want your users to interact with unchecked data: disable appropriate controls and display an activity indicator until the notification occurs.

Related

Swift Func called after expected

I have a button setup so that it saves a CK record based on a users choice from a different part of the UI. Once the function is called the CKRecord is saved in a variable. The next operation the code should take is unwrapping that variable and using it to edit and save the CK record. The Issue is the function I call first, loadChallengeRecord(), isn't the first operation made when the button is pressed. Instead the unwrapping function is run first which is causing the program to exit the unwrap function because the record is nil, and then the loadChallengeRecord() function is called late. Here is the example:
func loadChallengeRecord() {
if let unwrapped = existingChallengeToDetails {
recordID = CKRecord.ID(recordName: unwrapped, zoneID: zone)
publicDatabase.fetch(withRecordID: recordID!) { (record, error) in
if record != nil {
self.currentChallenge = record
} else {
print("error fetching challenge record from server")
}
}
}
}
#IBAction func btnVote(_ sender: Any) {
// load record and save it to var existingChallengeToDetails
loadChallengeRecord()
if let unwrapped = existingChallengeToDetails { }// edit and save record
else { // error }
What am i doing wrong? How can i fix this? Can I denote a priority for these functions to run?
Write your function with completion handler like this
func loadChallengeRecord(completion:#escaping ()->Void) {
if let unwrapped = existingChallengeToDetails {
recordID = CKRecord.ID(recordName: unwrapped, zoneID: zone)
publicDatabase.fetch(withRecordID: recordID!) { (record, error) in
defer { completion }
if record != nil {
self.currentChallenge = record
} else {
print("error fetching challenge record from server")
}
}
}
}
Use it like this ... when it returns completion you can do other stuff dependent on this
loadChallengeRecord {
// do your stuff here
}
The easiest solution is to do the things you have to do in the (asynchronous) completion handler
func loadChallengeRecord() {
guard let unwrapped = existingChallengeToDetails else { return }
recordID = CKRecord.ID(recordName: unwrapped, zoneID: zone)
publicDatabase.fetch(withRecordID: recordID!) { (record, error) in
if let record = record {
self.currentChallenge = record
// edit and save record
} else {
print("error fetching challenge record from server", error!)
}
}
}
#IBAction func btnVote(_ sender: Any) {
// load record and save it to var existingChallengeToDetails
loadChallengeRecord()
}
The other two answers worked for the specific case, and work for many causes, but they weren't exactly what I was looking for. I kept running to similar issues and after some research I finally found what I was looking for: Semaphores
Here is a basic explanation:
func doSomething() {
let semaphore = DispatchSemaphore(value: 0) // Setup the semaphore to value:0
var x = 1
var y = 2
if y != 0 {
var sum = x + y
semaphore.signal(). // Sends signal that code is complete
// the code can continue from where semaphore.wait() is located.
}
semaphore.wait() // Wait for semaphore.signal() to fire, then continue to return
return sum // only after semaphore.signal() fires will this code run
}

Have to press same button twice to call function Swift 4

I am trying to call the function 'Checks' at the end of my button function, so that the 'Checks' function is able to check the value of labels in the app, and change the background colour, based off the value of the labels that were manipulated within the GoButton function. e.g. if the weather is 'Cloudy', then I'll display some rain clouds, and hide the sun, and hide the rain. Now, when I press the button, the button works fine, but the Checks function is not called, I then have to press the same button a second time, to get it to call?
I have tried placing the self.Checks() line above the catch, and outside of it, but it makes no difference, I still have to press the GoButton twice to get it to have an affect, and change the background.
Button Function:
//Go Button in Main View Controller
#IBAction func GoButton(_ sender: Any) {
//text IS EQUAL TO WHAT IS IN THE TEXT BOX
let text: String = userValue.text!
//API URL TO FETCH JSON DATA
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=***API***KEY***&units=Metric") else { return }
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
//JSON DECODER
let decoder = JSONDecoder()
do {
let weatherData = try decoder.decode(MyWeather.self, from: data)
if (self.MainLabel != nil)
{
if let gmain = (weatherData.weather?.first?.main) { //using .first because Weather is stored in an array
print(gmain)
DispatchQueue.main.async {
self.MainLabel.text! = String (gmain)
}
}
}
} catch {
print("Error in fetching data for your location: \(error)")
}
}.resume()
self.Checks()
}
Checks Function:
func Checks() {
//For some reason, this function doesn't get called on the first time you press the button? <<<<<<<<<<<<<<<<<<<<<
if (MainLabel.text! == "Rain") {
rainClouds.isHidden = false
rain.isHidden = false
sun.isHidden = true
} else if (MainLabel.text! == "Drizzle") {
rainClouds.isHidden = false
rain.isHidden = false
sun.isHidden = true
} else if (MainLabel.text! == "Clouds") {
rainClouds.isHidden = false
rain.isHidden = true
sun.isHidden = true
} else if (MainLabel.text! == "Cloudy") {
rainClouds.isHidden = false
rain.isHidden = true
sun.isHidden = true
}
}
Your issue is that you call self.Checks() in the wrong place. You are calling it long before the data is loaded (do some research on asynchronous calls). Move it to inside the completion handler where you set the label's text.
DispatchQueue.main.async {
self.MainLabel.text! = String (gmain)
self.Checks()
}
Unrelated but it is standard practice that function names, variable names, and enum cases start with lowercase letters. Class, struct, and enum names start with uppercase letters.
You have two places in your function that silently return when the guard conditions fail. I would tuck log statements in those blocks to see which one is causing it to exit prematurely the first time you call the function, and then you can work on why.
As a side note, I would also recommend using more descriptive function names, and ones that can't be confused with variable or object names.

Remove View and Reference in Array

Currently working on an video conferencing app where we add/remove subviews that have a video/OpenGL display when users join or leave. We keep track of the views in an array in the class. We're getting a BAD_ACCESS in GL ASM code when we remove the subview and also the array reference. Commenting out the self.streams.remove(at: index) will cause it to work.
func add(stream: StreamType, withView streamView: StreamViewType? = nil) -> Bool {
if !self.has(streamId: stream.streamId) {
let view = streamView ?? StreamViewType(stream: stream, userId: self.userId, delegate: self)
let insertAt = view.stream.isLocal ? self.streams.count : 0
self.streams.append(view)
self.streamViews.insertSubview(view as! UIView, at: insertAt)
self.delegate?.layoutManager(layoutOnly: true)
return true
}
return false
}
func remove(streamId: String?) -> StreamViewType? {
if let index = self.indexOf(streamId: streamId) {
let streamView = self.streams[index]
streamView.removeFromSuperview()
self.streams.remove(at: index)
self.delegate?.layoutManager(layoutOnly: true)
return streamView
}
return nil
}
It seems to be a race condition of some sort. Any ideas?
Its seems to be an error in you openGL code when deallocating your view.
How is StreamViewType implemented?
Did you tear down the EAGLContext when deallocating your view?

How to return a bool to the outside function when you have nested functions in Swift?

So I have a function I have that has an inner function that connects to a Firebase database. the function returns a bool but I need the to return true or false based on whats inside the Firebase database. So basically I need to return true or false inside of the Firebase function the problem is it thinks that I am trying to return to the Firebase function when I am actually trying to return to the outside function a simple example is this
func someOtherFunc(name: String, lastname: String){
}
func someFunc(name: String, lastname: String) -> bool {
someOtherFunc(name: name, lastname: lastname) {
if name == "George" {
return true
} else {
return false // will not work because some other func does not return a value
}
}
}
here is the code that I need fixed which is a little more complicated than the above function because the inner Firebase function runs asynchronously(in the background) and so all the code inside the function needs to run synchronously to return the correct value
Here is my function
func takeAwayMoney(_ howMuch: String) -> Bool{
if let notMuch = Int(howMuch) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let money = value?["money"] as? String ?? ""
//convert money to int
if let conMoney = Int(money) {
var conMoreMoney = conMoney
if conMoreMoney < notMuch {
print(" sorry you don't have enough money")
return false
} else {
conMoreMoney -= notMuch
let values = ["money": String(conMoreMoney)]
//update the users money
self.datRef.child("User").child(userID!).updateChildValues(values)
return true //doesn't work because of example above
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
This code does not compile because there is no return values to the main function.
I know the really hard way to fix this would be to initialize values at the top of the function that would be the money of the user then check it after dispatching it for a couple of seconds then you could return the values, but I know there must be another way because this way would cause a lot of problems.
The root cause is that takeAwayMoney is synchronous, but it uses observeSingleEvent, which is asynchronous.
The "right" way to solve this is to make takeAwayMoney return Void, but take a completion function that will give you the bool asynchronously, like this:
func takeAwayMoney(_ howMuch: String, completion: #escaping (Bool)->()) -> Void {
/// When you want to "return" the bool, call the completion. e.g.:
// ...
if conMoreMoney < notMuch {
print(" sorry you don't have enough money")
completion(false)
return // if you want to stop processing
}
// ...
}
If you cannot do this, then takeMoney needs to wait for the completion to finish and you use semaphores to have them communicate with each other. You do not just wait a couple of seconds. See this for an example:
Semaphores to run asynchronous method synchronously

Methods firing out of order

I have updateHeight, updateWeight, and updateBMI methods in my HealthAlgorithm class. I then try to call them in order in ViewController.swift
HealthAlgorithm.swift:
//MARK: Properties
var healthManager:HealthManager?
var kUnknownString = "Unknown"
var bmi:Double?
var height:HKQuantitySample?
var weight:HKQuantitySample?
func updateHeight() {
// 1. Construct an HKSampleType for weight
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
// 2. Call the method to read the most recent weight sample
HealthManager().readMostRecentSample(sampleType!, completion: { (mostRecentHeight, error) -> Void in
if( error != nil )
{
print("Error reading height from HealthKit Store: \(error.localizedDescription)")
return
}
var heightLocalizedString = self.kUnknownString
self.height = mostRecentHeight as? HKQuantitySample
print(self.height)
// 3. Format the height to display it on the screen
if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {
let heightFormatter = NSLengthFormatter()
heightFormatter.forPersonHeightUse = true
heightLocalizedString = heightFormatter.stringFromMeters(meters)
}
})
}
func updateBMI(){
if weight != nil && height != nil {
// 1. Get the weight and height values from the samples read from HealthKit
let weightInKilograms = weight!.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
let heightInMeters = height!.quantity.doubleValueForUnit(HKUnit.meterUnit())
bmi = ( weightInKilograms / ( heightInMeters * heightInMeters ) )
}
print("BMI: ",bmi)
}
I call these methods in ViewController.swift like this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
HealthAlgorithm().updateHeight()
HealthAlgorithm().updateWeight()
HealthAlgorithm().updateBMI()
}
The problem is that BMI is returned as nil. The reason this is happening is that the updateBMI method is firing before the updateHeight and updateWeight method.
I use print(self.height) right after I define the variable in the updateHeight method, and I use print("BMI: ", bmi) right after I define the bmi variable in the updateBMI method. Since I am calling updateHeight first, print(self.height) should happen before print("BMI: ", bmi), but for some reason, the BMI: nil is getting returned first which makes no sense to me.
The methods are not being called out of order. The problem is that the function completes asynchronously. You need to call dependent code from the completion handler.

Resources