i am new to Swift programming and have been working on a To-Do List app . I am trying to use the Permanent Data Storage to save the information entered by user,but i keep getting the error "Thread 1: signal SIGABRT " . When i checked the output log, i see the error
"Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '-[__NSCFArray
insertObject:atIndex:]: mutating method sent to immutable object'"
My code is below. I use a simple textbox and a button:
#IBOutlet var text1: UITextField!
#IBAction func button1(_ sender: AnyObject) {
let listObject = UserDefaults.standard.object(forKey: "lists")
var items:NSMutableArray
if let tempitems = listObject as? NSMutableArray {
items = tempitems
items.addObjects(from: [text1.text!])
} else {
items = [text1.text!]
}
UserDefaults.standard.set(items, forKey: "lists")
text1.text = ""
}
The crash is exactly what it means: you can't mutate an immutable object. Try this:
var items: NSMutableArray!
if let listObject = UserDefaults.standard.object(forKey: "lists") as? NSArray {
items = listObject.mutableCopy() as! NSMutableArray
} else {
items = NSMutableArray()
}
items.addObjects(from: [text1.text!])
Related
I have a view with a button that calls an API, the API either returns an HTTP code 200 or 400 based on a particular scenario.
The button works just fine and everything works smoothly if code 200 is returned, however if code 400 is returned, the view is not updated that the user have to click on the button once again to get the updated message.
I added the http code property as a published variable in the VM's class and the http is an observable, but it doesn't get updated in the view on the first API call, I'm not sure what I'm missing.
I made a lot of changes to the shared code just to help in demonstrating the actual problem.
Update: Also I think another part of the problem, is that the url function returns the value before the url session returns the data, I don't know why this is happening, that when I execute it a second time it uses the values from the previous execution.
HTTPError Class
class HTTPError : Codable, ObservableObject {
var statusCode: Int?
var message: [String]?
var error: String?
init(statusCode: Int? = nil, message: [String]? = [], error: String? = nil){
self.statusCode = statusCode
self.message = message ?? []
self.error = error
}
convenience required init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.statusCode = try container.decodeIfPresent(Int.self, forKey: .statusCode)
do {
self.message = try container.decodeIfPresent([String].self, forKey: .message)
} catch {
guard let value = try container.decodeIfPresent(String.self, forKey:
.message) else {return}
self.message = []
self.message?.append(value)
}
self.error = try container.decodeIfPresent(String.self, forKey: .error)
}
VM Class
class VM: ObservableObject {
#Published var isLoading = true
#Published var httpError = HTTPError()
func checkDriverIn(_ record: DriverQRParam) async -> (Bool) {
...
var request = URLRequest(url: url)
...
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
...
let task = URLSession.shared.dataTask(with: request) { (data, response,
error) in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
self.httpError = try! JSONDecoder().decode(HTTPError.self, from: data)
//gets updated just fine in this class//
}
task.resume()
}catch {
print("Couldn't encode data \(String(describing: error))")
}
if httpError.statusCode != nil && httpError.statusCode == 400 {
return (false)
} else {
return (true)
}
}
View.Swift
struct xyz: View {
#State private var VM = VM()
Button("click") {
Task {
await VM.checkDriverIn(driverParam)
}
}
}
I have a List structure that removes a line with ondelete, but while returning the result, the line is deleted but shows an error.
I think the problem is that the result is not updated correctly.
What code should I add to update the list?
List{
ForEach(datosRealm.aquaris, id: \.self) { item in
Text(item.nombre)
}.onDelete { (indexSet) in
let aqua = datosRealm.aquaris[indexSet.first!]
let realm = try! Realm()
try! realm.write{
realm.delete(aqua)
}
}
}
import SwiftUI
import RealmSwift
final class DadesRealm: ObservableObject {
#Published var aquaris: [Aquaris]
private var cargaToken: NotificationToken?
init() {
let realm = try! Realm()
aquaris = Array(realm.objects(Aquaris.self))
recargarDatos()
}
private func recargarDatos() {
let realm = try! Realm()
let personas = realm.objects(Aquaris.self)
cargaToken = personas.observe { _ in
self.aquaris = Array(personas)
}
}
deinit {
cargaToken?.invalidate()
}
}
class Aquaris: Object {
#objc dynamic var nombre = ""
#objc dynamic var litros = ""
#objc dynamic var tipoAcuario = ""
#objc dynamic var data : Date = Date()
#objc dynamic var id = UUID().uuidString
override static func primaryKey() -> String? {
return "id"
}
let mascota = List<Controls>()
}
Error:
libc++abi.dylib: terminating with uncaught exception of type
NSException
*** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.' terminating with uncaught
exception of type NSException
The problem is taking a live updating Realm results object and casting it to an array, making those objects non-live updating (static)
In other words realm Results objects always reflect the current state of that data; if an object is deleted from Realm, the realm results object will also have that object removed. Likewise if an object is added then the results object will reflect that addition. (noting this is for objects that fit however the results objects was crafted; filtered, sorted etc)
So here's the issue
final class DadesRealm: ObservableObject {
#Published var aquaris: [Aquaris]
private var cargaToken: NotificationToken?
init() {
let realm = try! Realm()
aquaris = Array(realm.objects(Aquaris.self)) <- PROBLEM IS HERE
So you need to do one of two things
1 - Make aquaris a realm results object
#Published var aquaris: Results<Aquaris>? = nil
then
aquaris = realm.objects(Aquaris.self)
or
2 - If you must use that as an array, when the object is deleted from Realm, you also need to delete it from the array to keep them 'in sync'
I advise using suggestion 1 as it will make things easier in the long run.
This is my code, my app crashes in the middle of printing the data, without an error message in the log. it prints almost 30 people and then crashes, with this message on the line of code that crashed:
Thread 1: EXC_BREAKPOINT (code=1, subcode =.....)
I will mark the line of code with //CRASH where this message appears on in my code:
import UIKit
import Contacts
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for cont in contacts {
print(cont.givenName)
let num = ((cont.phoneNumbers.first?.value)! as CNPhoneNumber).stringValue //CRASH
print(num)
}
// Do any additional setup after loading the view, typically from a nib.
}
lazy var contacts: [CNContact] = {
let contactStore = CNContactStore()
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactPhoneNumbersKey,
CNContactImageDataAvailableKey,
CNContactThumbnailImageDataKey] as [Any]
// Get all the containers
var allContainers: [CNContainer] = []
do {
allContainers = try contactStore.containers(matching: nil)
} catch {
print("Error fetching containers")
}
var results: [CNContact] = []
// Iterate all containers and append their contacts to our results array
for container in allContainers {
let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)
do {
let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
results.append(contentsOf: containerResults)
} catch {
print("Error fetching results for container")
}
}
return results
}()
}
I thought I may be unwrapping nil but its not the case since it is not an optional (I tried to unwrap it in a safe way and the compiler says it is not an optional type).
From the comments:
The problem turns out to be the forced unwrapping of cont.phoneNumbers.first?.value since it will be nil if there are no phone numbers (and therefore no first to evaluate).
I am facing a problem while saving the NSManagedObject to NSManagedObjectContext in Swift 3.0 and Xcode 8. Adding the code snippets for better Understanding
let config = NSManagedObject(entity: entityDescription!, insertInto: self.moc) as! Config
Here Config class is derived from NSManagedObject
class Config: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
Assigning the Key and value to my config as below and calling a save
config.key = "access_token"
config.value = access_token
do
{
try config.managedObjectContext?.save()
}catch let error as NSError
{
NSLog(error.localizedDescription)
onCompletion("Login Failed")
return
}
This doesnt throw any error to me, but while fetching the value of access_token from NSManagedObject, value is nil
do
{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Config")
let predicate = NSPredicate(format: "key == %#", "access_token")
fetchRequest.predicate = predicate
let fetchResults = try moc.fetch(fetchRequest) as? [Config]
if(fetchResults?.count > 0)
{
//NSLog((fetchResults?.first!.value)!)
return fetchResults?.first!.value
}
} catch let error as NSError{
NSLog(error.localizedDescription)
}
What is wrong with this piece of code?
EDIT: I can see the following code where persistentStoreCoordinator is set for managedObjectContext
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Thanks to Apple my iOS 9 Project 'Swift 2.3' is completely unusable with iOS 10's 'Swift 3'...
I fixed almost everything except that I am having issue with using NSURLSession, Xcode is telling me that it has been renamed to URLSession, if I rename it Xcode will tell me:
use of undeclared type URLSession
Foundation is imported.
What is the issue?!
For example I am using it this way...
lazy var defaultSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "reCoded.BGDownload")
configuration.sessionSendsLaunchEvents = true
configuration.isDiscretionary = true
let session = URLSession(configuration: configuration, delegate: self, delegateQueue, queue: nil)
return session
}()
and even with the delegate methods the same issue.
Try using Foundation.URLSession where ever you use URLSession.
/Got it to work/ In some cases try to copy your code somewhere else then remove everything in your class that uses URLSession then type the session methods again and put back your copied code you should be fine.
Update your URLSessin functions with;
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.data.append(data as Data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
I can explain how but by playing around with the code I got this to work in SWIFT 3 after two days of frustration. I guess SWIFT 3 removed a lot of unnecessary words.
let task = Foundation.URLSession.shared.dataTask(with: <#T##URL#>, completionHandler: <#T##(Data?, URLResponse?, Error?) -> Void#>)
Here's where I am right now. It's not perfect but works maybe half of the time.
First, in the class where my URLsession is defined:
import Foundation
class Central: NSObject, URLSessionDataDelegate, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {
I don't think all of that is necessary, but there it is. Then here is the function that is called by my background fetch:
func getWebData() {
var defaults: UserDefaults = UserDefaults.standard
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myBGconfig")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
urlString = "https://www.powersmartpricing.org/psp/servlet?type=dayslider"
if let url = URL(string: urlString) {
let rateTask = backgroundSession.downloadTask(with: URL(string: urlString)!)
rateTask.taskDescription = "rate"
rateTask.resume()
}
When the task comes back:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) {
if downloadTask.taskDescription == "rate" { // I run 2 web tasks during the session
if let data = NSData(contentsOf: location) {
var return1 = String(data: data as! Data, encoding: String.Encoding.utf8)!
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 0.2){
var defaults: UserDefaults = UserDefaults.standard
defaults.set(myNumber, forKey: "electricRate") // myNumber is an extract of the text in returned web data
defaults.set(Date(), forKey: "rateUpdate")
defaults.synchronize()
self.calcSetting() //Calls another function defined in the same class. That function sends the user a notification.
let notificationName = Notification.Name("GotWebData")
NotificationCenter.default.post(name: notificationName, object: nil)
} // Closes the Dispatch
}
if session.configuration.identifier == "myBGconfig" {
print("about to invalidate the session")
session.invalidateAndCancel()
}
}
I haven't figured out yet how to kill the session when BOTH tasks have completed, so right now I kill it when either one is complete, with invalidateAndCancel as above.
And finally, to catch errors:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didCompleteWithError: Error?) {
if downloadTask.taskDescription == "rate" {
print("rate download failed with error \(didCompleteWithError)")
}
if downloadTask.taskDescription == "other" {
print("other download failed with error \(didCompleteWithError)")
}
downloadTask.resume() // I'm hoping this retries if a task fails?
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
if let error = error as? NSError {
print("invalidate, error %# / %d", error.domain, error.code)
} else {
print("invalidate, no error")
}
}