I played around a bit with TouchID and I have the following question:
After successful TouchID login, how do I present a new ViewController?
The code in the viewController.m is:
#import "ViewController.h"
#import LocalAuthentication;
#import "SVProgressHUD.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LAContext *context = [[LAContext alloc] init];
NSError *error;
// check if the policy can be evaluated
if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error])
{
NSLog(#"error:%#", error);
NSString *msg = [NSString stringWithFormat:#"Can't evaluate policy! %#", error.localizedDescription];
[SVProgressHUD showErrorWithStatus:msg];
return;
}
// evaluate
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Please login through TouchID"
reply:
^(BOOL success, NSError *authenticationError) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
[SVProgressHUD showSuccessWithStatus:#"Everything Worked!"];
//Code for new viewController should come here!
}
else {
NSLog(#"error:%#", authenticationError);
[SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:#"FAILED! %#", authenticationError.localizedDescription]];
}
});
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
viewController.h is standart. Nothing changed in it.
Thanks for support :)
To present a view controller we normally use following methods
If we are using storyboard,then call following method
[self performSegueWithIdentifier:#"indentifierForViewController" sender:self];
If we are not using storyboard then we can use
NextTaskViewControler *add = [[NextTaskViewControler alloc]
initWithNibName:#"NextTaskViewController" bundle:nil];
[self presentViewController:nextTaskVC animated:YES completion:nil];
I suggest you to use UINavigationController, a specialized view controller that manages other view controllers to provide a hierarchical navigation for the user. Present a viewcontroller only for specific purpose such as presenting a photo with few actions in it.It's easy to maintain when view hierarchy becomes complex
Please go-through UINavigationController Refrence
Related
I have a sample app that uses AudioKit to record audio and display a waveform of that audio data. This sample app has two viewControllers with the root vc being a blank page with a button that will take the user to the audio recording page.
For some reason, only on iPhone X (iOS 11.4.1), while recording audio, if I hit the back button on the navigation bar (top left) and then try to go and record again the app will crash.
Specifically the app appears to crash when the recorder's method appendDataFromBufferList: withBufferSize: calls ExtAudioFileWrite(self.info->extAudioFileRef, bufferSize, bufferList). The error message that is printed in the console is:
testAudioCrash(1312,0x16e203000) malloc: * **error for object 0x109803a00: incorrect checksum for freed object - object was probably modified after being freed.
* **set a breakpoint in malloc_error_break to debug
I've gone through zombie profiling, leak profiling, stepped through the logic and the stack but I can't seem to figure out why this is happening.
Below i've provided the code for the test app as well as screenshots of the stack and the console output. Any help with figuring out why this is crashing would be greatly appreciated. Unfortunately the fact that this crash is also not 100% reproducible makes it a little more obscure to me.
Notes for code below:
There is no custom code in the .h files so I have not provided that. There are xib files for each view controller with the UI components for this. They're pretty simple so I have not provided information on those as well though I have no problem in providing any information on them, that anyone requests. I can also zip up the project and share that if anyone feels it's necessary.
Repro steps:
1) launch app
2) tap on record Audio button
3) tap on record button
4) hit back button on navigation bar
5) repeat steps 2-4 until crash happens
AppDelegate.m code:
#import "AppDelegate.h"
#import "testViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
testViewController* rootVC = [[testViewController alloc] initWithNibName: #"testViewController" bundle: NSBundle.mainBundle];
UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController: rootVC];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// 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.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// 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.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// 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.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// 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.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
testViewController.m code:
#import "testViewController.h"
#import "testSecondViewController.h"
#interface testViewController ()
#end
#implementation testViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)AudioRecording:(id)sender
{
testSecondViewController* sVC = [[testSecondViewController alloc] initWithNibName: #"testSecondViewController" bundle: NSBundle.mainBundle];
[self.navigationController pushViewController: sVC animated: YES];
}
#end
testSecondViewController.m code:
#import "testSecondViewController.h"
#import AudioKit;
#import AudioKitUI;
#interface testSecondViewController () <EZMicrophoneDelegate, EZRecorderDelegate>
#property (nonatomic, strong) EZRecorder* recorder;
#property (nonatomic, strong) EZMicrophone* mic;
#property (nonatomic, strong) EZAudioPlayer* player;
#property (strong, nonatomic) IBOutlet EZAudioPlot *audioPlot;
#property (nonatomic, strong) NSURL *finishedRecordingURL;
#property (atomic, assign) BOOL isRecording;
#end
#implementation testSecondViewController
- (void)dealloc
{
if(_isRecording) [self pauseRecording: _mic];
if(_recorder) [self finalizeAudioFile: _recorder];
_recorder.delegate = nil;
_mic.delegate = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[EZAudioUtilities setShouldExitOnCheckResultFail: NO];
[self setupUI];
[self setupConfig];
[self audioKitSetup];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UI Methods
-(void)setupUI
{
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style: UIBarButtonItemStylePlain target: nil action:#selector(cancelButtonClicked)];
[self configureWaveFormViewForAudioInput];
}
-(void)setupConfig
{
[self initializeMic];
[self initializeRecorder];
}
-(void)initializeMic
{
self.mic = [[EZMicrophone alloc] initWithMicrophoneDelegate: self];
self.isRecording = NO;
}
-(void)initializeRecorder
{
NSURL *fileUrl = [self testFilePathURL];
self.finishedRecordingURL = fileUrl;
self.recorder = [[EZRecorder alloc] initWithURL: fileUrl clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
}
#pragma mark - Utils
- (NSArray *)applicationDocuments
{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
}
- (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
- (NSURL *)testFilePathURL
{
self.finishedRecordingURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#",
[self applicationDocumentsDirectory],
#"test2.m4a"]];
if (self.finishedRecordingURL && [[NSFileManager defaultManager] fileExistsAtPath:self.finishedRecordingURL.path])
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtURL:self.finishedRecordingURL error:&error];
if(error){
printf("%s", error.description);
}
}
return self.finishedRecordingURL;
}
#pragma mark AudioKit Util methods
- (void) audioKitSetup
{
[AKSettings setDefaultToSpeaker: YES];
[AKSettings setAudioInputEnabled: YES];
[AKSettings setPlaybackWhileMuted: YES];
[AKSettings setSampleRate: 44100];
[AKSettings setChannelCount: 1];
}
- (void) configureWaveFormViewForAudioInput
{
// self.audioPlot.gain = 6;
// self.audioPlot.color = [UIColor blueColor];
self.audioPlot.plotType = EZPlotTypeRolling;
// self.audioPlot.shouldFill = YES;
// self.audioPlot.shouldMirror = YES;
[self.view addSubview: self.audioPlot];
self.audioPlot.clipsToBounds = YES;
}
- (IBAction)startRecording:(id)sender
{
if (!self.mic)
{
self.mic = [EZMicrophone microphoneWithDelegate: self];
}
if (!self.recorder)
{
if (self.finishedRecordingURL && [[NSFileManager defaultManager] fileExistsAtPath:self.finishedRecordingURL.path])
{
self.recorder = [EZRecorder recorderWithURL: self.finishedRecordingURL clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
}
else
{
self.recorder = [EZRecorder recorderWithURL: [self testFilePathURL] clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
self.finishedRecordingURL = self.recorder.url;
}
}
[self.mic startFetchingAudio];
self.isRecording = YES;
}
- (IBAction)pauseRecording:(id)sender
{
[self.mic stopFetchingAudio];
self.isRecording = NO;
}
- (void) finalizeAudioFile: (EZRecorder*) recorder
{
if (self.isRecording)
{
[self.mic stopFetchingAudio];
}
[recorder closeAudioFile];
}
- (IBAction)cancelButtonClicked:(id)sender
{
if(self.isRecording)
{
[self pauseRecording: self.mic];
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle: #"Delete recording?" message:#"Would you like to delete your audio recording and stop recording?" preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"Discard"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self finalizeAudioFile: self.recorder];
NSError *error;
[[NSFileManager defaultManager] removeItemAtURL:self.finishedRecordingURL error:&error];
if(error){
printf("%s", error.description);
}
[self dismissViewControllerAnimated:YES completion:NULL];
}];
UIAlertAction* noButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[alert dismissViewControllerAnimated:YES completion: nil];
}];
[alert addAction:yesButton];
[alert addAction:noButton];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - EZMicrophone Delegate methods
- (void) microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
__weak typeof (self) weakling = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakling.audioPlot updateBuffer:buffer[0]
withBufferSize:bufferSize];
});
}
- (void) microphone:(EZMicrophone *)microphone
hasBufferList:(AudioBufferList *)bufferList
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
if (self.isRecording)
{
[self.recorder appendDataFromBufferList:bufferList
withBufferSize:bufferSize];
}
}
- (void)microphone:(EZMicrophone *)microphone changedPlayingState:(BOOL)isPlaying
{
self.isRecording = isPlaying;
}
#end
images:
Using my 6+ I've been trying to read the relative altitude and pressure using CoreMotion's new CMAltimeter. However the callback is never firing. I have a very similar setup which instead uses the accelerometers, gyros, and magnetometers. They all seem to work fine.
Was wondering if anyone out there has managed to get a reading?
- (void)viewDidLoad {
[super viewDidLoad];
if([CMAltimeter isRelativeAltitudeAvailable]){
CMAltimeter *altimeterManager = [[CMAltimeter alloc]init];
[altimeterManager startRelativeAltitudeUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
// This never fires.
NSString *data = [NSString stringWithFormat:#"Altitude: %f %f", altitudeData.relativeAltitude.floatValue, altitudeData.pressure.floatValue];
NSLog(#"%#", data);
self.altimeterLabel.text = data;
}];
NSLog(#"Started altimeter");
self.altimeterLabel.text = #"-\n-";
} else {
NSLog(#"Altimeter not available");
}
}
I've tried taking this on a quick walk, but there's only one story of altitude to lose/gain around here.
I'm pretty embarrased to answer my own question with such a huge oversight.
In the original post I had the CMAltimiter declared in the scope of viewDidLoad, thus it goes out of scope and is deallocated. I moved it to be an iVar and the callback now fires.
#import "ViewController.h"
#import CoreMotion;
#interface ViewController ()
#property (nonatomic, strong) CMAltimeter *altimeterManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if([CMAltimeter isRelativeAltitudeAvailable]){
self.altimeterManager = [[CMAltimeter alloc]init];
[self.altimeterManager startRelativeAltitudeUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
// This now fires properly
NSString *data = [NSString stringWithFormat:#"Altitude: %f %f", altitudeData.relativeAltitude.floatValue, altitudeData.pressure.floatValue];
NSLog(#"%#", data);
self.altimeterLabel.text = data;
}];
NSLog(#"Started altimeter");
self.altimeterLabel.text = #"-\n-";
} else {
NSLog(#"Altimeter not available");
}
}
You need to call [altimeterManager stopRelativeAltitudeUpdates]; for the references to be released to the dispatch queue.
I would like my save button to run the JSON call and then finish it's function, but it finishes the function while running the JSON call, so the variables become initialized as nil.
My DeviceDetailViewController.m
//
// DeviceDetailViewController.m
// Steam Backpack Viewer
//
// Created by Vishwa Iyer on 5/22/14.
// Copyright (c) 2014 MoAppsCo. All rights reserved.
//
#import "DeviceDetailViewController.h"
#import "MasterViewController.h"
#import "ProfileManager.h"
#import "ProfileCommunicator.h"
#import "SteamProfile.h"
#import "DeviceViewController.h"
#interface DeviceDetailViewController () <ProfileManagerDelegate> {
ProfileManager *_manager;
NSArray *profile;
SteamProfile *s;
}
extern NSString *ID;
#end
#implementation DeviceDetailViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (IBAction)cancel:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new managed object
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"BackpackViewer" inManagedObjectContext:context];
[newDevice setValue:self.steamIDTextField.text forKey:#"steamID"];
ID = [NSString stringWithFormat:#"%#", [newDevice valueForKey:#"steamID"]];
[self startFetchingGroups]; // I would like this JSON call to finish before calling the rest of the function below
[newDevice setValue:s.personaname forKey:#"steamName"];
[newDevice setValue:s.avatar forKey:#"imageURL"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_manager = [[ProfileManager alloc] init];
_manager.communicator = [[ProfileCommunicator alloc] init];
_manager.communicator.delegate = _manager;
_manager.delegate = self;
// Do any additional setup after loading the view.
}
- (void)startFetchingGroups
{
[_manager fetchGroups];
}
- (void)didReceieveProfileInfo:(NSArray *)groups
{
//the JSON call finishes here, when the groups are receives from the call. I would then like the rest of the save button method above to run after this runs, so that the s variable (which corresponds to a SteamProfile object) becomes initialized correctly.
profile = groups;
s = [profile objectAtIndex:0];
NSLog(s.personaname);
}
- (void)fetchingGroupsFailedWithError:(NSError *)error
{
NSLog(#"Error %#; %#", error, [error localizedDescription]);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I think you looking for something like this. Note this syntax could be wrong it is untested. I will leave it to you to read the documentation on function call backs.
#interface MyClass: NSObject
{
void (^_completionHandler)(int someParameter);
}
- (void)startFetchingGroups:(void(^)(int))handler;
#end
#implementation MyClass
- (void)startFetchingGroups:(void(^)(void))handler
{
[_manager fetchGroups];
if (handler) {
handler();
}
}
#end
[var startFetchingGroups:^{
[newDevice setValue:s.personaname forKey:#"steamName"];
[newDevice setValue:s.avatar forKey:#"imageURL"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}];
The behavior of when a callback gets called depends on what the _manager fetchGroups actually does. You could also use delegation as some of the people in the comments suggested, and is definatly a clean solution as well.
Sorry for ugly formatting. This code does exactly what you want..
#import "DeviceDetailViewController.h"
#import "MasterViewController.h"
#import "ProfileManager.h"
#import "ProfileCommunicator.h"
#import "SteamProfile.h"
#import "DeviceViewController.h"
typedef void(^EmptyBlock_t)();
#interface DeviceDetailViewController () <ProfileManagerDelegate> {
ProfileManager *_manager;
NSArray *profile;
SteamProfile *s;
// 1
// here you define a block, an anonymous function pointer that will be called right after you callback is called..
EmptyBlock_t _blockAfterJSONFetched;
}
extern NSString *ID;
#end
#implementation DeviceDetailViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (IBAction)cancel:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new managed object
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"BackpackViewer" inManagedObjectContext:context];
[newDevice setValue:self.steamIDTextField.text forKey:#"steamID"];
ID = [NSString stringWithFormat:#"%#", [newDevice valueForKey:#"steamID"]];
[self startFetchingGroups]; // I would like this JSON call to finish before calling the rest of the function below
// 2
// here you assign a value to your block. Notice that all objects inside block are called "retain" automatically. Also they a called "release" when you release the block itself..
_blockAfterJSONFetched=^{
[newDevice setValue:s.personaname forKey:#"steamName"];
[newDevice setValue:s.avatar forKey:#"imageURL"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
};
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_manager = [[ProfileManager alloc] init];
_manager.communicator = [[ProfileCommunicator alloc] init];
_manager.communicator.delegate = _manager;
_manager.delegate = self;
// Do any additional setup after loading the view.
}
- (void)startFetchingGroups
{
[_manager fetchGroups];
}
- (void)didReceieveProfileInfo:(NSArray *)groups
{
//the JSON call finishes here, when the groups are receives from the call. I would then like the rest of the save button method above to run after this runs, so that the s variable (which corresponds to a SteamProfile object) becomes initialized correctly.
profile = groups;
s = [profile objectAtIndex:0];
NSLog(s.personaname);
// 3
// finally after your callback is fired you check if block pointer is not null and if it is you call it as a casual function. Assigning nil in the end is optional..
if(_blockAfterJSONFetched){
_blockAfterJSONFetched();
_blockAfterJSONFetched=nil;
}
}
- (void)fetchingGroupsFailedWithError:(NSError *)error
{
NSLog(#"Error %#; %#", error, [error localizedDescription]);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm sorry for the weird title, I'm a beginner and didn't really know how to ask this so i'll explain myself:
I'm learning Core Data and currently learning from an Apple sample code. The sample code is for listing books using a table view.
In their AddViewController.h their declare an NSManagedObjectContext like this:
#import "DetailViewController.h"
#protocol AddViewControllerDelegate;
#interface AddViewController : DetailViewController
#property (nonatomic, weak) id <AddViewControllerDelegate> delegate;
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#end
#protocol AddViewControllerDelegate
- (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save;
#end
AddViewController.m
#import "AddViewController.h"
#implementation AddViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Set up the undo manager and set editing state to YES.
[self setUpUndoManager];
self.editing = YES;
}
- (IBAction)cancel:(id)sender
{
[self.delegate addViewController:self didFinishWithSave:NO];
}
- (IBAction)save:(id)sender
{
[self.delegate addViewController:self didFinishWithSave:YES];
}
- (void)dealloc
{
[self cleanUpUndoManager];
}
#end
Now, in their delegate method that is in RootViewController which is the main table view screen they perform this code:
- (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save {
if (save) {
NSError *error;
NSManagedObjectContext *addingManagedObjectContext = [controller managedObjectContext];
if (![addingManagedObjectContext save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() 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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
if (![[self.fetchedResultsController managedObjectContext] save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() 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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
// Dismiss the modal view to return to the main list
[self dismissViewControllerAnimated:YES completion:nil];
}
And I don't get why do they save it to the managedObjectContext of the AddViewController class..?
I thought the hole idea behind setting the root view controller the delegate is so we can preform the saving in there and passing the context object and then save it...
Please help me get it :/
In Appdelegate.m
- (NSManagedObjectContext *)managedObjectContextInternal
{
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
assert([NSThread isMainThread]); // must be creating context on main thread (though it might get read on a background thread from the library importers, but only to create the child contexts)
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
__managedObjectContext.undoManager = nil;
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
[self debugClearArtwork];
}
return __managedObjectContext;
}
When you want to save data after creating or update
[APP_DEGATE saveContext]
Everywhere in your project just to use 1 NSManagedObjectContext and should call in mainThread. This is the best practice if you don't want to create NSManagedObjectContext in background thread.
I'm trying to make an iPhone app using Core Data. I have to use NSManagedObjectContext to access data and in order to do this i use UIManagedDocument. But if I try to create a document with UIManagedDocument, document's openWithCompletionHandler isn't success. This why my NSManagedObjectContext is always nil and Xcode says could not create document at etc. Here are the classes:
AddUserViewController.h
#import <UIKit/UIKit.h>
#interface AddUserViewController : UIViewController
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property (nonatomic,strong) UIManagedDocument *document;
#end
AddUserViewController.m
#import "AddUserViewController.h"
#import "User.h"
#import "User+Create.h"
#interface AddUserViewController ()
#property (weak, nonatomic) IBOutlet UITextField *nameField;
#property (weak, nonatomic) IBOutlet UITextField *ageField;
#property (weak, nonatomic) IBOutlet UITextField *sexField;
#property (weak, nonatomic) IBOutlet UITextField *weightField;
#property (weak, nonatomic) IBOutlet UITextField *activityField;
#end
#implementation AddUserViewController
-(void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext{
_managedObjectContext = managedObjectContext;
}
-(void)createOrWriteDocument{
NSURL *url = [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentationDirectory inDomains:NSUserDomainMask]firstObject];
url = [url URLByAppendingPathComponent:#"Activities"]; // edited mistakenly deleted
self.document = [[UIManagedDocument alloc] initWithFileURL:url];
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[self.document openWithCompletionHandler:^(BOOL success) {
if (success) [self documentIsReady];
if (!success){
NSLog(#"could not open document at %#",url);
}
}];
} else {
[self.document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
[self documentIsReady];
}
if (!success){
NSLog(#"could not create document at %#",url);
}
}];
}
}
- (void)documentIsReady
{
if (self.document.documentState == UIDocumentStateNormal) {
self.managedObjectContext = self.document.managedObjectContext;
} }
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (!self.managedObjectContext) {
[self createOrWriteDocument];
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
}
-(void)dismissKeyboard{
[self.nameField resignFirstResponder];
[self.ageField resignFirstResponder];
[self.sexField resignFirstResponder];
[self.weightField resignFirstResponder];
[self.activityField resignFirstResponder];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"setUser:"]) {
//User *user = [[User alloc]init];
User *user = [User createUserWithname:self.nameField.text
withAge:[NSNumber numberWithDouble:[self.ageField.text doubleValue]]
withSex:self.sexField.text
withWeight:[NSNumber numberWithDouble:[self.weightField.text doubleValue]]
withActivity:self.activityField.text
inManagedObjectContext:self.managedObjectContext];
if ([segue.destinationViewController respondsToSelector:#selector(setUser:)]) {
[segue.destinationViewController performSelector:#selector(setUser:) withObject:user];
}
}
}
#end
EDIT
I solved the problem.Problem is at the NSURL *url = [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentationDirectory inDomains:NSUserDomainMask]firstObject];
line. Instead of NSDocumentationDirectory, I used NSDocumentDirectory and it solved the problem.
You can try this code to create you managedobjectcontext.
- (NSManagedObjectContext *) managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return _managedObjectContext;
}
Take a look at the example below. This works for me, sorry don't have time to take a closer look to see the differences, I am using iCloud options so remove them but try setting the other persistentStoreOptions. And also post details of the error are you getting?
// This gets called when the user has done one of the following:
// 1. Created a new file and entered a new file name. We have then created the fileURL
// using the /Documents directory, the filename and appending '_UUID_'+uuid to ensure that
// avoid duplicate file names in case the user used the same file name on another device.
// 2. Selected an existing file from the file browser
//
- (void)createNewFile:(NSURL*)fileURL {
//FLOG(#"createNewFile called with url %#", fileURL);
_creatingNewFile = YES; // Ignore any file metadata scan events coming in while we do this because some iCloud
// files get created by Core Data before the local files are created and our scanning
// picks up new iCloud files and attempts to create local copies and we don't want this
// if this devices is busy creating the new iCloud file
_document = [[OSManagedDocument alloc] initWithFileURL:fileURL];
// Set oberving on this file to monitor the state (we don't use it for anything other than debugging)
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(documentStateChanged:)
name:UIDocumentStateChangedNotification
object:_document];
_openedVersion = [NSFileVersion currentVersionOfItemAtURL:fileURL];
_openedVersionDate = _openedVersion.modificationDate;
_openedVersionDevice = _openedVersion.localizedNameOfSavingComputer;
//FLOG(#" file version date: %#", _openedVersionDate);
//FLOG(#" file version device: %#", _openedVersionDevice);
NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
[_document setPersistentStoreOptions:#{NSPersistentStoreUbiquitousContentNameKey:fileName,
NSMigratePersistentStoresAutomaticallyOption:#YES,
NSInferMappingModelAutomaticallyOption:#YES,
NSSQLitePragmasOption:#{ #"journal_mode" : #"DELETE" }}];
_managedObjectContext = _document.managedObjectContext;
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
//FLOG(#" file exists so open it: %#",fileURL);
[_document openWithCompletionHandler:^(BOOL success){
if (!success) {
// Handle the error.
LOG(#" error creating file");
} else {
//LOG(#" file opened");
[self fileOpened]; // Now initialise the UI and let the user continue...
}
}];
}
else {
// File does not exist so that means the user has created a new one and we need to
// load some initialisation data into the Core Data store (codes tables, etc.)
//
// At this stage we have a database in memory so we can just use the _document.managedObjectContext
// to add objects prior to attempting to write to disk.
//LOG(#" file DOES NOT exist so add initial data");
[self addInitialData];
// Just checking if anything has been written to disk, nothing should not exist on disk yet.
// Debugging use only
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
LOG(#" file exists but keep going anyway :-(");
else
LOG(#" file still does not exist :-)");
// OK now save a copy to disk using UIManagedDocument
// NOTE: the iCloud files are written before the UIManagedDocument.fileURL, presumably because Core Data does this setup
// in response to the [moc save:]. Make sure we don't pick this up in our iCloud metaData scan and attempt to create
// it as if it were a new iCloud file created by some other device.
//
[_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if (!success) {
// Handle the error.
LOG(#" error saving file :-(");
}
// We close the file and wait for it to appear in the file browser and then
// let the user select it from the browser to open it and start using it.
// Skip this if you want to open it directly and [self fileopened] but
// bear in mind the fileListController will not be correctly set up when we return to it.
[_document closeWithCompletionHandler:^(BOOL success){
if (!success) {
// Handle the error.
LOG(#" error closing file after creation :-(");
FLOG(#" file URL is %#", [fileURL path]);
} else {
FLOG(#" file closed %#", fileName);
_creatingNewFile = NO; // OK we are done, so let metaData scanning go ahead as normal
// Tell our UITableView file list that we are done and trigger scanning of local and iCloud files
// The fileListController will the add the new file itself and the user will then pick the
// file from this list in order to open it.
// To open the file automatically use the callback in the fileListController
// to select and then open the file so it looks seamless to the user.
[self.fileListController fileHasBeenCreated:fileURL];
// Stop observing now
[center removeObserver:self
name:UIDocumentStateChangedNotification
object:_document];
}
}];
}];
}
}
Oh and also create a subclass of UIManagedDocument so you can get the errors, if you don't already have one. All you need is the following in the subclass.
#implementation OSManagedDocument
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
LOG(#"Auto-Saving Document");
return [super contentsForType:typeName error:outError];
}
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted
{
FLOG(#" error: %#", error.localizedDescription);
NSArray* errors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(errors != nil && errors.count > 0) {
for (NSError *error in errors) {
FLOG(#" Error: %#", error.userInfo);
}
} else {
FLOG(#" error.userInfo = %#", error.userInfo);
}
}
#end
This is the method to get the /Documents directory on the device. The file name is appended to this path.
- (NSURL*)documentsDirectoryURL
{
_dataDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES];
return [_dataDirectoryURL URLByAppendingPathComponent:#"Documents"];
}
Here is a link to more information http://ossh.com.au/design-and-technology/software-development/uimanageddocument-icloud-integration/