Setting up iBeacon actions and monitoring from Appdelegate - bluetooth

I am making an app that has multiple views. I want it to be able to monitor for beacons in the background of all the views. So, I am setting up the code in the App delegate. When the code is in the app delegate, it does nothing. If I move it to the first view controller to load, it will ask for Authorization to use location but doesn't perform actions when entering the beacon region. Here is the code in the app delegate. What am I doing wrong?
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationDidFinishLaunchingWithOptions(_ application: UIApplication) {
let beaconManager = CLLocationManager()
var locationManager: CLLocationManager!
var window: UIWindow?
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedAlways {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
if CLLocationManager.isRangingAvailable() {
startScanning()
}
}
}
}
func startScanning() {
let uuid = NSUUID(uuidString: "2F234454-CF6D-4AOF-ADF2-F4911BA9FFA6")
let beaconRegion1 = CLBeaconRegion(proximityUUID: uuid as! UUID, major: 0, minor: 1, identifier: "AuschwitzAlbum")
let beaconRegion2 = CLBeaconRegion(proximityUUID: uuid as! UUID, major: 0, minor: 2, identifier: "Children")
locationManager.startMonitoring(for: beaconRegion1)
locationManager.startMonitoring(for: beaconRegion2)
}
func beaconManager(manager: Any, didEnterRegion: CLBeaconRegion) {
switch CLBeaconRegion() {
case beaconRegion1:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "exhibitions")
self.window?.rootViewController?.present(controller, animated: true, completion: nil)
case beaconRegion2: break
default: break
}
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}

To more easily get started, you could try to move the call to startScanning() outside the nested conditions or use the debugger to see if the enter and exit region part of your code actually gets called.
Here is some simplified sample code to get you started:
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var locationManager: CLLocationManager!
let beaconRegion1 = CLBeaconRegion(proximityUUID: UUID(uuidString: "2F234454-CF6D-4AOF-ADF2-F4911BA9FFA6")!, identifier: "AuschwitzAlbum")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
locationManager = CLLocationManager()
locationManager.delegate = self
requestLocationAuthorization()
return true
}
// Standard AppDelegate implementations here
}
extension AppDelegate : CLLocationManagerDelegate {
func requestLocationAuthorization() {
if CLLocationManager.authorizationStatus() != .authorizedAlways {
locationManager.requestAlwaysAuthorization()
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways {
startMonitoringRegion()
}
}
func startMonitoringRegion() {
locationManager.startMonitoring(for: beaconRegion1)
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("Did enter region \(region.identifier)")
if region.identifier == beaconRegion1.identifier{
showViewController()
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("Did exit region \(region.identifier)")
}
func showViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "exhibitions")
self.window?.rootViewController?.present(controller, animated: true, completion: nil)
}
}

Related

Using AVAudioPlayer in background

In my app, I've added the capabilities of background audio and background processing.
My code presently uses AVAudioPlayer to play audio. While playback is good when the app in the foreground, with a locked screen, the audio is has some static jitteriness to it.
My app is written using SwiftUI and Combine. Has anyone encountered this issue and what would you suggest as a workaround?
Here is the play method:
/// Play an `AudioFile`
/// - Parameters:
/// - audioFile: an `AudioFile` struct
/// - completion: optional completion, default is `nil`
func play(_ audioFile: AudioFile,
completion: (() -> Void)? = nil) {
if audioFile != currentAudioFile {
resetPublishedValues()
}
currentAudioFile = audioFile
setupCurrentAudioFilePublisher()
guard let path = Bundle.main.path(forResource: audioFile.filename, ofType: "mp3") else {
return
}
let url = URL(fileURLWithPath: path)
// everybody STFU
stop()
do {
// make sure the sound is one
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
// instantiate instance of AVAudioPlayer
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer.prepareToPlay()
// play the sound
let queue = DispatchQueue(label: "audioPlayer", qos: .userInitiated, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
queue.async {
self.audioPlayer.play()
}
audioPlayer.delegate = self
} catch {
// Not much to go wrong, so leaving alone for now, but need to make `throws` if we handle errors
print(String(format: "play() error: %#", error.localizedDescription))
}
}
This is the class definition:
import AVFoundation
import Combine
import Foundation
/// A `Combine`-friendly wrapper for `AVAudioPlayer` which utilizes `Combine` `Publishers` instead of `AVAudioPlayerDelegate`
class CombineAudioPlayer: NSObject, AVAudioPlayerDelegate, ObservableObject {
static let sharedInstance = CombineAudioPlayer()
private var audioPlayer = AVAudioPlayer()
/*
FIXME: For now, gonna leave this timer on all the time, but need to refine
down the road because it's going to generate a fuckload of data on the
current interval.
*/
// MARK: - Publishers
private var timer = Timer.publish(every: 0.1,
on: RunLoop.main,
in: RunLoop.Mode.default).autoconnect()
#Published public var currentAudioFile: AudioFile?
public var isPlaying = CurrentValueSubject<Bool, Never>(false)
public var currentTime = PassthroughSubject<TimeInterval, Never>()
public var didFinishPlayingCurrentAudioFile = PassthroughSubject<AudioFile, Never>()
private var cancellables: Set<AnyCancellable> = []
// MARK: - Initializer
private override init() {
super.init()
// set it up with a blank audio file
setupPublishers()
audioPlayer.setVolume(1.0, fadeDuration: 0)
}
// MARK: - Publisher Methods
private func setupPublishers() {
timer.sink(receiveCompletion: { completion in
// TODO: figure out if I need anything here
// Don't think so, as this will always be initialized
},
receiveValue: { value in
self.isPlaying.send(self.audioPlayer.isPlaying)
self.currentTime.send(self.currentTimeValue)
})
.store(in: &cancellables)
didFinishPlayingCurrentAudioFile.sink(receiveCompletion: { _ in
},
receiveValue: { audioFile in
self.resetPublishedValues()
})
.store(in: &cancellables)
}
private func setupCurrentAudioFilePublisher() {
self.isPlaying.send(false)
self.currentTime.send(0.0)
}
// MARK: - Playback Methods
/// Play an `AudioFile`
/// - Parameters:
/// - audioFile: an `AudioFile` struct
/// - completion: optional completion, default is `nil`
func play(_ audioFile: AudioFile,
completion: (() -> Void)? = nil) {
if audioFile != currentAudioFile {
resetPublishedValues()
}
currentAudioFile = audioFile
setupCurrentAudioFilePublisher()
guard let path = Bundle.main.path(forResource: audioFile.filename, ofType: "mp3") else {
return
}
let url = URL(fileURLWithPath: path)
// everybody STFU
stop()
do {
// make sure the sound is one
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
// instantiate instance of AVAudioPlayer
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer.prepareToPlay()
// play the sound
let queue = DispatchQueue(label: "audioPlayer", qos: .userInitiated, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
queue.async {
self.audioPlayer.play()
}
audioPlayer.delegate = self
} catch {
// Need to make `throws` if we handle errors
print(String(format: "play error: %#", error.localizedDescription))
}
}
func stop() {
audioPlayer.stop()
resetPublishedValues()
}
private func resetPublishedValues() {
isPlaying.send(false)
currentTime.send(0.0)
}
private var currentTimeValue: TimeInterval {
audioPlayer.currentTime
}
/// Use the `Publisher` to determine when a sound is done playing.
/// - Parameters:
/// - player: an `AVAudioPlayer` instance
/// - flag: a `Bool` indicating whether the sound was successfully played
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if let currentAudioFile = currentAudioFile {
didFinishPlayingCurrentAudioFile.send(currentAudioFile)
}
resetPublishedValues()
}
}
So I got it figured out. I had a few issues to contend with. Basically, I needed to play audio files at a specific time when the app was in the background. While this works fine if the sound is playing when the app is active, AVAudioPlayer won't let me start something after the app is in the background if audio playback is not already in progress.
I won't go into the nitty gritty details, but I ended up making use of AVQueuePlayer, which I initialized as part of my CombineAudioPlayer class.
Update AppDelegate.swift
I added the following lines to AppDelegate's didFinishLaunchingWithOptions method.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
do {
try AVAudioSession.sharedInstance().setCategory(.playback,
mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(String(format: "didFinishLaunchingWithOptions error: %#", error.localizedDescription))
}
return true
}
In my AudioPlayer class, I declared an AVQueuePlayer. It is critical this be initialized with the AudioPlayer class, not inside of a method.
My ViewModel subscribes to a notification that listens for the app about to exit the foreground, it quickly generates a playlist and fires it just before the app exits.
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).sink { _ in
self.playBackground()
}
.store(in: &cancellables)
private var bgAudioPlayer = AVQueuePlayer()
Then, I created a method to generate a playlist for the AVQueuePlayer that looks something like this:
func backgroundPlaylist(from audioFiles: [AudioFile]) -> [AVPlayerItem] {
guard let firstFile = audioFiles.first else {
// return empty array, don't wanna unwrap optionals
return []
}
// declare a silence file
let silence = AudioFile(displayName: "Silence",
filename: "1sec-silence")
// start at zero
var currentSeconds: TimeInterval = 0
var playlist: [AVPlayerItem] = []
// while currentSeconds is less than firstFile's fire time...
while currentSeconds < firstFile.secondsInFuture {
// add 1 second of silence to the playlist
playlist.append(AVPlayerItem(url: silence.url!))
// increment currentSeconds and we loop over again, adding more silence
currentSeconds += 1
}
// once we're done, add the file we want to play
playlist.append(AVPlayerItem(url: audioFiles.first!.url!))
return playlist
}
Lastly, the sound is played as follows:
func playInBackground() {
do {
// make sure the sound is one
try AVAudioSession.sharedInstance().setCategory(.playback,
mode: .default,
policy: .longFormAudio,
options: [])
try AVAudioSession.sharedInstance().setActive(true)
let playlist = backgroundPlaylist(from: backgroundPlaylist)
bgAudioPlayer = AVQueuePlayer(items: playlist)
bgAudioPlayer.play()
} catch {
// Not much to mess up, so leaving alone for now, but need to make
// `throws` if we handle errors
print(String(format: "playInBackground error: %#",
error.localizedDescription))
}
}

Setting Up CoreData with SceneDelegate - unknown identifier 'window' error - iOS 13 onwards

I was trying to use the official apple documentation for Core Data. Found here. I also ran into a question which was related to my issue, right here on stack.
I ran into an issue where, it kept saying that 'window' is not available in the context of AppDelegate. This is the very basic step as per the official documentation.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let rootVC = window?.rootViewController as? ViewController {
rootVC.container = persistentContainer
}
return true
}
How do I get past this?
The issue boils down to the changes, primarily, support for multiple scenes in iOS 13 and above. check this reddit link for the discussion.
The solution is to move some of things from AppDelegate to SceneDelegate.
Here is the final form of the essential parts of the above two classes.
----SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let rootVC = storyboard.instantiateViewController(identifier: "ViewController") as? ViewController else {
print("ViewController not found")
return
}
//set the storage here
rootVC.container = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
//I dont want a UI navigation controller.
//let rootNC = UINavigationController(rootViewController: rootVC)
//self.window?.rootViewController = rootNC
//I want to use my basic view controller here. use rootNC to get a UI navigation controller
self.window?.rootViewController = rootVC
self.window?.makeKeyAndVisible()
}
--AppDelegate (remains unchanged )
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//before iOS 13 you would be putting stuff here but not anymore.
return true
}
Finally, you will leave the storage related code, where it was, in AppDelegate itself.

How do I save data using NSKeyedArchiver?

I am very close to finishing my first iOS App using Swift 4 and iOS 11.
The app has a list displayed in a table view controller and a detail view with a UITextView object that is editable. My goal is for the user to be able to make edits to the content in the UITextView and save those changes using NSKeyedArchiver.
I have the list view complete and the detail view connected. You can make edits but they do not save.
I have confirmed that the entry does save to memory that persists beyond the session, but the edits do not save.
Reviews of documentation and working through multiple tutorials have not provided the insights needed. I have attached a screen shot to show the interface of the detail view and here is the code from the detail view controller where the save button triggers the Save action:
import UIKit
import os.log
class ViewController: UIViewController, UINavigationControllerDelegate, UITextViewDelegate {
var season: Season?
//MARK: Properties
#IBOutlet weak var seasonDetail: UITextView!
#IBAction func saveButton(_ sender: UIBarButtonItem) {
if let selectedDetail = seasonDetail.text {
seasonDetail.text = selectedDetail
} else {
print("failed to save changes.")
}
saveChanges()
print("Save button clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
title = season?.name
seasonDetail.text = season?.detail
seasonDetail.delegate=self
}
override func viewWillDisappear(_ animated: Bool) {
season?.detail = (seasonDetail?.text)!
}
func textViewDidEndEditing(_ textView: UITextView) {
seasonDetail.text = season?.detail
}
//MARK: UITextViewdDelegate
func textViewShouldReturn(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
func saveChanges() {
print("Saving items to: \(Season.ArchiveURL)")
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(season as Any, toFile: Season.ArchiveURL.path)
if isSuccessfulSave {
os_log("Season sucessfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save season.", log: OSLog.default, type: .debug)
}
}
}
Here is the code from the data model class:
import UIKit
import os.log
class Season: NSObject, NSCoding {
//MARK: Properties
var name: String
var detail: String
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("season")
//MARK: Types
struct PropertyKey {
static let name = "name"
static let detail = "detail"
}
//MARK: Initialization
init?(name: String, detail: String) {
guard !name.isEmpty else {
return nil
}
guard !detail.isEmpty else {
return nil
}
// Initialize stored properties
self.name = name
self.detail = detail
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(detail, forKey: PropertyKey.detail)
}
required convenience init?(coder aDecoder: NSCoder) {
// the name is required. If we cannnot get a name string, the initializer should fail.
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String
else {
os_log("Unable to decode the name for a Season object.", log: OSLog.default, type: .debug)
return nil
}
let detail = aDecoder.decodeObject(forKey: PropertyKey.detail)
self.init(name: name, detail: detail as! String)
}
}
My goal is to understand what is missing with my code and know how to persist all the data, including the edits. I would appreciate any direction that would help.
Please check :
class ViewController: UIViewController, UINavigationControllerDelegate, UITextViewDelegate {
var season: Season?
#IBOutlet weak var seasonDetail: UITextView!
#IBAction func saveButton(_ sender: UIBarButtonItem) {
if let selectedDetail = seasonDetail.text {
season?.detail = selectedDetail // this is the line
} else {
print("failed to save changes.")
}
saveChanges()
print("Save button clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
if season == nil {
season = Season(name: "Season Name", detail: "Season Details")
}
title = season?.name
seasonDetail.text = season?.detail
seasonDetail.delegate=self
}
override func viewWillDisappear(_ animated: Bool) {
season?.detail = (seasonDetail?.text)!
}
func textViewDidEndEditing(_ textView: UITextView) {
season?.detail = seasonDetail.text
}
//MARK: UITextViewdDelegate
func textViewShouldReturn(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
func saveChanges() {
print("Saving items to: \(Season.ArchiveURL)")
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(season as Any, toFile: Season.ArchiveURL.path)
if isSuccessfulSave {
os_log("Season sucessfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save season.", log: OSLog.default, type: .debug)
}
}
}

iOS10 & Switt3 - Core Data not saving nor return results

I am building an app with Core Data. it has always worked for me so far. Recently,
I get not result. no error. it seems that no data is persisted. has anyone ever encountered this weird malfunction?
My viewcontroller: to display contacts list
import UIKit
import CoreData
class ContactsTableViewController: UITableViewController {
#IBAction func addContactAction(_ sender: AnyObject) {
alertDialog()
}
let identifier = "contactCell"
var contacts:[String] = [String]()
var managedContext:NSManagedObjectContext?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
addContacts(numContacts: 30);
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fetchContacts("") { (list) in
for i in 0..<list.count {
contacts.append(list[i].value(forKey:"name")! as! String)
}
tableView.reloadData()
}
}
func alertDialog() {
//It takes the title and the alert message and prefferred style
let alertController = UIAlertController(title: "Add Contact", message: "", preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.placeholder = "contact"
}
let defaultAction = UIAlertAction(title: "Add", style: .default) { (UIAlertAction) in
let textField = alertController.textFields![0]
self.addContact(name: textField.text!)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
//now we are adding the default action to our alertcontroller
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
//and finally presenting our alert using this method
present(alertController, animated: true, completion: nil)
}
}
extension ContactsTableViewController {
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
// Configure the cell...
cell.textLabel?.text = contacts[indexPath.row]
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let contactToDelete = contacts[indexPath.row]
deleteContact(contactToDelete)
contacts.remove(at: (indexPath as NSIndexPath).row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
}
extension ContactsTableViewController {
func addContacts(numContacts:Int) {
for i in 1..<numContacts {
let contact = NSEntityDescription.insertNewObject(forEntityName: "Contact", into: managedContext!) as! Contact
contact.setValue("name \(i)", forKeyPath: "name")
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
try managedContext?.save()
print("\(contact.value(forKeyPath: "name") as! String)) successfully saved")
} catch {
fatalError("Failure to save context: \(error)")
}
}
self.tableView.reloadData()
}
func addContact(name:String) {
let contact = NSEntityDescription.insertNewObject(forEntityName: "Contact", into: managedContext!) as! Contact
contact.setValue(name, forKeyPath: "name")
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
try managedContext?.save()
print("\(contact.value(forKeyPath: "name") as! String)!) successfully saved")
} catch {
fatalError("Failure to save context: \(error)")
}
self.tableView.reloadData()
}
func fetchContacts(_ predicate:String, completion:(_ array:[Contact]) -> ()) {
var arr:[Contact] = [Contact]()
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Contact")
request.predicate = NSPredicate(format: "name = %#", predicate)
do {
let results = try managedContext?.fetch(request) as! [Contact]
for result in results {
let name = (result as AnyObject).value(forKey: "name") as? String
arr.append(result)
} //for
print(results)
completion(arr as [Contact])
} catch {
print("error fetching results")
} //do
}
func deleteContact(_ name:String) {
fetchContacts(name) { (array) -> () in
for result in array {
let aContact = (result as AnyObject).value(forKey: "name") as? String
if aContact == name {
//delete
self.managedContext?.delete(result)
//save
do {
try self.managedContext!.save()
print("\(aContact) deleted")
} catch {
print("error deleting contact")
} //do
} // if
} //for
}
}
}
My AppDelegate.swift
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "ContactLists_coreData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = 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)")
}
}
}
}
Generated entity class
import Foundation
import CoreData
extension Contact {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Contact> {
return NSFetchRequest<Contact>(entityName: "Contact");
}
#NSManaged public var name: String?
}
the data model. very simple
<img src="https://drive.google.com/file/d/0B1Usy68B1DzYLUduNTlCY092VEk/view" width="1970" height="1084">
datasource and cell identifier are connected properly
fetchContacts("") always returns an empty list because you have no contacts with a name of "". Also whenever you add or insert to core-data you do not see those changes because you are not doing another fetch and updating the contacts array.
Other more general problems with your code:
You should treat the persistentContainer's viewContext as readonly. To write to core-data use perform​Background​Task(_:​)
after you create the persistentContainer set container.viewContext.automaticallyMergesChangesFromParent = true
use a fetchedResultsController to sync core-data with your view.
If you are doing lots of changes or inserts to core data like you are doing in addContacts do a single save at the end, not after every insert in the loop.

How to present a view controller while performing background tasks?

I am using Parse.com and swift
I have an initial view controller that presents the parse.com login.
Once the login is complete and the objects are saved in the background I want to present my navigation controller's root view controller (first controller linked in the storyboard).
How is this done with all the asynchronous calls?
This is what I have but it jumps back to the login screen and doesn't
func signUpViewController(signUpController: PFSignUpViewController!, didSignUpUser user: PFUser!) {
currentUser = user as? User
currentUser!.isManager = false
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if objects.count == 1 {
currentUser!.isManager = true
}
currentUser?.saveInBackgroundWithBlock({ (success: Bool, error: NSError!) -> Void in
if success == false || error != nil {
println(error)
} else {
currentCompany = Company()
currentCompany!.companyName = "My Company"
currentCompany!.saveInBackgroundWithBlock({ (success: Bool!, error: NSError!) -> Void in
if success == false {
println(error)
}
})
}
})
}
dismissViewControllerAnimated(true, completion: { () -> Void in
self.performSegueWithIdentifier("jumpFromInitialToMessages", sender: self)
// let vc = MessagesViewController()
// self.navigationController?.presentViewController(vc, animated: true, completion: nil)
})
}
If you want to initiate a UI transition/update inside the completion handler, you should insert a dispatch back to the main queue inside the completion handler closure:
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
// do something
// when done, do some UI update, e.g. perform segue to another scene
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("identifier", sender: self)
}
}
This is an example with a simple query, but the idea works with any asynchronously called completion handler closure: Just dispatch the UI update back to the main queue from within the closure.

Resources