UITableViewCell image + saving to Core Data - core-data

I have a UITableView where I display data from Core Data (people). When scrolling through the table, I fetch (asynchronously) the person's profile image from the web and update the Core Data object once it's done as well as the cell's image view.
However, I am running into an issue because - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { is fired every single time I save an image to Core Data (which eventually slows down the app once the user has scrolled far down). In controllerDidChangeContent: I call reloadData on the table view which of course is the reason the frame rates drops drastically.
Any recommendations for how to deal with saving the images to the core data objects once it's downloaded and updating the cell appropriately?
Thanks.

You can respond to more granular changes in NSFetchedResultsController which may help (see example code).
You may also like to take a look at a session from this years WWDC (2012) which discusses how to improve scrolling in a UITableView, specifically techniques that try to limit the amount of processing to just those rows that are on screen: Session 211 - Building Concurrent User Interfaces on iOS
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}

Related

How the detail view controller told the main controller about the changes made?

I'm reading the Big Nerd Ranch book and I'm at the 10th chapter about the navigation controller.
In the main controller, there is a TableView ( https://github.com/bignerdranch/iOS3eSolutions/blob/master/11.%20Homepwner/Homepwner/Homepwner/ItemsViewController.m ) where there are two methods that interact with the detail view :
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self tableView] reloadData];
}
- (void)tableView:(UITableView *)aTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailViewController = [[DetailViewController alloc] init];
NSArray *items = [[BNRItemStore sharedStore] allItems];
BNRItem *selectedItem = [items objectAtIndex:[indexPath row]];
// Give detail view controller a pointer to the item object in row
[detailViewController setItem:selectedItem];
// Push it onto the top of the navigation controller's stack
[[self navigationController] pushViewController:detailViewController
animated:YES];
}
In the detail view controller (https://github.com/bignerdranch/iOS3eSolutions/blob/master/11.%20Homepwner/Homepwner/Homepwner/DetailViewController.m), there is a method that "saves" the BNRItem being changed :
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Clear first responder
[[self view] endEditing:YES];
// "Save" changes to item
[item setItemName:[nameField text]];
[item setSerialNumber:[serialNumberField text]];
[item setValueInDollars:[[valueField text] intValue]];
}
The code works well :
I understand how the main controller set the object to edit but I don't understand how the main view controller knows that the BNRItem has been changed and then set it back to the tableview ?
I was excepting the author to write a setter in the main controller (ItemsViewController.m ) that could be called by the detail view controller (DetailViewController.m) giving the new BNRItem.
But this part works "automatically".
Thank you.
As you are pointing to the same memory chunk in both controllers, changed made in the DetailViewController are obviously reported in ItemsViewController.m.

Table View in View Controller using Core Data

I am using core data and am trying to add table view cells to a table view in a view controller. When I implement it in a table view controller the app runs. When I run it when the table view is part of another view controller, the app does not run. It shows an error in the code: property tableview not found on object of type DeviceViewController. DeviceViewController.h has a table view data source and delegate. My code is:
#import "DeviceViewController.h"
#import "DeviceDetailViewController.h"
#interface DeviceViewController ()
#property (strong) NSMutableArray *devices;
#end
#implementation DeviceViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Device"];
self.devices = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.devices.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[cell.textLabel setText:[NSString stringWithFormat:#"%# ", [device valueForKey:#"name"]]];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObjectContext *context = [self managedObjectContext];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete object from database
[context deleteObject:[self.devices objectAtIndex:indexPath.row]];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
// Remove device from table view
[self.devices removeObjectAtIndex:indexPath.row];
[self. otableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
As I see it, in this case, it has nothing to do with CoreData.
You should add the UITableViewDataSource and UITableViewDelegate protocols to your VC.
After that you should create a UITableView (you can do it in interface builder) and connect the datasource and delegate to your view controller.
You can also add a property for the UITableView in your VC.
When you use UITableViewController, all this is done from Xcode, but if you use UITableView inside a regular UIViewController you should take care of this stuff.
Good Luck!
EDIT:
Add this as a propert
#property (nonatomic, strong) UITableView *deviceTableView;
Change the viewDidLoad method to this:
- (void)viewDidLoad {
[super viewDidLoad];
_deviceTableView = [[UITableView alloc] initWithFrame:self.view.frame];
_deviceTableView.delegate = self;
_deviceTableView.dataSource = self;
[self.view addSubview:_deviceTableView];
[_deviceTableView reloadData];
}
Delete what you have written in viewDidAppear and replace self.tableView with _deviceTableView everywhere in this file.
Another problem that could be causing the crashes is if your NSManagedObjectContext is nil when you are trying to execute the fetch request. I would add an if([self managedObjectContext])
before using it in any of your view hierarchy methods.
Also, you should be careful when using views instantiated from storyboards. Nikola's answer said to add the code configuring the view controller in viewDidLoad, and that is ok as long as you did not drag a UITableView onto your UIViewController instance in interface builder. If you already have created and linked a tableview in interface builder, you will be needlessly replacing it with a new one, so before the
_deviceTableView = [[UITableView alloc] initWithFrame:self.view.frame];
line, you should add an if(!_deviceTableView)
statement because it will prevent you from adding a second table view to your view. The issue is that if you have already added a tableview in IB, it will be retained by your view even after you change your tableview property, so it is possible to have two tables show up.
Alternatively, you could use "lazy instantiation" in your getter method for the table view property to make sure it is initialized when you access it.
Lastly, in order to help give us a better idea of why your app is crashing it would be helpful to get the exception log from the console in Xcode.

pass core data record from uiview

I've a little issue in my app. my app is based on core data using magical record.
In my first view ( a tableview) I have all the data, when one of the cell is tapped, it open the second view (detail UIview).
But i don't have enough space to show all the detail so i create a second detail view from the first one ( I don't want a scroll view).
the segue between the tableView and the firstDetailView work perfectly
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
DetailViewController*dvc = segue.destinationViewController;
dvc.indice = indexPath.row;
}
}
But when I go to the second detail UIview I always get the detail of the first record in the tableview. I think the problem is in the prepareForSegue method, but I can't figure out how to solve it,
Somebody could help me??
I created another property in the second detail view and set it equal to the property "indice" of the first detail view
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showMaking"]) {
DettaglioPreferito2*dvc = segue.destinationViewController;
dvc.indiceDue = self.indice;
}
}

how to add an UISwitch to a cell in a grouped table view

I am trying to add a UISwitch to a cell in a grouped table view but I am not getting the proper way to do this. Please help me.
Thanks & Regards,
Chakri
Set UISwitch button as accessory for UITableViewCell:
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellId = #"cellId";
UITableViewCell* cellView = [self.tableView dequeueReusableCellWithIdentifier:cellId];
if(!cellView) {
cellView = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
UISwitch* switchBtn = [[UISwitch alloc] init];
[switchBtn addTarget:self action:#selector(onSwitchBtn:) forControlEvents:UIControlEventValueChanged];
cellView.accessoryView = switchBtn;
}
return cellView;
}
You can translate to C# the existing (Objective-C) answers but, since you're using MonoTouch, you should have a look at MonoTouch.Dialog (and it's sample application).
It provides a much easier API on top of UITableView and still covers most of the common usage/scenario requiring tables.
See the screenshots (from previous link) to see UISwitch being used in cells.
are you using storyboards or xibs?
I think in storyboard you can drag the UISwith to the cell.
If you are using xibs, you will need a custom cell. Using interface builder drag a UITableViewCell to your xib outside the tableview, drag the UISwitch inside the UITableViewCell, and put a IBOutlet in the UITableViewCell, then
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == rowWhereYouWantTheCellWithTheUISwitch){
return UITableViewCellWithTheSwitchName;
}
}
rowWhereYouWantTheCellWithTheUISwitch is the number of the cell where you want your custom cell.
UITableViewCellWithTheSwitchName is the name of the IBOutlet you created.

Passing ManagedObjectContext to view controllers using storyboards with a root UITabBarController

Using storyboards you have no easy access to the first view controller in appDelegate (though once you do prepareForSegue makes it easy to pass the ManagedObjectContext down the navigation stack.
I've settled on giving each view controller (or superclass of each view controller) requiring Core Data access a moc member:
#synthesize moc = _moc;
#property (nonatomic) __weak NSManagedObjectContext *moc;
I'm uneasy about it because it doesn't seem a very elegant way to do it - too much code. But assigning directly requires specifying absolute indexes into the viewControllers arrays and changing appDelegate every time the requirement for ManagedObjectContexts change
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
// rootView gets a tab bar controller
for(UINavigationController *navController in tabBarController.viewControllers) {
for(UIViewController *viewController in navController.viewControllers) {
if([viewController respondsToSelector:#selector(setMoc:)]) {
[viewController performSelector:#selector(setMoc:) withObject:self.managedObjectContext];
NSLog(#"Passed moc to %#", [viewController description]);
}
}
}
return YES;
}
What are the pitfalls of this approach and is there a better way? Is it better to try and be more generic:
- (void)assignManagedObjectContextIfResponds:(UIViewController *)viewController {
if([viewController respondsToSelector:#selector(setMoc:)]) {
[viewController performSelector:#selector(setMoc:) withObject:self.managedObjectContext];
NSLog(#"Passed moc to %#", [viewController description]);
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSMutableArray *viewControllers = [NSMutableArray array];
UIViewController *firstLevelViewController = self.window.rootViewController;
if([firstLevelViewController respondsToSelector:#selector(viewControllers)]) {
NSArray *firstLevelViewControllers = [firstLevelViewController performSelector:#selector(viewControllers)];
for(UIViewController *secondLevelViewController in firstLevelViewControllers) {
if([secondLevelViewController respondsToSelector:#selector(viewControllers)]) {
NSArray *secondLevelViewControllers = [secondLevelViewController performSelector:#selector(viewControllers)];
for(UIViewController *thirdLevelViewController in secondLevelViewControllers) {
[viewControllers addObject:thirdLevelViewController];
}
} else {
[viewControllers addObject:secondLevelViewController];
}
}
} else {
// this is the simple case, just one view controller as root
[viewControllers addObject:firstLevelViewController];
}
// iterate over all the collected top-level view controllers and assign moc to them if they respond
for(UIViewController *viewController in viewControllers) {
[self assignManagedObjectContextIfResponds:viewController];
}
return YES;
}
Don't know if I understood properly, but why don't you left the managed object context directly in AppDelegate class and leave there all the logic for instantiate. And from then you can ask for it.
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
then you can recall it anytime from anywhere.
NSManagedObjectContext *moc = [(YourApplicationDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
For convenience I declared a define for it:
#define MOC [(YourApplicationDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext]
Therefore this become:
[MOC save:&error];
You can take this everywhere you like.
Just try to have a look at the auto generated code for a CoreData application in Xcode, you will see that many accessors with CoreData are in there, and the CoreData itself is lazily initialized at first request.
Adam,
Whilst I was exploring storyboards I pretty much did it the same way you did except I made each of my view controllers that had a MOC property conform to a protocol.
There's nothing significantly different there, so I'll move on.
I think the point is Storyboards, IMO, are half-baked. Coming from a .Net background what is obviously missing is an object builder framework coupled with an IoC container.
When Apple add that Storyboards will be awesome. When the storyboard framework can look at the destinationViewController, determine it's dependencies and resolve those from a container life will be great. For now, all it can really do is look at the destinationViewController and init you a generic one, which is of limited use.
Unfortunately, because it's a half-baked solution I'm sticking with the traditional approach for now so all my view controllers are alloc'd and init'd manually and more importantly I've added a method to each view controller to initWithMOC:(MOC *)moc;
The architect in me is telling me this code is more robust, I guess it's a matter of opinion as to whether it's worth the trade-off.
Anyone else come up with a better way?
CA.

Resources