Getting UITabBarController to work with Core Data - core-data

I've been reading this thread on Stackoverflow and have been trying to replicate the solution with no success in my own project.
My project has 4 tabs. In my app delegate I do this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
Page1 *page1 = (Page1 *)[navController topViewController];
Page2 *page2 = (Page2 *)[navController topViewController];
Page3 *page3 = (Page3 *)[navController topViewController];
Page4 *page4 = (Page4 *)[navController topViewController];
page1.managedObjectContext = self.managedObjectContext;
page2.managedObjectContext = self.managedObjectContext;
page3.managedObjectContext = self.managedObjectContext;
page4.managedObjectContext = self.managedObjectContext;
[self.window makeKeyAndVisible];
return YES;
}
In the originating thread it says I need to create a IBOutlet to each navController for each tab I want to use Core data on.
Whilst you can assign multiple delegates for the UINavigationController the same is not true for the outlets, you can only ever supply ONE outlet for the navController.
I can get Page1 to work, but the other pages simply crash; because of the lack of an IBOutlet.
Do I really need X IBOutlets for Y Tabs or can I do it another way?
Another issue is that the originating thread the accepted answer is:
Ideally you want to pass either the
NSManagedObjectContext,
NSFetchedResultsController or the
relevant NSManagedObject "down" into
the UIViewController.
But there is no code or example of how to do this.
Ideally, I do not want to use a singelton or use the app delegate all over the place.
Any confirmation and clarification would be great.
Thanks.

Your immediate problem has nothing to do with Core Data. You are assigning the same navigation controller to each tab when you need a separate navigation controller for each tab otherwise the navigation controller's hierarchy of views will get scrambled every time you change tabs.
The pattern recommended in the question you linked to is called "dependency injection" and it is the one that Apple recommends in most cases. However, in the case of tabbars or any other complex view/view-controller hierarchy, dependency injection can get to complicated. It's a particular issue with tabbars because you don't usually load all tab view/view-controllers when the app starts but wait until each tab is selected before loading its elements.
Instead, you can use an alternative pattern that exploits the UIApplication objects singleton status. Since there is only one application object, there is only one application delegate object. That means that anywhere in the app you can make a call like this:
(MyApplicationDelegate *) appDelegate=(MyApplicationDelegate *)[[UIApplication sharedApplication] delegate];
... and always get the same application object. Then, if you have the managed object context defined as a property of the app delegate you can get the context just by:
theManagedObjectContext=appDelegate.managedObjectContext
Add these two lines to every view controller and you can always be sure of getting the app delegate's managed object context.

Related

Use core data to send email with string

I have set up a core data model and I would like the ability to be able to tap on an entry and have an blank email pop up with the core data contents included. here is my code for saving the data-
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Device" inManagedObjectContext:context];
NSNumber *timetickNumber = [NSNumber numberWithInt:timeTick];
NSString *versionString = [NSString stringWithFormat:#"Minutes of %#", self.versionTextField.text];
[newDevice setValue:timetickNumber forKey:#"name"];
[newDevice setValue:versionString forKey:#"version"];
[newDevice setValue:self.companyTextField.text forKey:#"company"];
Thanks in advance!
You can't click (or tap) on a Core Data object, because they do not have any UI and do not respond to UI events. The general flow you want is probably something like:
Figure out what UI element should trigger the email action-- a button, or a table row, or whatever UI element you're using.
In the handler method for taps on that element, create the MFMailComposeViewController. Where this happens depends on what UI element you're using. If your UI element is a button, this happens in whatever method your button calls when tapped. If the UI element is a table row, you probably want to use tableView:didSelectRowAtIndexPath: in your table view's delegate.
In that same method, get a reference to the managed object you want to send. If you fetched it (or created it) earlier, you might already have an instance var that points to it. If not, you may need to fetch it here.
Before displaying the MFMailComposeViewController, configure either the message body (via setMessageBody:isHTML:) or the attachment (via addAttachmentData:mimeType:fileName:) to contain the data from your managed object's data
Display the mail compose view to the user.

Issue with Map view delegate method 'mapView:regionDidChange:' do not call

while using map view in my application some times MKMapKit delegate method 'mapView: regionDidChange' do not call.
Its happens only when I drag the map. but when i zoom in or Zoom out Its working perfectly. So its create issue related to place new annotations on map while dragging the map.
I have do this code in mapView:regionDidChange:
int j=0;
-(void) mapView:(MKMapView *)mapsView regionDidChangeAnimated:(BOOL)animated{
zoomLevel = self.mapView.region.span.latitudeDelta;
if (![appDelegate internetConnected]){
return;
}
if (appDelegate.isMapViewRegionChanged) {
if (j==0) {
j++;
return;
}else{
j=0;
appDelegate.isMapViewRegionChanged = FALSE;
return;
}
}
[self callGetMapViewWithObject:nil];
}
/*
first boolean is to check Internet connection.
[appDelegate internetConnected]
Second condition is to return when we navigate from any view controller too map View controller.
appDelegate.isMapViewRegionChanged
Third is a method to place new annotations.
[self callGetMapViewWithObject:nil];
*/
I checked all conditions and booleans but my coding is not reason for this bug.
so may be its related to region did change method.
So while using my app with map, 20% of time its behave like Ideal(method do not call).
can some one help me out with this.
Thank you in advance.
EDIT It broke randomly, so I now call the function again (undoing what I said below), no changes extra... and um, it works. I feel like I'm flipping a coin.
I just had this happen because I have a subclassed MKMapView. I don't know if you're subclassing this or not, but for some reason Apple's super functions, eg: -(void) scrollViewDidScroll; called super but was not intercepted properly and skipped that call.
When I removed the "overridden" call, that was just a call to [super scrollView], it started working properly.
I don't know why apple's code is broken that way (calling super doesn't have the same effect not overriding it), but make sure you're not subclassing these:
ScrollView functions
MKMapView functions...
or perhaps using the WildCard Gesture Recognizer provided very kindly by the answer to why Map Views don't respond to touchesBegan/Moved etc here: How to intercept touches events on a MKMapView or UIWebView objects? .
If this doesn't help, ensure you don't have a view on top of the other views, improper delegates, xibs are arranged and hooked up, the usual stuff.

What must be done to migrate from Storyboard back to XIBs for iOS 4.x support?

I have seen scattered information related to this subject across stackoverflow and other sites, but nothing comprehensive. I've wasted hours upon hours trying to get this to work to no avail. However, I see in this post on stackoverflow (http://stackoverflow.com/questions/7909906/extracting-nibs-xibs-from-storyboards-ios), but nothing with step by step details.
I have only a single view controller (HomeViewController) in my storyboard, and it is extended from a navigation controller.
Can someone please help me identify exactly what all needs to happen to make this work?
Please correct me, add to, etc to make these steps correct:
Create a new XIB (say, HomeViewController.xib)
Copy all items from the storyboard's view controller into this xib
Set the Class type for the view controller to be HomeViewController class.
Hook up all of the IBOutlets and IBActions.
?? - Should I make the xib contain a navigation controller which contains the UIViewController, or should I set the File's Owner to the HomeViewController class??
Add new Window property in the AppDelegate class.
Create a new MainWindow.xib. What setup needs to happen here? (a) File's Owner to UIApplication; (b) Drag new Object to the "Objects" panel and set it's class to my AppDelegate class; (c) Drag new Window to the "Objects" panel; (d) Connect the IBOutlet for the AppDelegate Window property to this new window object.
Go to the project settings (click on the project in the Project Navigation tab, select the target), and clear the "Main Storyboard" field and set the Main Interface to what? HomeViewController? Or Main Window?
Make the change in the AppDelegate class as shown below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
//self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
This is all I know to do, but it still doesn't work. What am I missing?

Monotouch - programming for Swipe Gesture

I am developing a control for an IPAD application (My first time doing Apple development). Its a simple control that mimics a grid - consists of a collection of UIViews (each of which represents a cell) all added to a parent UIView (in a grid like fashion).
One of the requirements is to implement a swipe gesture - the users swipe across the grid to activate/inactivate the cell - this corresponds to a 1/0 in the database.
I create a UISwipeGesture and added it to each of my UIView which represents a cell. That appears to be an incorrect approach as it fires the event for the UIView in which the swipe originated but not across all the UIViews.
My understanding would be that i need to implement the SwipeGesture across the parent UIView which contains all these children UIView. However if i do that how will i know which child UIView has been swiped over? Or any other approach which would make sense?
I know this thread is fairly old, but I created a Swipe extension method that might have helped.
View.Swipe(UISwipeGestureRecognizerDirection.Right).Event += Swipe_Event;
void Swipe_Event(ViewExtensions.SwipeClass sender, UISwipeGestureRecognizer recognizer)
{
View view = sender.View; // do something with view that was swiped.
}
This may not answer your question, but I can speak to the approach I've taken here with a similar use case:
1) I would abandon UIScrollView and use UITableView. You'll notice that UITableView inherits from UIScrollView and has all the performance benefits of virtualization and cell / view re-use. Which you'll find terribly useful as you work towards optimizing your app for performance on device.
2) Utilize the UITableViewCell's ContentView to create custom "Grid" cells. Or better yet, utilize MonoTouch.Dialog if you're not required to create Grid rows ad-hoc.
3) Use this awesome class (props to #praeclarum) to setup gestures in MonoTouch. You essentially provide a UIGestureRecognizer as a generic argument. You can then utilize the LocationInView method to grab the point in the UITableView where the gesture occurred
public void HandleSwipe(UISwipeGestureRecognizer recognizer)
{
if(recognizer.State == UIGestureRecognizerState.Ended) {
var point = recognizer.LocationInView(myTableView);
var indexPath = myTableView.IndexPathForRowAtPoint(point);
// do associated calculations here
}
}
I think you're correct that the gesture recognizer has to be attached to the parent view. In the action method associated with the gesture recognizer I think you can use the Monotouch equivalent of CGRectContainsPoint() to determine whether the swipe occurred in a particular subview. I imagine you would have to iterate through the subviews until you found the one in which the swipe occurred. I'm not aware of a method that would immediately identify the swiped subview.

How can I suspend the working of an NSFetchedResultsController?

I have a UITableViewController fed by an NSFetchedResultsController. From it, the user can call up a modal ViewController in which he or she can enter new data. As this begins, I create a temporary object as follows:
newPtr = [[Entry alloc] initWithEntity:[NSEntityDescription
entityForName:#"Entry" inManagedObjectContext:self.nmocontext]
insertIntoManagedObjectContext:self.nmocontext];
As the user makes choices, attributes of this 'provisional' object, newPtr, are set.
The problem is that the base UITableViewController remains active while the modal ViewController is visible. It seems to be freaking out (causing crashes) in some cases when it realizes a mandatory attribute of newPtr has not been set yet.
What can I do to stop the NSFetchedResultsController from looking at my managed object context until the modal ViewController is dismissed?
Core Data supports "nested" managed object contexts which allow for a flexible architecture that make it easy to support independent, cancellable, change sets. With a child context, you can allow the user to make a set of changes to managed objects that can then either be committed wholesale to the parent (and ultimately saved to the store) as a single transaction, or discarded. If all parts of the application simply retrieve the same context from, say, an application delegate, it makes this behavior difficult or impossible to support.
I haven't tested this myself but a possible approach would be to implement viewWillAppear and viewWillDisappear, and set the fetchedResultsController delegate to self on will appear and nil on will disappear.
OR
You could create an NSObject that mirrors the attributes of your NSManagedObject in your editing window. Once the user has finished editing the attributes (and you have run the appropriate validation rules) you can pass them back to your NSManagedObject instance and let the fetchedResultsController do its job.

Resources