Table View in View Controller using Core Data - 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.

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.

how to add a sound after X clicks on Xcode

I am trying to make an application that counts the number of clicks on a button, and plays a sound when the number of clicks is 10 ... I've tried a lot of different code, but it's not working. Any help is appreciated.
This is the .m file
What should I add inside the if statement?
#import "ViewController.h"
#import "AVFoundation/AVAudioPlayer.h"
#interface ViewController ()
#end
#implementation ViewController
-(IBAction)Add{
count++;
number.text =[NSString stringWithFormat:#"%i", count];
if (count==10) {
}
}
-(IBAction)Reset{
count=0;
number.text =[NSString stringWithFormat:#"%i", count];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
And this is the .h file
#import <UIKit/UIKit.h>
int count;
#interface ViewController : UIViewController
{
IBOutlet UILabel *number;
}
-(IBAction)Add;
-(IBAction)Reset;
#end

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.

IOS Modal View Controller Presents Black Screen

I am working on an IOS app and have been stumped by this problem for about a week now and cannot find a solution. Any help you can offer would be greatly appreciated. Here's my set-up:
I have a tab bar controller.
I have a TableViewController which has a navigation bar with a navigation item "Add".
After you press the "Add" selector I am modally presenting another viewController that has a picker on it.
I am using Core Data.
When the second view controller is modally presented it comes up with a black screen with a navigation bar. If I access the second view controller from an unrelated screen modally it comes up fine without the navigation bar.
No error message is logged, not even that the object wasn't saved when you press the "save" on the navigation bar. However, pressing "save" will bring you back to the TableViewController and it looks like the entity was added.
Here is the code in my TableViewController:
- (void)add:(id)sender {
SecondViewController *addController = [[SecondViewController alloc] init];
addController.delegate = self;
Entity *newEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Entity" inManagedObjectContext:self.managedObjectContext];
addController.entity = newEntity;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addController];
[self.navigationController presentModalViewController:navController animated:YES];
}
- (void)secondViewController:(SecondViewController *)secondViewController didAddEntity:(Entity *)entity {
if (entity) {
[self showEntity:entity animated:NO];
}
[self dismissModalViewControllerAnimated:YES];
}
- (void)showEntity:(Entity *)entity animated:(BOOL)animated {
EntityDetailTableViewController *detailViewController = [[EntityDetailTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
detailViewController.entity = entity;
[self.navigationController pushViewController:detailViewController animated:animated];
}
Here is the code in my second View Controller:
- (void) save {
entity.attribute = attributeTextField.text;
NSError *error = nil;
if (![entity.managedObjectContext save:&error])
{
NSLog(#"Problem saving attribute: %#", [error localizedDescription]);
}
NSLog(#"saveAttribute");
[self.delegate secondViewController:self didAddEntity:entity];
}
Any suggestions on where to go from here would be really helpful.
After much frustration I found the answer. If you are using storyboard you cannot navigate to the next view controller by the standard code. I put the prepareForSegue statement in my TableViewController.m file and then hooked up the connection on storyboard and identified the segue.
Now, when you press the Add button it segues to the new view controller screen and it's not black.
Here's a link to a useful tutorial
My guess is the way you are initializing is not right:
SecondViewController *addController = [[SecondViewController alloc] init];
should be initWithNIB:

Resources