Realtime coredata autosave - objective-c

I have a little statusbar app. It saves when I close the window, it saves when I quit the app but it isn't going to save every time I edit a row in my tableview or I add something to my arraycontroller.
Is there a method to call saveAction at least every "enter" hit or when I confirm an edit?
A save button is not what I'm searching for.
Thanks in advance.

This is my approach:
1) Create a subclass of NSManagedObject to add functionality for autosaving. You can substitute the line do { try managedObjectContext?.save() } catch { print(error) } by something like saveContext() if you have a global function defined elsewhere. Note that autosave is disabled by default.
class AutoSaveManagedObject: NSManagedObject {
class var autosave: Bool { return false }
var autosave: Bool?
private var previousValue: AnyObject?
override func willChangeValueForKey(key: String) {
super.willChangeValueForKey(key)
if ( autosave == true ) || ( autosave == nil && self.dynamicType.autosave ) {
previousValue = valueForKey(key)
}
}
override func didChangeValueForKey(key: String) {
super.didChangeValueForKey(key)
if ( autosave == true ) || ( autosave == nil && self.dynamicType.autosave ) {
if "\(previousValue)" != "\(valueForKey(key))" {
do { try managedObjectContext?.save() } catch { print(error) }
}
previousValue = nil
}
}
}
2) Make all core data objects subclasses of AutoSaveManagedObject rather than of NSManagedObject. If you want to enable autosaving, you should write something like this:
class MyMO: AutoSaveManagedObject {
override class var autosave: Bool { return true }
// Your #NSManaged vars here
}
3) Now all the instances of MyMO have autosave enabled. If you want to disable it for a certain instance, you can always write:
let myMO = ... as? MyMO
myMO?.autosave = false
Note that the instance's var autosave always has higher priority than the class var autosave, so you can set myMO?.autosave = nil in order to use the default autosave setting of the class.

I would simply set your view controller as the delegate for both the textfield and textview. In an iOS environment you would add the protocol UITextFieldDelegate and UITextViewDelegate to your view controller header file and implement the methods - (void)textFieldDidEndEditing:(UITextField *)textField and - (void)textViewDidEndEditing:(UITextView *)textView for the UITextField and UITextView respectively.
As an alternative on a UITextField (iOS), there is a delegate method named - (BOOL)textFieldShouldReturn:(UITextField *)textField which is called whenever the 'enter' key is pressed on a UITextField.
In a Mac OSX environment you would add the appropriate protocol to your view controller header file (for NSTextView add the NSTextDelegate, for NSTextField add the NSControlTextEditingDelegate) and then implement the appropriate methods: -(void)textDidChange:(NSNotification *)notification for an NSTextView and - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor for an NSTextField.
You can do any kind of validation you need to in those methods and then do a call to [myMOC save:&error]; before you return.

When ever you edit a row within that method write this code
[[NSNotificationCenter defaultCenter] addObserver:self selector:(autosaveCoreData:) name:nil object:your_tableview_object];
-(void)autosaveCoreData:(Event*)event{
event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:_managedObjectContext];
[event setValue:Attribute_Value forKey:#"your atttribute"];
NSError *error;
if (![_managedObjectContext save:&error]) {
}
}
}
I hope this may solve your problem

Related

app broke after Xcode update

I have a Swift app, with some Objective-C code mixed in. It was working yesterday, but this morning I updated XCode and now everything had gone to hell!
Firstly, after updating, I clicked the XCode popup box to allow it to upgrade my app to Swift4. This is where the problems started.
I have a Swift class called RestClient with the following 4 functions (among others):
class func getInstance() -> RestClient {
if (gRestClient == nil) {
let prefs:UserDefaults = UserDefaults.standard
return RestClient(username: prefs.string(forKey: "username")!, password: prefs.string(forKey: "password")!)
}
return gRestClient!
}
class func getUsername() -> String {
if (gUsername == nil) {
let prefs:UserDefaults = UserDefaults.standard
gUsername = prefs.string(forKey: "username")!
}
return gUsername!
}
class func getPassword() -> String {
if (gPassword == nil) {
let prefs:UserDefaults = UserDefaults.standard
gPassword = prefs.string(forKey: "password")!
}
return gPassword!
}
public func getServer() -> String {
return MAIN_SERVER;
}
Then in my /Objective-C/ folder, I have some more files, once of which is called RestClientObj.m. In here, I have this lines of code:
NSString* url = [NSString stringWithFormat:#"%#/receipt/email/%#/%#/", [[RestClient getInstance] getServer], rrn, emailAddress];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", [RestClient getUsername], [RestClient getPassword]];
So as you can see, I'm calling the RestClient.swift from here. The RestClientObj.h is as follows:
#ifndef RestClientObj_h
#define RestClientObj_h
#endif /* ResClientObj_h */
#interface RestClientObj : NSObject {
}
+(BOOL) sendSMS:(NSString *)rrn mn:(NSString *)mobileNumber;
+(BOOL) sendEmail:(NSString *)rrn mn:(NSString *)emailAddress;
#end
This whole upgrade is causing other problems too. I have another class with the following error:
No visible #interface for 'AppDelegate' declares the selector 'managedObjectContext'
on the line:
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext]; <-ERROR
Can anyone shed any light on this?
EDIT: Here's some code from the AppDelegate class:
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
if coordinator == nil {
return nil
}
var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Just to close this off.
The issue was missing the #obj identifier before the variable declaration to make it visible to my objective-c code, in combination with the XCode Swift Upgrade wizard renaming some functions.

Changing cocoa bindings value programmatically

I have an NSButton in my preferences to interact with adding the application to the LoginItems. If the adding of the login item fails, I want to uncheck the box so the user doesn't get a false sense that it was added to the login items. However, after doing this, when I click the checkbox again, the bindings is not triggered.
- (void)addLoginItem:(BOOL)status
{
NSURL *url = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:
#"Contents/Library/LoginItems/HelperApp.app"];
// Registering helper app
if (LSRegisterURL((__bridge CFURLRef)url, true) != noErr) {
NSLog(#"LSRegisterURL failed!");
}
if (!SMLoginItemSetEnabled((__bridge CFStringRef)[[NSBundle mainBundle] bundleIdentifier], (status) ? true : false)) {
NSLog(#"SMLoginItemSetEnabled failed!");
[self willChangeValueForKey:#"startAtLogin"];
[self.startAtLogin setValue:[NSNumber numberWithBool:[self automaticStartup]] forKey:#"state"];
[self didChangeValueForKey:#"startAtLogin"];
}
}
- (void)setAutomaticStartup:(BOOL)state
{
NSLog(#"Set automatic startup: %d", state);
if ([self respondsToSelector:#selector(addLoginItem:)]) {
[self addLoginItem:state];
}
}
- (BOOL)automaticStartup
{
BOOL isEnabled = NO;
// the easy and sane method (SMJobCopyDictionary) can pose problems when sandboxed. -_-
CFArrayRef cfJobDicts = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
NSArray* jobDicts = CFBridgingRelease(cfJobDicts);
if (jobDicts && [jobDicts count] > 0) {
for (NSDictionary* job in jobDicts) {
if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:[job objectForKey:#"Label"]]) {
isEnabled = [[job objectForKey:#"OnDemand"] boolValue];
break;
}
}
}
NSLog(#"Is Enabled: %d", isEnabled);
// if (isEnabled != _enabled) {
[self willChangeValueForKey:#"startupEnabled"];
startupEnabled = isEnabled;
[self didChangeValueForKey:#"startupEnabled"];
// }
return isEnabled;
}
I have my databinding for the checkbox bound to self.automaticStartup. If I remove the line [self.startAtLogin setValue:[NSNumber numberWithBool:[self automaticStartup]] forKey:#"state"]; then the bindings work fine, but it doesn't uncheck, if the adding of the item fails.
How can I change this binding value programmatically so that every other binding event is not ignored?
From your explanation, your bound value is automaticStartup, but you are sending willChangeValueForKey: for startAtLogin. In order for bindings to work correctly, you need to alert on the change to the bound variable at some point. However, since you are in the midst of setAutomaticStartup: at the time, it's not really safe to do that here.
In this case, I would not use bindings to perform the change itself, I would consider the old-style IBAction mechanism and then set the checkbox value manually through an IBOutlet when you can confirm the status.

Detect Enter/Tab/Up/Down keys in NSTextView?

Cocoa noob here. I'm wondering how I can capture the Enter and tab keys onKeyDown whilst the user is typing in an NSTextView?
Thanks!
The easiest way is to implement the - (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector delegate method and look for the insertNewline: and insertTab: selectors.
- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
{
if (aSelector == #selector(insertNewline:)) {
// Handle the Enter key
return YES;
} else if (aSelector == #selector(insertTab:)) {
// Handle the Tab key
return YES;
}
return NO;
}
You should handle keyDown:(NSEvent*)theEvent message of NSTextView (i.e. write your own descendant).
In this event you will have key code in [theEvent keyCode].
For return there is a constant kVK_Return, for tab - kVK_Tab, etc.
You should add Carbon framework (and #import Carbon/Carbon.h) to access these constants.

NSTextField autocomplete

Does anyone know of any class or lib that can implement autocompletion to an NSTextField?
I'am trying to get the standard autocmpletion to work but it is made as a synchronous api. I get my autocompletion words via an api call over the internet.
What have i done so far is:
- (void)controlTextDidChange:(NSNotification *)obj
{
if([obj object] == self.searchField)
{
[self.spinner startAnimation:nil];
[self.wordcompletionStore completeString:self.searchField.stringValue];
if(self.doingAutocomplete)
return;
else
{
self.doingAutocomplete = YES;
[[[obj userInfo] objectForKey:#"NSFieldEditor"] complete:nil];
}
}
}
When my store is done, i have a delegate that gets called:
- (void) completionStore:(WordcompletionStore *)store didFinishWithWords:(NSArray *)arrayOfWords
{
[self.spinner stopAnimation:nil];
self.completions = arrayOfWords;
self.doingAutocomplete = NO;
}
The code that returns the completion list to the nstextfield is:
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
{
*index = -1;
return self.completions;
}
My problem is that this will always be 1 request behind and the completion list only shows on every 2nd char the user inputs.
I have tried searching google and SO like a mad man but i cant seem to find any solutions..
Any help is much appreciated.
Instead of having the boolean property doingAutocomplete, make the property your control that made the request. Let's call it autoCompleteRequestor:
#property (strong) NSControl* autoCompleteRequestor;
So where you set your current property doingAutocomplete to YES, instead store a reference to your control.
- (void)controlTextDidChange:(NSNotification *)obj
{
if([obj object] == self.searchField)
{
[self.spinner startAnimation:nil];
[self.wordcompletionStore completeString:self.searchField.stringValue];
if(self.autoCompleteRequestor)
return;
else
{
self.autoCompleteRequestor = [[obj userInfo] objectForKey:#"NSFieldEditor"];
}
}
}
Now when your web request is done, you can call complete: on your stored object.
- (void) completionStore:(WordcompletionStore *)store didFinishWithWords:(NSArray *)arrayOfWords
{
[self.spinner stopAnimation:nil];
self.completions = arrayOfWords;
if (self.autoCompleteRequestor)
{
[self.autoCompleteRequestor complete:nil];
self.autoCompleteRequestor = nil;
}
}
NSTextView has the functionality of completing words of partial words.
Take a look at the documentation for this component.
Maybe you can switch to this component in your application.

Uiswitch on/off

m new to objective-c, i have made a application of login page in which i have used UISwitch to remember d login details if switch is in on mode. i have done with to remember the login details but problem is that how to use the switch on/off condition. Thanx in advance
The easiest solution of all :)
if (switchValue.on){
//Remember Login Details
}
else{
//Code something else
}
You would add a conditional statement somewhere in your code depending on the switch's on property. Let's say, for example, that you remember login details in a method called rememberLoginDetails. What you would do is, when some action is triggered (the user leaves the login page, for example):
if([yourSwitch isOn]) {
[self rememberLoginDetails];
} else {
// Do nothing - switch is not on.
}
The important method here is the isOn method for the UISwitch yourSwitch. isOn is the getter for the switch's on property, which is a BOOL property containing YES if the switch is on, and NO if it is not.
For more information, you can see the UISwitch class reference, specifically the part about isOn.
if you want to remember the login details just the moment where the user TURN ON the switch, you can done it by create an Action.
- (IBAction)yourSwitch:(id)sender {
if([sender isOn]){
//do your stuff here
}else{
}
}
I believe the code needs to be this:
if([yourSwitch isOn] == YES) {
[self rememberLoginDetails];
} else {
// Do nothing - switch is not on.
}
This is another solution, if your UISwitch is in a tableView
1 add this code in "tableView:cellForRowAtIndexPath:" method
[switchControl addTarget:self action:#selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
2 add this method
- (void) switchChanged:(id)sender {
UISwitch *switchControl = sender;
NSLog( #"The switch is %#", switchControl.on ? #"ON" : #"OFF" );
}
if(switchValue.isOn){
[switchValue setOn:NO];
} else {
[switchValue setOn:YES];
}
i had the same problem, i had the name of the UISwitch = Selected
i changed it to another name and it worked.
This is another solution for that question.
if (switchValue.on == YES)
{
// Code...
}
else
{
// Other code...
}