Deep copy of array with elements containing an array? - swift

I'm trying to make a deep copy of a list of the following objects:
struct Book {
var title: String
var author: String
var pages: Int
}
struct BookShelf {
var number: Int
}
class BookShelfViewModel {
var bookShelf: BookShelf
var number: Int
var books: [BookViewModel]?
init(bookShelf: BookShelf) {
self.bookShelf = bookShelf
self.number = bookShelf.number
}
required init(original: BookShelfViewModel) {
self.bookShelf = original.bookShelf
self.number = original.number
}
}
class BookViewModel {
var book: Book
var title: String
var author: String
var pages: Int
init(book: Book) {
self.book = book
self.title = book.title
self.author = book.author
self.pages = book.pages
}
required init(original: BookViewModel) {
self.book = original.book
self.title = original.title
self.author = original.author
self.pages = original.pages
}
}
Books for BookShelf is fetched in the BookShelfViewModel.
If I go like:
var copiedArray = originalArray
for bs in copiedArray {
bs.books = bs.books.filter { $0.title == "SampleTitle" }
}
The above filter both the copiedArray and the originalArray, and I obviously just want the copiedArray altered.
When I clone the array like this:
var originalArray = [BookShelfViewModel]()
... // Fill the array
var clonedArray = originalArray.clone()
clonedArray is cloned, but clonedArray.books is empty.
I've created the extension and followed this gist. How do I clone the array in the objects in the array?
I've done a quick playground to visualize the problem, hopefully it helps understand what I'm talking about.

In your copying initialiser in BookShelfViewModel you don't actually clone the books array. You need to add self.books = original.books?.clone() to required init(original: BookShelfViewModel)
class BookShelfViewModel: Copying {
var bookShelf: BookShelf
var number: Int
var books: [BookViewModel]?
init(bookShelf: BookShelf) {
self.bookShelf = bookShelf
self.number = bookShelf.number
}
required init(original: BookShelfViewModel) {
self.bookShelf = original.bookShelf
self.books = original.books?.clone()
self.number = original.number
}
}

Related

Picker in swiftUI is selecting document ID instead of document field from Firestore

So what I am trying to do is use a firebase collection within a picker in a form in swiftui but when I select the sport I want, the document ID of that sport is what gets saved to my variable.
So this is my view where I have the picker.
struct ContentView: View {
#ObservedObject var sportsObserved = sportsObserver()
#State private var sport = ""
var body: some View {
VStack{
Form {
Picker(selection: $sport, label: Text("Sport")) {
ForEach(self.sportsObserved.sports) { i in
Text(i.sportName)
}
}
Text("You selected \(sport)")
}
}
}
}
These are the fields of my Firestore document.
struct datatype3: Identifiable{
var id : String
var sportName: String
var eventCount: Int
}
This is my database observable object which wasn't as necessary but I included it just in case someone needed it to fully reproduce the bug I am experiencing.
class sportsObserver: ObservableObject{
#Published var sports = [datatype3]()
init() {
let db = Firestore.firestore()
db.collection("sports").addSnapshotListener{ (snap, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
if i.type == .added {
let id = i.document.documentID
let sportName = i.document.get("sportName") as! String
let eventCount = i.document.get("eventCount") as! Int
self.sports.append(datatype3(id: id, sportName: sportName, eventCount: eventCount))
}
if i.type == .removed {
let id = i.document.documentID
for j in 0..<self.sports.count{
if self.sports[j].id == id{
self.sports.remove(at: j)
return
}
}
}
if i.type == .modified {
let id = i.document.documentID
let sportName = i.document.get("sportName") as! String
let eventCount = i.document.get("eventCount") as! Int
for j in 0..<self.sports.count{
if self.sports[j].id == id{
self.sports[j].sportName = sportName
self.sports[j].eventCount = eventCount
return
}
}
}
}
}
}
}
And the result of this is as follows once I chose "Basketball" :
As you can see the document ID of Basketball is set to 12Basketball in my database, this cannot simply be changed to "Basketball" because of other things I have to take into consideration when constructing my database.
What I would like is for the variable "sport" to store "Basketball" which is the sportName and not "12Basketball" which is the documentID.
This is a fix that I came up with, I would still love if someone could show me a better way as this way is quite clunky.
.onAppear {
for i in sportsObserved.sports {
if sportID == i.id {
sport = i.sportName
}
}
Picker(selection: $sportID, label: Text("Sport")) {
ForEach(self.sportsObserved.sports) { i in
Text(i.sportName).tag(i.id)
}
}
Posting the fix as a Community Wiki for visibility:
.onAppear {
for i in sportsObserved.sports {
if sportID == i.id {
sport = i.sportName
}
}
Picker(selection: $sportID, label: Text("Sport")) {
ForEach(self.sportsObserved.sports) { i in
Text(i.sportName).tag(i.id)
}
}

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

Instance Member Cannot Be Used On Type - Firebase

I am new to Swift and I am following a tutorial on how to create a social media app with Xcode and Firebase. However, I got this error:
Instance member 'database' cannot be used on type 'DatabaseReference'
Here is my code:
import Foundation
import Firebase
class Post {
private var _username: String!
private var _userImg: String!
private var _postImg: String!
private var _likes: Int!
private var _postKey: String!
private var _postRef: DatabaseReference
var username: String {
return _userImg
}
var postImg: String {
get {
return _postImg
}set {
_postImg = newValue
}
}
var likes: Int {
return _likes
}
var postKey: String {
return _postKey
}
init(imgUrl: String, likes: Int, username: String, userImg: String) {
_likes = likes
_postImg = postImg
_username = username
_userImg = userImg
}
init(postKey: String, postData: Dictionary<String, AnyObject>) {
_postKey = postKey
if let username = postData["username"] as? String {
_username = username
}
if let userImg = postData["userImg"] as? String{
_userImg = userImg
}
if let postImage = postData["imageUrl"] as? String {
_postImg = postImage
}
if let likes = postData["likes"] as? Int {
_likes = likes
}
_postRef = DatabaseReference.database().reference().child("posts")
}
}
I get my error on the third to last line that says:
_postRef = DatabaseReference.database().reference().child("posts")
The database property is an instance type, meaning it must be referenced by an instance of DatabaseReference. Your call to DatabaseReference.database is accessing for a class, or static, type. You need to change your call to an instance of DatabaseReference.
Presumably, you need to initialize an instance of DatabaseReference. I don't know Firebase to know what is required for that, but that will take care of your issue.
Essentially:
let databaseReference = DatabaseReference() // Likely won't work, but some init method here will
_postRef = databaseReference.database()... // Whatever you need here
It sounds like you're looking for either:
_postRef = Database.database().reference("posts")
Or
_postRef = DatabaseReference.root.child("posts")

Pull out data from a numbered variable using a loop in swift

I have a realm database. So that the database is more readable. I have designed the questionbank as follows:
class Question: Object {
#objc dynamic var id: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var answered: Bool = false
#objc dynamic var lastAnswer: Bool = false
#objc dynamic var howManyTimesAnswered: Int = 0
#objc dynamic var answer0: String = ""
#objc dynamic var answer1: String = ""
#objc dynamic var answer2: String = ""
#objc dynamic var answer3: String = ""
#objc dynamic var correctAnswer: Int = 0
let parentCategory = LinkingObjects(fromType: Category.self, property: "questions") //back relationship to category
}
I am trying to pull out the answers and putting them into an array of tuples (String, Bool)
var currentAnswers = [(String, Bool)]()
for i in 0...3 {
if i == question.correctAnswer {
currentAnswers.append(("question.answer2", true))
} else {
currentAnswers.append((question.answer1, false))
}
}
what I want to achieve is something like this where the answer pulled out is equal to i obviously the one below will not compile
for i in 0...3 {
if i == question.correctAnswer {
currentAnswers.append((question.answer(i), true))
} else {
currentAnswers.append((question.answer(i), false))
}
}
You can use Key path Try this code
var currentAnswers = [(String, Bool)]()
for i in 0...3 {
if i == question.correctAnswer {
currentAnswers.append((question.value(forKey: "answer\(i)") as! String, true))
} else {
currentAnswers.append((question.value(forKey: "answer\(i)") as! String, false))
}
}
I have managed to get a workaround it. Only one line of extra code.
I hope this will help if someone wants to achieve something similar.
var currentAnswers = [(String, Bool)]()
let answers = [question.answer0, question.answer1, question.answer2, question.answer3]
for i in 0...3 {
if i == question.correctAnswer {
currentAnswers.append((answers[i], true))
} else {
currentAnswers.append((answers[i], false))
}
}
I have been using tuple above for true randomization so that the answers always show up in different order on the answer buttons.

How to list all Variables of a class in swift

Is there a way to list all Variables of a class in Swift?
For example:
class foo {
var a:Int? = 1
var b:String? = "John"
}
I want to list it like this: [a:1, b:"John"]
How you can do it in Swift 3.0 recursively:
import Foundation
class FirstClass {
var name = ""
var last_name = ""
var age = 0
var other = "abc"
func listPropertiesWithValues(reflect: Mirror? = nil) {
let mirror = reflect ?? Mirror(reflecting: self)
if mirror.superclassMirror != nil {
self.listPropertiesWithValues(reflect: mirror.superclassMirror)
}
for (index, attr) in mirror.children.enumerated() {
if let property_name = attr.label {
//You can represent the results however you want here!!!
print("\(mirror.description) \(index): \(property_name) = \(attr.value)")
}
}
}
}
class SecondClass: FirstClass {
var yetAnother = "YetAnother"
}
var second = SecondClass()
second.name = "Name"
second.last_name = "Last Name"
second.age = 20
second.listPropertiesWithValues()
results:
Mirror for FirstClass 0: name = Name
Mirror for FirstClass 1: last_name = Last Name
Mirror for FirstClass 2: age = 20
Mirror for FirstClass 3: other = abc
Mirror for SecondClass 0: yetAnother = YetAnother
The following should use reflection to generate the list of members and values. See fiddle at http://swiftstub.com/836291913/
class foo {
var a:Int? = 1
var b:String? = "John"
}
let obj = foo()
let reflected = reflect(obj)
var members = [String: String]()
for index in 0..<reflected.count {
members[reflected[index].0] = reflected[index].1.summary
}
println(members)
Output:
[b: John, a: 1]
Maybe a bit late for the party, but this solution using reflection and Mirror is 100% working:
class YourClass : NSObject {
var title:String
var url:String
...something other...
func properties() -> [[String: Any]] {
let mirror = Mirror(reflecting: self)
var retValue = [[String:Any]]()
for (_, attr) in mirror.children.enumerated() {
if let property_name = attr.label as String! {
retValue.append([property_name:attr.value])
}
}
return retValue
}
}
and somewhere in your code...
var example = MoreRow(json: ["title":"aTitle","url":"anURL"])
print(example.listPropertiesWithValues())
I got clue from here. https://medium.com/#YogevSitton/use-auto-describing-objects-with-customstringconvertible-49528b55f446
This is a demo above Swift 4.0.
import Foundation
extension CustomStringConvertible {
var description : String {
var description: String = ""
if self is AnyObject { // unsafeAddressOf((self as! AnyObject))
description = "***** \(type(of: self)) - <\(Unmanaged.passUnretained(self as AnyObject).toOpaque())>***** \n"
} else {
description = "***** \(type(of: self)) *****\n"
}
let selfMirror = Mirror(reflecting: self)
for child in selfMirror.children {
if let propertyName = child.label {
description += "\(propertyName): \(child.value)\n"
}
}
return description
}
}
extension NSObject {
var description: String {
var description: String = ""
if self is AnyObject { // unsafeAddressOf((self as! AnyObject))
description = "***** \(type(of: self)) - <\(Unmanaged.passUnretained(self as AnyObject).toOpaque())>***** \n"
} else {
description = "***** \(type(of: self)) *****\n"
}
let selfMirror = Mirror(reflecting: self)
for child in selfMirror.children {
if let propertyName = child.label {
description += "\(propertyName): \(child.value)\n"
}
}
return description
}
}
class AA: CustomStringConvertible {
var a: String = "aaa"
}
class BB: NSObject {
var b: String = "bbb"
}
let aa = AA()
print(aa)
let bb = BB()
print(bb.description)
Output --
***** AA - <0x00000001001038e0>*****
a: aaa
***** BB - <0x0000000100103310>*****
b: bbb