Swift Unwrapping optional class variable yields failure - swift

In my code I have two classes: Reviews and BlogPosts:
class Review {
var author = ""
var stars = 0
}
class BlogPost {
var title = ""
var body = ""
var author = ""
var review: Review?
}
The review variable in BlogPost is optional, since not all blog posts might have a review.
I have a function which prints the amount of stars of a post:
func checkForPostStars(post: BlogPost) {
if let review = post.review {
print("\"\(post.title)\" has: \(review.stars) stars")
} else {
print("There is no review for the post.")
}
}
I then create two blog posts. The first one has no review, meaning that the function should print "There is no review for the post". For the other review I add an author and a star amount, but when I run the function it will still print "There is no review for the post".
var firstPost = BlogPost()
firstPost.title = "Famous developer has died!"
firstPost.body = "Lorem ipsum"
firstPost.author = "Riccardo Perego"
var secondPost = BlogPost()
secondPost.title = "iOS 12 is finally out!"
secondPost.body = "Lorem ipsum"
secondPost.author = "Riccardo Perego"
secondPost.review?.author = "John"
secondPost.review?.stars = 4
checkForPostStars(post: firstPost)
checkForPostStars(post: secondPost)
I have discovered that I can fix the issue by adding secondPost.review = Review() nevertheless, I would like the compiler to do it automatically as soon as it sees that I set a value for stars or author. Thanks.

The problem lies within these 2 lines:
secondPost.review?.author = "John"
secondPost.review?.stars = 4
The review is not initialized. It's like you would set some value to nil and expect it's properties to live even if it doesn't work... That's why the ? is there.
You should add constructor to the Review:
class Review {
var author: String
var stars: Int
init(author: String = "", stars: Int = 0) {
self.author = author
self.starts = stars
}
}
Also it is good practice not to assign the variables on class level scope, rather use it in initialiser.
Simply the problem is that you do not create Review instance, so you cannot add properties to it... you should handle it like this:
secondPost.review = Review(authoer: "John", stars: 4)
Also, for performance reasons, you should make the Review object struct instead of class...
So if you create a struct instead, Swift figures out the initialiser for you and life's even better:
struct Review {
var author: String
var stars: Int
}

Related

How to change properties of all set elements in Swift

enum Posts {
case roxham
case satellite
case zone34
}
struct Member: Hashable, Identifiable {
var name: String
var permanent: Bool
var shift: Shifts
var post: Posts?
var canLedger = false
let id = UUID()
}
let allMembers: Set<Member> = [Member(name: "Bob Smith", permanent: false, shift: .fiveAM),
Member(name: "Dave Johnson", permanent: false, shift: .sevenAM)
// This set contains 15 more members ]
var tempSet = Set<Member>()
What I am trying to do is to assign .satellite to the post property of each element of the allMembers set and then put them into the tempSet Set (or put them in tempSet first then change the property, doesn’matter). I have tried mapping but the fact that I’m using a struct makes it impossible. I have also attempted some loops but with the same result. Of note, I have many more members in allMembers, I only showed 2 for simplification purposes. I obviously know I can just write it in manually in allMembers, however I will have to this a lot throughout the code and the sets will become much larger. Thanks
How about:
let tempSet = Set<Member>(allMembers.map {
var temp = $0
temp.post = .satellite
return temp
})

class in playground vs in program file

I have a question about something simple that works in Playground but not in a Project: (Playground code below)
In the project where the class is in a separate swift file the code correction won't show me the the person.lastName and if I fully type it give me an error.... hmm, very strange - might be a beginner error or?
How would I have to state in in the program file and the separate swift file to work?
Thanks,
Roman
import UIKit
class human {
var firstName = ""
var lastName = ""
}
let person = human()
person.lastName = "Smith"
person.firstName = "Peter"
print (person.firstName)
print (person.lastName)
This is why I hate playgrounds. They are not really Swift. In real Swift, all executable code must be inside a function (e.g. a method of some class or struct or enum); you can't have lines like person.lastName = "Smith" just hanging in space like that.
So in a real iOS project you'd need to write something more like this:
class Human {
var firstName = ""
var lastName = ""
}
func test() {
let person = Human()
person.lastName = "Smith"
person.firstName = "Peter"
print (person.firstName)
print (person.lastName)
}
And even then nothing would happen until you actually call test(), and you can't do that except in a function. This is why people usually test code in the viewDidLoad of a view controller.
class Human {
var firstName = ""
var lastName = ""
}
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let person = Human()
person.lastName = "Smith"
person.firstName = "Peter"
print (person.firstName)
print (person.lastName)
}
}

Attempting to add food object to array causing crash Unexpectedly found nil while unwrapping an Optional value

I'm fairly new to swift and MVC. I'm getting html and parsing that to create a list of foods in season, which is working correctly.
I'm then trying to create food objects of each food and add them to a list of foods. I can create the food object but when I try to add them to the list of foods I get the error: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Here is my model:
import Foundation
class food {
var foodImageName: String = ""
var foodName: String = ""
init(fn: String) {
foodName = fn
foodImageName = "FreshFruitAndVeg.jpg"
}
}
class foodList {
var listOfFoods:[food]
init() {
listOfFoods = []
}
func add(foodType:food) {
self.listOfFoods.append(foodType)
print("Food added!")
}
func delete(rowNum: Int) {
self.listOfFoods.remove(at: rowNum)
}
}
In my FoodsViewController I get the current month and determine the currentSeason and the nextSeason from that. I arrange the html tags into an array which contains a lot of extraneous information, so I find the index of both the currentSeason and nextSeason elements. Once I have that information I run a for loop using those indices to create a food object and add it to myFoodList. I can see that the food objects are being created and I can print out the contents, but when I attempt to add it to myFoodList it gives the error described above.
I create my variables inside the FoodsViewController like this:
var currentSeason = ""
var nextSeason = ""
var currentSeasonIndex = 0
var nextSeasonIndex = 0
var endIndex = 0
var myFoodList: foodList!
Here is the code snippet where I create the food object and try to add it to the foodList
:
do {
for i in currentSeasonIndex+1..<nextSeasonIndex {
let text = try nextElements[i].text()
print("This season's foods: ", text)
let newFood = food.init(fn: text)
print ("New food to be added: ", newFood.foodName)
myFoodList.add(foodType: newFood)
for item in 0..<myFoodList.listOfFoods.count {
print("My food List: ", myFoodList.listOfFoods[item])
}
}
}
catch {
}
I can see in the debugger that the food object is there, but myFoodList is always nil causing a crash at the line
myFoodList.add(foodType: newFood)
I've been reading about the error and basically understand that it is expecting a value of some some, but there is nothing there, hence the nil which I can also see in the debugger.
I though maybe I needed to initialize the object with the init() function but that just causes errors in compilation
I can't figure out why this add function is causing this problem. Can anyone point out where I am going wrong?
This
var myFoodList: FoodList!
is nil if you don't init it. So first you need
var myFoodList = FoodList()

textual questionnaire system - swift 3

This question is about building a textual questionnaire system using swift 3.
Assuming we have (n) questions, the user will be asked one question at the time, replying with text (using UITextField).
if the answer is something that is expected (YES/NO for example) the code will carry on to the next question, otherwise it will try to give some more elaborate question with possible answers (see code below).
The idea is having some kind of an array of dictionaries, each dictionary (questionItem) contains a single question/answer data...
Question: even if the following code works fine, I feel that this is not the most scalable nor elegant solution. Thus my question is more about your ideas regarding code design.
how can one make a code such as this - that is scalable for multiple questions.
static var questionItem = [
"question": "Is the sky blue?",
"answer": "yes",
"afterAnswer": "question #2",
"explanation": "simply answer: yes or no"
]
class func getAnswer(answer: String) -> String
{
let expectedAnswer = questionItem["answer"]
let isEqual = (answer == expectedAnswer)
var respond = "undifined"
if isEqual {
// go for next question ( questionItem["afterAnswer"] )
respond = "ok, going to the next question"
} else {
// didn't got the answer I was waiting for, going to show some longer explanation
respond = questionItem["explanation"]!
}
// respond
return respond
}
You can create a struct with your desired information, then create a Trivia of Type Int: YourStruct to manage all the different questions answers and explanations, like my example below, let me know is its clear enough:
struct QuestionsAndAnswers{
var question: String = ""
var answer: String = ""
var explanation: String = ""
}
var trivia: [Int: QuestionsAndAnswers] = [:]
trivia = [1: QuestionsAndAnswers.init(question: "Your First Question", answer: "Your Fist Answer", explanation: "Your First explanation")]
trivia = [2: QuestionsAndAnswers.init(question: "Your Second Question", answer: "Your Second Answer", explanation: "Your Second explanation")]
func getAnswer(answer: String, quesiontNumber: Int) -> String
{
let expectedAnswer = trivia[quesiontNumber]?.answer
let isEqual = (answer == expectedAnswer)
var respond = "undifined"
if isEqual {
// go for next question ( questionItem["afterAnswer"] )
respond = "ok, going to the next question"
} else {
// didn't got the answer I was waiting for, going to show some longer explanation
respond = (trivia[quesiontNumber]?.explanation)!
}
// respond
return respond
}

Deleting with one-to-many relationship

I have a one-to-many relationship:
class GameSystem: Object {
dynamic var gameSystemName = ""
}
class games: Object {
dynamic var gameSystemName = gameSystemName().name
dynamic var gameTitle = ""
dynamic var gameGenre = ""
}
The gameSystemNames are currently displayed on a TableView. If the user deletes a gameSystemName, I want that gameSystemName along with all of that system's games deleted.
The code I'm currently using will only delete the GameSystem, but leaves all the games.
func deleteRowAtIndexPath(indexPath: NSIndexPath) {
let realm = Realm()
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete)
}
gameSystemTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
gameSystems = Realm(path: Realm.defaultPath).objects(GameSystem)
}
I'm assuming there's a simple way to do this.
If you keep your model as it is, the solution would be to query first for the relevant objects of the relation Game:
// …
let objectToDelete = gameSystems[indexPath.row]
let gameSystemName = objectToDelete.gameSystemName
realm.write {
let games = realm.objects(Game).filter("gameSystemName = ?", gameSystemName)
realm.delete(games)
realm.delete(objectToDelete)
}
// …
Model Recommendation
I'd propose instead that you add an explicit link to your model, instead of expressing the relationship through a loosely linked foreign key. But the object-mapping is very individual and may be dependent on further constraints going beyond the scope of your question and this answer. For further reference, that would look like below:
class GameSystem : Object {
dynamic var name = ""
let games = List<Game>()
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var gameSystem: GameSystem? {
return linkingObjects(GameSystem.self, forProperty: "games").first
}
}
If you setup your model like this, you can delete your games very easy:
// …
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete.games)
realm.delete(objectToDelete)
}
// …
Note: In the future, Realm will bring Cascading Deletes as feature. Once that is released, you won't even need to take care of the manual deletion of associated games, but you will rather be able to declare the strong relationship in your model, so that the Games are automatically deleted.
Alternative Link Declaration
You can also declare your link vice-versa, but that would make it likely harder to use a feature like Cascading Deletes in the future. However the code to delete them for now would look the same as above.
class GameSystem : Object {
dynamic var name = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var games: [Game] {
return linkingObjects(Game.self, forProperty: "gameSystem")
}
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
let gameSystem: GameSystem? = nil
}
It is very easy to delete the Parent as well as childrens in REALM SWIFT... You just need to write a small piece of code.
Here I am trying to delete my main category called "Swift" and whenever my main category gets deleted all the Sub Categories are also deleted...
do{
try realm.write({
realm.delete(Category.items)
realm.delete(Category)
})
}catch{
print("ERROR WHILE DELETING CELL ::: \(error)")
}
In short to delete subcategories all you need is to place "items" after . followed by parent name.