Array of structs: How to save in coredata? - arrays

I´m trying to save an array of structs into coredata. I did a lot of research, but i cannot find the solution.
Here´s what i´ve got:
import Cocoa
import CoreData
class ViewController: NSViewController {
struct StudentsStruct {
let firstName: String
let lastName: String
let age: Int
}
let Studentsdata: [StudentsStruct] = [StudentsStruct(firstName: "Albert", lastName: "Miller", age: 24), StudentsStruct(firstName: "Susan", lastName: "Gordon", age: 24), StudentsStruct(firstName: "Henry", lastName: "Colbert", age: 24)]
override func viewDidLoad() {
super.viewDidLoad()
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
for items in Studentsdata {
student.firstName = StudentsStruct.firstName
student.lastName = StudentsStruct.lastName
student.age = StudentsStruct.age
}
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
print (student)
}
}
The DatabaseController is solution i´ve got from this tutorial:
https://www.youtube.com/watch?v=da6W7wDh0Dw
It´s not so important, it´s just making the "getContext" function.
Whats important, in teh commandline "student.firstName = StudentsStruct.firstName" i´m getting the error "instance member "firstName" cannot be used on type ViewController.StudentStruct.
After trying and trying, i´m running out of ideas how to get the array of structs into coredata.
This is the DatabaseController file:
import Foundation
import CoreData
class DatabaseController {
private init() {
}
class func getContext() -> NSManagedObjectContext {
return DatabaseController.persistentContainer.viewContext
}
// MARK: - Core Data stack
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "StudentCoreFile")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
})
return container
}()
class func saveContext () {
let context = DatabaseController.persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
for any help thanks in advance!
Ok, you are right, i forgot to execute the fetchrequest. Here´s my current code:
import Cocoa
import CoreData
class ViewController: NSViewController {
struct StudentsStruct {
let firstName: String
let lastName: String
let age: Int
}
let Studentsdata: [StudentsStruct] = [StudentsStruct(firstName: "Albert", lastName: "Miller", age: 24), StudentsStruct(firstName: "Susan", lastName: "Gordon", age: 24), StudentsStruct(firstName: "Henry", lastName: "Colbert", age: 24)]
override func viewDidLoad() {
super.viewDidLoad()
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
for item in Studentsdata {
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
student.firstName = item.firstName
student.lastName = item.lastName
student.age = Int16(item.age)
}
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
print("number of results: \(searchResults.count)")
for result in searchResults as [Student] {
print(student)
}
} catch {
print ("error")
}
}
}
It´s running without errors. Now i´m getting 32 search results. Every entry is: age = 0; firstName = nil; lastName = nil;
For comparison, this code, without the loop is working:
import Cocoa
import CoreData
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
student.firstName = "henry"
student.lastName = "miller"
student.age = 22
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
print("number of results: \(searchResults.count)")
for result in searchResults as [Student] {
print(student)
}
} catch {
print ("error")
}
}
}

You need to access the item in your for loop also you are currently accessing the same object Student object in for loop instead of that you need to create a new Student in every iteration of for loop.
for item in Studentsdata {
//Create new student in for loop
let student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
//To get firstName, lastName and age access the item
student.firstName = item.firstName
student.lastName = item.lastName
student.age = item.age
}
//Save context now
DatabaseController.saveContext()

In case someone is interested, I found the solution:
You first have to set up the struct in the CoredataEntity Class like that:
import Foundation
import CoreData
struct StudentsStruct {
let firstName: String
let lastName: String
let age: Int
}
#objc(Student)
public class Student: NSManagedObject {
#NSManaged public var firstName: String?
#NSManaged public var lastName: String?
#NSManaged public var age: Int16
var allAtributes : StudentsStruct {
get {
return StudentsStruct(firstName: self.firstName!, lastName: self.lastName!, age: Int(self.age))
}
set {
self.firstName = newValue.firstName
self.lastName = newValue.lastName
self.age = Int16(newValue.age)
}
}
}
Then use the same struct to paste the data:
import Cocoa
import CoreData
class ViewController: NSViewController {
let studentsdata: [StudentsStruct] = [StudentsStruct(firstName: "Albert", lastName: "Miller", age: 24), StudentsStruct(firstName: "Susan", lastName: "Gordon", age: 24), StudentsStruct(firstName: "Henry", lastName: "Colbert", age: 24)]
override func viewDidLoad() {
super.viewDidLoad()
for items in studentsdata {
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
student.allAtributes = items
}
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
print("number of results: \(searchResults.count)")
for result in searchResults as [Student] {
print("student: \(firstName), \(lastName), \(age)" )
}
} catch {
print ("error: \(error)")
}
}
}
Thats it.

Perhaps this is lazy. You could also just encode your array as a json object and then create a field on your NSManagedObject for it as a transformable. When you want to retrieve you'd just decode and downcast to the proper type. That's what I did in one of my projects; worked fine.

Related

Appending Data Read in from FireBase to Array results in Empty Array Swift

I have two frames, one which displays the employees and another that adds employees, which then updates the first frame using #Binding. In the ListFrame struct where I display the list of employees, I am attempting to read in the data from Firebase (getData func) and append it to the array. I am successful in reading the data, as the employeesTemp array which I initialize in the getData function gets populated correctly. However, when I try to append the values in the correctly populated employeesTemp array to the employees global array, the global array stays empty, which means my first frame doesn't display anything. I have read other posts that talk about the fact that Firebase's operations are async, but after going through dozens of stackoverflows I have not been able to find a solution. Any help is appreciated.
import SwiftUI
import Firebase
struct Employee: Identifiable {
let id = UUID()
var firstName: String
var lastName: String
var emailID: String
var education: String
// combo drop down box
var department: String
var address: String
var salary: String
}
struct ListFrame: View {
#State var employees = [Employee]()
init() {
self.getData()
}
func getData() {
// get reference to data base
let db = Firestore.firestore()
// temp employee list (gets populated correctly)
var employeesTemp = [Employee]()
// read in the employees from employeelist data base
db.collection("EmployeeList").getDocuments { snapshot, error in
// checking for errors
if error == nil {
// no errors
if let snapshot = snapshot {
// get all documents and create employees
DispatchQueue.main.async {
print("adding employee")
employeesTemp = [Employee]()
employeesTemp = snapshot.documents.map { document in
let new_employee:Employee = Employee(firstName: document["firstName"] as? String ?? "", lastName: document["lastName"] as? String ?? "" , emailID: document["emailID"] as? String ?? "", education: document["education"] as? String ?? "", department: document["department"] as? String ?? "", address: document["address"] as? String ?? "", salary: document["salary"] as? String ?? "")
print(new_employee)
employeesTemp.append(new_employee)
print(employeesTemp)
print("appending to global employees")
employees.append(contentsOf: employeesTemp)
print(employees)
//self.employees = employeesTemp
return new_employee
}
}
}
}
else {
// handle error
print("error")
}
}
}
var body: some View {
NavigationView {
VStack {
Spacer()
List {
ForEach(employees) { employee in
Text(employee.firstName)
}
}
.navigationTitle("List of Employees")
Spacer()
HStack {
Button(action: {
}) {
NavigationLink(destination: AddEmployeeFrame(employees: $employees)) {
Text("Add Employee")
}
}
}
}
}
}
}
struct AddEmployeeFrame: View {
#State private var firstName: String = ""
#State private var lastName: String = ""
#State private var emailID: String = ""
#State private var education: String = ""
// combo drop down box
#State private var department: String = ""
#State private var address: String = ""
#State private var salaryString: String = ""
#Binding var employees: [Employee]
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
func addData(firstName: String, lastName: String, emailID: String, education: String, department: String, address: String, salary: String) {
// get reference to database
let db = Firestore.firestore()
// add a document to collection
db.collection("EmployeeList").addDocument(data: ["firstName": firstName, "lastName": lastName, "emailID": emailID,
"education": education, "department": department, "address": address,
"salary": salary]) { error in
if error == nil {
// no error
//self.getData()
}
else {
// handle error
}
}
}
var body: some View {
NavigationView {
// text fields
VStack(spacing: 50) {
Group {
TextField("First Name", text: $firstName).multilineTextAlignment(.center)
TextField("Last Name", text: $lastName).multilineTextAlignment(.center)
TextField("Email ID", text: $emailID).multilineTextAlignment(.center)
TextField("Education", text: $education).multilineTextAlignment(.center)
}
.navigationTitle("Add Employee")
Menu("Select Department") {
Button("Other") {
department = "Other"
print(department)
}
Button("IT Department") {
department = "IT Department"
print(department)
}
Button("Storage Department") {
department = "Storage Department"
print(department)
}
Button("HR Department") {
department = "HR Department"
print(department)
}
Button("Testing Department") {
department = "Testing Department"
print(department)
}
}
TextField("Address", text: $address).multilineTextAlignment(.center)
TextField("Salary", text: $salaryString).multilineTextAlignment(.center)
// cancel and save buttons
HStack() {
Spacer()
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
})
Spacer()
Button(action: {
let new_employee = Employee(firstName: firstName, lastName: lastName, emailID: emailID, education: education, department: department, address: address, salary: salaryString)
// adding data to server
self.addData(firstName: new_employee.firstName, lastName: new_employee.lastName, emailID: new_employee.emailID, education: new_employee.education, department: new_employee.department, address: new_employee.address, salary: new_employee.salary)
employees.append(new_employee)
print(employees)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Save")
})
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListFrame()
}
}

Creating an array from stuct data SwiftUI

First of all, i am very sorry for the noob question, but i just cant seem to figure this out.
I am very new to coding and just started to get my feet wet with SwiftUI, following a few courses and started to dabble in trying to create some basic apps.
I am currently working on an app that does an API call and displays the data.
My issue is, im trying to put the decoded data into an array, it sounds so simple and I think i am missing something very easy, but for the life of me I cant seem to figure it out.
Below is the codable struct I have
struct Drinks: Codable, Identifiable {
let id = UUID()
let strDrink : String
let strInstructions: String
let strDrinkThumb: String?
let strIngredient1: String?
let strIngredient2: String?
let strIngredient3: String?
let strIngredient4: String?
let strIngredient5: String?
}
I want to put the ingredients into an Array so I can go through them in lists etc
import SwiftUI
struct IngredientView: View {
let drink : Drinks
let ingredientArray : [String] = [] // I want to append the ingredients here
var body: some View {
GroupBox() {
DisclosureGroup("Drink Ingredience") {
ForEach(0..<3) { item in
Divider().padding(.vertical, 2)
HStack {
Group {
// To use the array here
}
.font(Font.system(.body).bold())
Spacer(minLength: 25)
}
}
}
}
}
}
Again, sorry for the noob question that probably has a simple answer, but worth a shot asking :D
Thanks!
you could use this approach to get all your ingredients into an array and use
it in Lists. The idea is to use a function to gather all your ingredients into an array of
Ingredient objects. You could also use a computed property.
It is best to use a Ingredient object and declare it Identifiable
so that when you use them in list and ForEach, each one will be
unique, even if the names are the same.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var drinkList = [Drink]()
var body: some View {
List {
ForEach(drinkList) { drink in
VStack {
Text(drink.strDrink).foregroundColor(.blue)
Text(drink.strInstructions)
ForEach(drink.allIngredients()) { ingr in
HStack {
Text(ingr.name).foregroundColor(.red)
Text(ingr.amount).foregroundColor(.black)
}
}
}
}
}
.task {
let theResponse: ApiResponse? = await getData(from: "https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita")
if let response = theResponse {
drinkList = response.drinks
}
}
}
func getData<T: Decodable>(from urlString: String) async -> T? {
guard let url = URL(string: urlString) else {
print(URLError(.badURL))
return nil // <-- todo, deal with errors
}
do {
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print(URLError(.badServerResponse))
return nil // <-- todo, deal with errors
}
return try JSONDecoder().decode(T.self, from: data)
}
catch {
print("---> error: \(error)")
return nil // <-- todo, deal with errors
}
}
}
struct ApiResponse: Decodable {
var drinks: [Drink]
}
struct Drink: Decodable, Identifiable {
let id = UUID()
let idDrink: String
let strDrink: String
let strDrinkThumb: String
let strAlcoholic: String
let strGlass: String
let strInstructions: String
let strIngredient1: String?
let strIngredient2: String?
let strIngredient3: String?
let strIngredient4: String?
let strIngredient5: String?
let strIngredient6: String?
let strIngredient7: String?
let strIngredient8: String?
let strIngredient9: String?
let strIngredient10: String?
var strMeasure1: String?
var strMeasure2: String?
var strMeasure3: String?
var strMeasure4: String?
var strMeasure5: String?
var strMeasure6: String?
var strMeasure7: String?
var strMeasure8: String?
var strMeasure9: String?
var strMeasure10: String?
// --- here adjust to your needs, could also use a computed property
func allIngredients() -> [Ingredient] {
return [
Ingredient(name: strIngredient1 ?? "", amount: strMeasure1 ?? ""),
Ingredient(name: strIngredient2 ?? "", amount: strMeasure2 ?? ""),
Ingredient(name: strIngredient3 ?? "", amount: strMeasure3 ?? ""),
Ingredient(name: strIngredient4 ?? "", amount: strMeasure4 ?? ""),
Ingredient(name: strIngredient5 ?? "", amount: strMeasure5 ?? ""),
Ingredient(name: strIngredient6 ?? "", amount: strMeasure6 ?? ""),
Ingredient(name: strIngredient7 ?? "", amount: strMeasure7 ?? ""),
Ingredient(name: strIngredient8 ?? "", amount: strMeasure8 ?? ""),
Ingredient(name: strIngredient9 ?? "", amount: strMeasure9 ?? ""),
Ingredient(name: strIngredient10 ?? "", amount: strMeasure10 ?? "")
].filter{!$0.name.isEmpty}
}
}
struct Ingredient: Identifiable {
let id = UUID()
var name: String
var amount: String
}
change you struct to
struct Drink: Codable, Identifiable {
let id = UUID()
let strDrink : String
let strInstructions: String
let strDrinkThumb: String?
let strIngredients: [String] = []
}
wherever you want to loop through ingredients you can use drink.strIngredients array
First of all your design cannot work because you are ignoring the root object, a struct with a key drinks
struct Root : Decodable {
let drinks : [Drink]
}
A possible solution is to write a custom init method. However this a bit tricky because you have to inject dynamic CodingKeys to be able to decode the ingredients in a loop
First create a custom CodingKey struct
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil } // will never be called
}
In the Drink struct – by the way it's supposed to be named in singular form – specify a second container, create the ingredient keys on the fly, decode the ingredients in a loop and append the results to an array until the value is nil. Somebody should tell the owners of the service that their JSON structure is very amateurish. As you have to specify the CodingKeys anyway I mapped the keys to more meaningful and less redundant struct member names.
struct Drink: Decodable, Identifiable {
private enum CodingKeys : String, CodingKey {
case name = "strDrink", instructions = "strInstructions", thumbnail = "strDrinkThumb"
}
let id = UUID()
let name: String
let instructions: String
let thumbnail: URL
let ingredients: [String]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.instructions = try container.decode(String.self, forKey: .instructions)
self.thumbnail = try container.decode(URL.self, forKey: .thumbnail)
var counter = 1
var temp = [String]()
let anyContainer = try decoder.container(keyedBy: AnyKey.self)
while true {
let ingredientKey = AnyKey(stringValue: "strIngredient\(counter)")!
guard let ingredient = try anyContainer.decodeIfPresent(String.self, forKey: ingredientKey) else { break }
temp.append(ingredient)
counter += 1
}
ingredients = temp
}
}
In the view display the ingredients like this
struct IngredientView: View {
let drink : Drink
var body: some View {
GroupBox() {
DisclosureGroup("Drink Ingredience") {
ForEach(drink.ingredients) { ingredient in
Divider().padding(.vertical, 2)
HStack {
Group {
Text(ingredient)
}
.font(Font.system(.body).bold())
Spacer(minLength: 25)
}
}
}
}
}
}

SwiftUI - How to append data to nested struct?

I have Student class that is connected to a struct - Details. Which has a nested struct - Subjects. I know how to append to a normal struct or class but I am having difficulties trying to append to a nested struct. What I have is a Form where a student's name and number of subjects are asked after which they have to enter the subject name and grade. Then, press the save button in the Toolbar items/ NavigationBarItems.
class Students: ObservableObject {
#Published var details = [Details]()
}
struct Details: Identifiable {
let id = UUID()
let name: String
struct Subjects: Identifiable {
let id = UUID()
let name: String
let grade: String
}
let subjects: [Subjects]
}
The View class:
import SwiftUI
struct TestStudentView: View {
#StateObject var students = Students()
#State private var name = ""
#State private var numberOfSubjects = ""
#State private var subject = [String](repeating: "", count: 10)
#State private var grade = [String](repeating: "", count: 10)
#State private var details = [Details.Subjects]()
var body: some View {
NavigationView {
Group {
Form {
Section(header: Text("Student details")) {
TextField("Name", text: $name)
TextField("Number of subjects", text: $numberOfSubjects)
}
let count = Int(numberOfSubjects) ?? 0
Text("Count: \(count)")
Section(header: Text("Subject grades")) {
if count>0 && count<10 {
ForEach(0 ..< count) { number in
TextField("Subject", text: $subject[number])
TextField("Grade", text: $grade[number])
}
}
}
}
VStack {
ForEach(students.details) { student in
Text(student.name)
ForEach(student.subjects) { subject in
HStack {
Text("Subject: \(subject.name)")
Text("Grade: \(subject.grade)")
}
}
}
}
}
.navigationTitle("Student grades")
.navigationBarItems(trailing:
Button(action: {
//let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
//students.details.append(details)
//^Above to append
}, label: {
Text("Save")
})
)
}
}
}
I have tried creating a variable of type [Subjects] but that would not let me append to it after the Textfield values are entered it gives me the error : “Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols” (Which makes sense, as it would require a button). I have also tried appending to it once the save button is pressed using a ForEach but that also gives me the same error.
I think you want the model change to be the responsibility of your Students class. Try adding a public method to Students and call it from your view, like this:
class Students: ObservableObject {
#Published var details = [Details]()
public func addDetails(_ details : Details) {
details.append(details)
}
}
Then in your button action in the View, replace students.details.append(details) with a call to this method:
let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
students.details.append(details)
Is that what you're trying to do?
Your view tries to do too much for a single view. Your trying to add students and grades in a single view. The answer of Asperi is correct. However your error is indicating that something else is wrong with your code. Try to run the code below in an isolated environment and it should work fine.
For now I just added a save button for each grade it will add the grade to the first student always.
import SwiftUI
class Students: ObservableObject {
#Published var details = [Details]()
}
struct Details: Identifiable {
let id = UUID()
let name: String
struct Subjects: Identifiable {
let id = UUID()
let name: String
let grade: String
}
var subjects: [Subjects]
}
struct TestStudentView: View {
#StateObject var students = Students()
#State private var name = ""
#State private var numberOfSubjects = ""
#State private var subject = [String](repeating: "", count: 10)
#State private var grade = [String](repeating: "", count: 10)
#State private var details = [Details.Subjects]()
var body: some View {
NavigationView {
Group {
Form {
Section(header: Text("Student details")) {
TextField("Name", text: $name)
TextField("Number of subjects", text: $numberOfSubjects)
}
let count = Int(numberOfSubjects) ?? 0
Text("Count: \(count)")
Section(header: Text("Subject grades")) {
if count>0 && count<10 {
ForEach(0 ..< count) { number in
TextField("Subject", text: $subject[number])
TextField("Grade", text: $grade[number])
Button(action: {
if students.details.count > 0 {
var test = students.details[0]
students.details[0].subjects.append(Details.Subjects(name: subject[number], grade: grade[number]))
}
}) {
Text("Save")
}
}
}
}
}
VStack {
ForEach(students.details) { student in
Text(student.name)
ForEach(student.subjects) { subject in
HStack {
Text("Subject: \(subject.name)")
Text("Grade: \(subject.grade)")
}
}
}
}
}
.navigationTitle("Student grades")
.navigationBarItems(trailing:
Button(action: {
let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
students.details.append(details)
//^Above to append
}, label: {
Text("Save")
})
)
}
}
}

Swift 4 Copy an array of objects by value which have array inside

I'm trying to understand how the copy() function works in Swift 4. I have two classes which are structured like this:
class Project {
var id: Int
var name: String
var team: [Person]?
init(id: Int, name: String, team: [Person]?) {
self.id = id
self.name = name
self.team = team
}
}
class Person {
var id: Int
var name: String
var project: Project?
init(id: Int, name: String, project: Project?) {
self.id = id
self.name = name
self.project = project
}
}
In my program I have an array of projects and what I'm trying to do is to create a copy of the values in the array the following way:
// arrProjects is some array of projects.
let projectsCopy = arrProjects.map { $0.copy() } as! [Project]
For this to work, I've implemented the NSCopying protocol to both classes the following way:
extension Project: NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
let teamCopy = self.team?.map { $0.copy() } as! [Person]?
return Project(id: self.id, name: self.name, team: teamCopy)
}
}
extension Person: NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
let projectCopy = self.project?.copy() as! Project?
return Person(id: self.id, name: self.name, project: projectCopy)
}
}
However, when I run the code and the arrProjects.map { $0.copy() } runs, the app freezes as if it was cycling and a Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5a61ff8) error is thrown at the let teamCopy = self.team?.map { $0.copy() } as! [Person]? line.
Any idea where I'm going wrong?
You should't use copy() inside the definition of public func copy(with zone: NSZone? = nil) -> Any {
You are creating an infinite loop.
extension Project: NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
let copy = Project(id: self.id, name: self.name, team: self.team)
return copy
}
}
extension Person: NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
let copy = Person(id: self.id, name: self.name, project: self.project)
return copy
}
}

How to mutate Values inside a Array

I need to mutate the following array:
struct Person {
var name: String
var age = 0
}
func showPersonArray() -> [Person] {
var dataArray = [Person]()
dataArray.append(Person(name: "Sarah_Yayvo", age: 29))
dataArray.append(Person(name: "Shanda_Lear", age: 45))
dataArray.append(Person(name: "Heidi_Clare", age: 45))
return dataArray
}
How could I split the "name"-key into two keys: "givenName" and "familyName".
Some nice person gave me this code before:
let arraySeparated1 = dataArray.map { $0.substring(to: $0.range(of: "_")!.lowerBound) }
let arraySeparated2 = dataArray.map { $0.substring(from: $0.range(of: "_")!.upperBound) }
Is it possible to make the mutation inside the struct?
The function showPersonArray() is only for demo and test.
Maybe there is a way to work with a target struct, like this:
struct Persontarget {
var familyname: String
var givenName: String
var age = 0
}
struct Person: Array -> [Persontarget] {
var name: String
var age = 0
// Here the split/mutating code
return [PersonWithTwoNames]
}
Or with an String extension. Possibly my question sounds pretty newby-like, but i´m trying the whole day...
Thanks everyone!
I would write an initializer on the new Person type, which initializes it from the old person type (which I called LegacyPerson):
import Foundation
struct LegacyPerson {
let name: String
let age: Int
}
func getPersonArray() -> [LegacyPerson] {
return [
LegacyPerson(name: "Sarah_Yayvo", age: 29),
LegacyPerson(name: "Shanda_Lear", age: 45),
LegacyPerson(name: "Heidi_Clare", age: 45)
]
}
struct Person {
let familyName: String
let givenName: String
let age: Int
}
extension Person {
init(fromLegacyPerson person: LegacyPerson) {
let index = person.name.range(of: "_")!
self.init(
familyName: person.name.substring(from: index.upperBound),
givenName: person.name.substring(to: index.lowerBound),
age: person.age
)
}
}
let people: [Person] = getPersonArray().map(Person.init)
people.forEach{ print($0) }
Create a method or computer variable for your Person class that returns what you want.
func firstName() -> String {
return self.substring(to: $0.range(of: "_")!.lowerBound)
}
You should not force cast though
with help of computed property defined in an extension
struct Person {
let name: String
let age: Int
}
let person = Person(name: "Maria_Terezia", age: 300)
extension Person {
var names:[String] {
get {
return name.characters.split(separator: "_").map(String.init)
}
}
}
for name in person.names {
print(name)
}
prints
Maria
Terezia

Resources