I'm struggling to understand why my code isn't working? Any help/correction is appreciated.
struct DogBowlWithFailableInitializers {
var foodLeft: Int
init?(foodLeft: Int) {
if foodLeft < 0 {
return nil
}
self.foodLeft = foodLeft
}
}
if let negativeDogFoodTest = DogBowlWithFailableInitializers(foodLeft: 10) {
print("Success!")
let negativeDogFoodTest = DogBowlWithFailableInitializers(foodLeft: 10)
}
else {
print("Invalid dog food amount inputted")
}
From my understanding, a new instance should be created since it passed both if statements inside the structure and out. But when I try to access any properties within the instance, I'm not able to, why is that?
print(negativeDogFoodTest.foodLeft) //doesn't work
You don't seem to understand the concept of scoping. Consider this simple if let statement:
if let x = y {
// A
someVariable = x // works
}
// B
someVariable = x // does not work
x is only accessible inside the if statement, i.e. at A. Outside the if statement, i.e. at B, x is out of scope.
Why? Because it makes no sense to access x at B because B is executed whether or not y is nil.
So you should access it inside the if statement.
Another problem is that you declared negativeDogFoodTest twice:
// 1st time
if let negativeDogFoodTest = DogBowlWithFailableInitializers(foodLeft: 10) {
print("Success!")
// 2nd time!
let negativeDogFoodTest = DogBowlWithFailableInitializers(foodLeft: 10)
}
One time is enough!
if let negativeDogFoodTest = DogBowlWithFailableInitializers(foodLeft: 10) {
print("Success!")
print(negativeDogFoodTest.foodleft)
}
You can also consider a guard statement:
guard let negativeDogFoodTest = DogBowlWithFailableInitializers(foodLeft: 10)
else { return }
negativeDogFoodTest is a local variable with scope in the then-branch of your if. It does not exist outside of that block.
So you need to do this:
if let negativeDogFoodTest = ... {
print(negativeDogFoodTest.foodLeft)
}
Note that your second assignment to negativeDogFoodTest is redundant.
You can also use guard instead:
guard let negativeDogFoodTest = ... else {
dealWithFailure()
(return|break|continue)
}
print(negativeDogFoodTest.foodLeft)
Related
I've been trying to extract some data from a dictionary. There should be 5 values. The first code snippet fails, only populating the scheduleForCurrentDay with 1 value, whereas the second snippet works, getting all 5. Can anyone explain why the first one doesn't work? I'm guessing it's something to do with the dictionary being copied but I'm not really sure.
// Fails; only gets one value
private var categories = [StudyCategory]() {
didSet {
let c = categories
for subject in c {
guard let target = subject.quota[currentDayComponent] else { continue }
scheduleForCurrentDay[subject.title] = target
}
}
}
// Succeeds; gets all 5 values
private var categories = [StudyCategory]() {
didSet {
let c = categories
var schedule = [String:Double]()
for subject in c {
schedule[subject.title] = subject.quota[currentDayComponent]
}
scheduleForCurrentDay = schedule
}
}
When using if/else to return a or b base on the condition, i got this error: Cannot convert value of type 'Observable<_.Element>' to specified type 'Observable<Int>'
let a = Observable.of(1)
let b = Observable.of(2)
var test = true
let c: Observable<Int> = Observable.of(3).flatMap { _ in
if test {
return a
} else {
return b
}
}
But when i changed to conditional operator, it worked fine:
let a = Observable.of(1)
let b = Observable.of(2)
var test = true
let c: Observable<Int> = Observable.of(3).flatMap { _ in
return test ? a : b
}
Why does the first one not work? What is the different between to cases?
Swift compiler usually has a hard time inferring types, especially with complex frameworks such as RxSwift. You can (and usually should) help the compiler by explicitly stating your intent and declaring the return type for the closure, e.g.:
let a = Observable.of(1)
let b = Observable.of(2)
var test = true
let c: Observable<Int> = Observable.of(3).flatMap { _ -> Observable<Int> in
if test {
return a
} else {
return b
}
}
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.
I am reading a very large json file and pulling in data. I can currently pull in the correct data, but when I try and append it to an array, it ends up replacing it.
func parseJSON(json: JSON){
let predicate = {
(json: JSON) -> Bool in
if let jsonID = json["class"].string where jsonID == "sg-header-heading"{
return true
}
return false
}
var backclass = ViewController()
let foundJSON = json.findmultiple(backclass, predicate: predicate)
Using this extension to search for multiple values that match "sg-header-heading"
When I print the array in the extension this is what I get. But when I print it above I get nil. Also I am only getting one value per instead of 6 values in the end.
extension JSON{
func findmultiple(viewclass: ViewController, #noescape predicate: JSON -> Bool) -> JSON? {
var backclass = ViewController()
if predicate(self) {
return self
}
else {
if let subJSON = (dictionary?.map { $0.1 } ?? array) {
for json in subJSON {
if let foundJSON = json.findmultiple(backclass, predicate: predicate) {
let shorten = foundJSON["html"].stringValue
let firstshort = shorten.componentsSeparatedByString(" ")
let secondshort = firstshort[1].componentsSeparatedByString("\r")
let classname = secondshort[0]
if(classname == "STUDY HALL (INSTRUCT)"){
print("skip")
}else{
backclass.importClass.append(classname)
print("fsgsfg \(backclass.importClass)")
//backclass.collection.reloadData()
}
}
}
}
}
return nil
}
}
You are first calling findmultiple() from the outermost context (let foundJSON = json.findmultiple(predicate)). As the code is executed and findmultiple() is actually executed it creates a new instance of with var backclass = ViewController(). As the code goes into its loop it executes findmultiple() again, recursively. As the code goes into its loop an additional time it creates a new instance of backclass. Each time it goes through the loop, the recursion goes deeper and a new instance of ViewController is created each time so backclass always points to a new instance of ViewController.
The solution to this would be to create backclass at a higher level. You might try an approach like this:
var backclass = ViewController()
let foundJSON = json.findmultiple(backclass, predicate)
Note that this would mean passing backclass to findmultiple from inside the loop where the recursive call happens.
I am using SwiftyJSON to call some APIs and fetch some data.
When I use:
if let variable = json["response"]["fieldname"] {
} else {
println("error")
}
I am not able to use the variable later on, for example to append the value to an array.
For example:
if let variable1 = json["response"]["fieldname1"] {
} else {
println("error")
}
if let variable2 = json["response"]["fieldname2"] {
} else {
println("error")
}
var currentRecord = structure(variable1, variable2) ---> This line returns an error (use of unresolved identifier variable1) as not able to find variable1 or variable2
myArray.append(currentRecord)
How can I solve this?
The scope of an if let is inside the brackets immediately following it:
if let jo = joseph {
// Here, jo is in scope
} else {
// Here, not in scope
}
// also not in scope
// So, any code I have here that relies on jo will not work
In Swift 2, a new statement, guard was added, that seems to have exactly the kind of behaviour you want:
guard let jo = joseph else { // do something here }
// jo is in scope
If you're stuck in Swift 1, though, an easy way for you to unwrap those variables without a pyramid of doom is:
if let variable1 = json["response"]["fieldname1"], variable2 = json["response"]["fieldname2"] {
var currentRecord = structure(variable1, variable2)
myArray.append(currentRecord)
} else {
println("error")
}
#oisdk already explained that the scope of a variable defined by if let is only inside the braces of that statement.
That's what you want, because if it if let statement fails, the variable is undefined. The whole point of if let is to unwrap your optional safely, so that inside the braces, you can be sure the variable is valid.
Another solution to your problem (in Swift 1.2) is to use multiple if let statements:
if let variable1 = json["response"]["fieldname1"],
let variable2 = json["response"]["fieldname2"]
{
//This code will only run if both variable1 and variable 2 are valid.
var currentRecord = structure(variable1, variable2)
myArray.append(currentRecord)}
else
{
println("error")
}
Your code checks variable2 always even variable 1 fails. But that causes (edited!) not the error.
You can check and assign both variables in the same line. The "true" branch will be executed only if both variables are not nil
let response = json["response"]
if let variable1 = response["fieldname1"], variable2 = response["fieldname2"] {
let currentRecord = structure(variable1, variable2)
myArray.append(currentRecord)
} else {
println("error")
}