I've programmatically created a UITableView within my MatchCenterViewController, however it doesn't seem to populate with the JSON data being returned by my cloud code function. It simply shows a blank View Controller. MatchCenterViewController is a ViewController embedded within a Navigation View Controller.
MatchCenterViewController.h:
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import "AsyncImageView.h"
#import "SearchViewController.h"
#interface MatchCenterViewController : UIViewController <UITableViewDataSource>
#property (nonatomic) IBOutlet NSString *itemSearch;
#property (nonatomic, strong) NSArray *imageURLs;
#property (strong, nonatomic) NSString *matchingCategoryCondition;
#property (strong, nonatomic) NSString *matchingCategoryLocation;
#property (strong, nonatomic) NSNumber *matchingCategoryMaxPrice;
#property (strong, nonatomic) NSNumber *matchingCategoryMinPrice;
#property (strong, nonatomic) NSArray *matchCenterArray;
#end
MatchCenterViewController.m:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [_matchCenter dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *matchCenterDictionary= [self.matchCenterArray objectAtIndex:indexPath.row];
cell.textLabel.text = [matchCenterDictionary objectForKey:#"Title"];// title of the first object
// if([matchCenterDictionary objectForKey:#"Price"] != NULL)
// {
// cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#",[matchCenterDictionary objectForKey:#"Price"]];
// }
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.matchCenterArray = [[NSArray alloc] init];
}
- (void)viewDidAppear:(BOOL)animated
{
self.matchCenterArray = [[NSArray alloc] init];
[PFCloud callFunctionInBackground:#"MatchCenterTest"
withParameters:#{
#"test": #"Hi",
}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
self.matchCenterArray = [result objectForKey:#"Top 3"];
dispatch_async(dispatch_get_main_queue(), ^{
[_matchCenter reloadData];
});
NSLog(#"Test Result: '%#'", result);
}
}];
}
#end
How do you create this viewController? If you don't use [[MatchCenterViewController alloc] initWithNibName:... bundle:...] the table creation won't be called. This might happen because you have the viewController in a storyBoard, in this case initWithCoder: would be the method to overwrite.
I would recommend to move this code to viewDidLoad, which will be called regardless how the viewController was created . E.g.:
- (void)viewDidLoad {
[super viewDidLoad];
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
self.matchCenterArray = [[NSArray alloc] init];
}
Related
I can't figure out why I am unable to set the value of an instance of PFObject. It is working successfully elsewhere in the app, but I must be doing something wrong in this view. I'd really appreciate your help figuring out what I'm missing. Thanks.
Basically
self.ABC.type = #"Frustrated";
NSLog(#"why is this null? -----> %#",self.ABC.type);
2015-10-18 18:26:29.277 XX [885:109003] why is this null? -----> (null)
Why is the value not being assigned? It should log as ... I'm not null or frustrating... I'm working, see -----> Frustrated !!!!!! but it is not working!
I'm therefore unable to set the object in Parse bc it's nil and the app crashes.
Below should be all and more of the code you need, but let me know it you have questions or suggestions. Thanks!:
FCollectionViewController.m:
#import "FCollectionViewController.h"
#import "FCell.h"
#import "SectionHeaderView.h"
#import "MBProgressHUD.h"
#import "Helper.h"
#interface FCollectionViewController()
<UICollectionViewDelegate, MBProgressHUDDelegate>
#property (nonatomic, strong) NSDictionary *presetsDictionary;
#property (nonatomic, strong) NSArray *presets;
#property (nonatomic, strong) NSIndexPath *textViewIndexPath;
#property (nonatomic, strong) FCell *fCell;
#property (nonatomic, strong) MBProgressHUD *HUD;
#property (nonatomic, strong) NSArray *headTitleArray;
#end
#implementation FCollectionViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.headTitleArray = [NSArray arrayWithObjects:
#"type",
#"model",
#"feature", nil];
PFQuery *query = [PFQuery queryWithClassName:#"Presets"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableArray *values = [[NSMutableArray alloc] init];
for (PFObject *obj in objects) {
[keys addObject:[obj objectForKey:#"key"]];
[values addObject:[obj objectForKey:#"value"]];
}
self.presetsDictionary = [NSDictionary dictionaryWithObjects:values forKeys:keys];
self.presets = [[NSMutableArray alloc] initWithObjects:
[self.presetsDictionary objectForKey:#"type"],
[self.presetsDictionary objectForKey:#"model"],
[self.presetsDictionary objectForKey:#"feature"],nil];
NSLog(#"self.presets ----> %#", self.presets);
[self.collectionView reloadData];
}];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return [self.presets count];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.presets[section] count];
}
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView * view = nil;
if ([kind isEqualToString:UICollectionElementKindSectionHeader])
{
ItemSectionHeaderView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:NSStringFromClass([ItemSectionHeaderView class])
forIndexPath:indexPath];
header.captionLabel.text = self.headTitleArray[indexPath.section];
view = header;
}
return view;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell;
FCell *aCell = (FCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"fCell" forIndexPath:indexPath];
aCell.label.text = self.presets[indexPath.section][indexPath.row];
aCell.label.textAlignment = NSTextAlignmentCenter;
cell = aCell;
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize size;
....
return size;
}
#pragma mark <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSArray * selectedRows = self.collectionView.indexPathsForSelectedItems;
for (NSIndexPath * selectedRow in selectedRows) {
if ((selectedRow.section == indexPath.section) && (selectedRow.row != indexPath.row)) {
[self.collectionView deselectItemAtIndexPath:selectedRow animated:NO];
}
}
switch (indexPath.section) {
case 0:
self.aBC.type = #"Frustrated";
NSLog(#"why isn't this being assigned? -----> %#",self.ABC.type);
break;
case 1:
self.aBC.model = self.presets[indexPath.section][indexPath.row];
NSLog(#"why is this null?!!!! -----> %#",self.ABC.model);
NSLog(#"this DOES log the value I want!! -----> %#",self.presets[indexPath.section][indexPath.row]);
break;
case 2:
...
default:
break;
}
}
FCollectionViewController.h:
#import <UIKit/UIKit.h>
#import "ABC.h"
#interface FCollectionViewController : UICollectionViewController
//<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#property (nonatomic, strong) ABC *aBC;
#end
ABC.h:
#import <Foundation/Foundation.h>
#interface ABC : NSObject
#property (nonatomic, strong) NSString *type;
#property (nonatomic, strong) NSString *model;
#property (nonatomic, strong) NSString *feature;
#property (nonatomic, strong) PFObject *pfObj;
#property (nonatomic, strong) PFUser *user;
- (id)initWithPFObject:(PFObject *)anObject;
#end
ABC.m:
#import "ABC.h"
#implementation ABC
- (id)initWithPFObject:(PFObject *)anObject
{
if(self = [super init])
{
[anObject fetchIfNeeded];
self.pfObj = anObject;
self.type = [anObject objectForKey:#"type"];
self.model = [anObject objectForKey:#"model"];
...
}
return self;
}
#end
FCell.h:
#import <UIKit/UIKit.h>
#interface FCell : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *label;
#end
FCell.m:
#import "FCell.h"
#implementation FCell
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.frame];
self.selectedBackgroundView.backgroundColor = [UIColor darkGrayColor];
[self setNeedsDisplay];
}
#end
aBC isn't initialized anywhere. You declare it, but it isn't initialized. So setting aBC.type isn't actually setting anything.
Add self.aBC = [[ABC alloc] init]; in your viewDidLoad method.
When a user taps a cell, I want itemURL to be set to that cells "Item URL" property. Once it does this, it should then send over the itemURL in prepareForSegue over to WebViewController, as I've attempted to do. When I have WebViewController NSLog the itemURL property however, it comes up as null. How can I make sure the value is sent over properly?
MatchCenterViewController.h:
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import "AsyncImageView.h"
#import "SearchViewController.h"
#import "WebViewController.h"
#interface MatchCenterViewController : UIViewController <UITableViewDataSource>
#property (nonatomic) IBOutlet NSString *itemSearch;
#property (nonatomic, strong) NSArray *imageURLs;
#property (strong, nonatomic) NSString *matchingCategoryCondition;
#property (strong, nonatomic) NSString *matchingCategoryLocation;
#property (strong, nonatomic) NSNumber *matchingCategoryMaxPrice;
#property (strong, nonatomic) NSNumber *matchingCategoryMinPrice;
#property (strong, nonatomic) NSArray *matchCenterArray;
#property (strong, nonatomic) NSString *searchTerm;
#property (strong, nonatomic) NSURL *itemURL;
#end
MatchCenterViewController.m:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
self.matchCenter.frame = CGRectMake(0,50,320,self.view.frame.size.height-100);
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
_matchCenterArray = [[NSArray alloc] init];
}
- (void)viewDidAppear:(BOOL)animated
{
self.matchCenterArray = [[NSArray alloc] init];
[PFCloud callFunctionInBackground:#"MatchCenter"
withParameters:#{
#"test": #"Hi",
}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[_matchCenter reloadData];
NSLog(#"Result: '%#'", result);
}
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _matchCenterArray.count;
}
//the part where i setup sections and the deleting of said sections
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 21.0f;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 21)];
headerView.backgroundColor = [UIColor lightGrayColor];
_searchTerm = [[[[_matchCenterArray objectAtIndex:section] objectForKey:#"Top 3"] objectAtIndex:3]objectForKey:#"Search Term"];
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 0, 250, 21)];
headerLabel.text = [NSString stringWithFormat:#"%#", _searchTerm];
headerLabel.font = [UIFont boldSystemFontOfSize:[UIFont systemFontSize]];
headerLabel.textColor = [UIColor whiteColor];
headerLabel.backgroundColor = [UIColor lightGrayColor];
[headerView addSubview:headerLabel];
UIButton *deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
deleteButton.tag = section;
deleteButton.frame = CGRectMake(300, 2, 17, 17);
[deleteButton setImage:[UIImage imageNamed:#"xbutton.png"] forState:UIControlStateNormal];
[deleteButton addTarget:self action:#selector(deleteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:deleteButton];
return headerView;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialize cell
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// No cell seperators = clean design
tableView.separatorColor = [UIColor clearColor];
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Title"];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
// price of the item
cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Price"]];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 65;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSURL *itemURL = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Item URL"];
// NSLog(#"The url is: '%#'", itemURL);
[self performSegueWithIdentifier:#"WebViewSegue" sender:self];
}
- (void)deleteButtonPressed:(id)sender
{
// links button
UIButton *deleteButton = (UIButton *)sender;
// Define the sections title
NSString *sectionName = _searchTerm = [[[[_matchCenterArray objectAtIndex:deleteButton.tag] objectForKey:#"Top 3"] objectAtIndex:3]objectForKey:#"Search Term"];
// Run delete function with respective section header as parameter
[PFCloud callFunctionInBackground:#"deleteFromMatchCenter"
withParameters:
#{#"searchTerm": sectionName,}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
[PFCloud callFunctionInBackground:#"MatchCenter"
withParameters:#{
#"test": #"Hi",
}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[_matchCenter reloadData];
NSLog(#"Result: '%#'", result);
}
}];
}
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
WebViewController *controller = (WebViewController *) segue.destinationViewController;
controller.itemURL = self.itemURL;
}
#end
WebViewController.h:
#import <UIKit/UIKit.h>
#import "MatchCenterViewController.h"
#interface WebViewController : UIViewController <UIWebViewDelegate>
#property (strong, nonatomic) NSURL *itemURL;
#property (weak, nonatomic) IBOutlet UIWebView *myWebView;
#end
WebViewController.m:
#import "WebViewController.h"
#interface WebViewController ()
#end
#implementation WebViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"The url is: '%#'", _itemURL);
// _myWebView=[[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
// _myWebView.delegate=self;
// [self.view addSubview:_myWebView];
self.myWebView.delegate = self;
//////////
NSURLRequest *request = [NSURLRequest requestWithURL:_itemURL];
//4
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//5
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil) [self.myWebView loadRequest:request];
else if (error != nil) NSLog(#"Error: %#", error);
}];
[self.myWebView setScalesPageToFit:YES];
//////
//[self.myWebView loadRequest:request];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
In your didSelectRowForIndexPath: instead of
NSURL *itemURL = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Item URL"];
use
self.itemURL = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Item URL"];
So I've been trying this for a while now and it is completely frustrating me but here is a brief summary of what I am trying to do. I have a LocationTableViewController with a plus button on the top right to add new locations to the table view. When that happens, I enter the LocationEditViewController where I can enter the name of the location I want to add. After adding my text and hitting the save location button I want the code to bring me back to the LocationTableViewController and there in my table I see my newly added location. Posted below is the code of the two view controllers. Hopefully you guys can help me thanks a ton!!
#import <UIKit/UIKit.h>
#import "Location.h"
#import "User.h"
#interface LocationEditViewController : UIViewController <UITextFieldDelegate>
#property (strong, nonatomic) Location *location;
#property (strong, nonatomic) User *user;
#property (strong, nonatomic) UITextField *locationNameField;
- (void)saveLocation:(id) sender;
#end
#import "LocationEditViewController.h"
#interface LocationEditViewController ()
#end
#implementation LocationEditViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = #"Edit";
self.location = [[Location alloc] init];
self.user = [[User alloc] init];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UILabel *locationLabel = [[UILabel alloc] init];
locationLabel.frame = CGRectMake(20,15,50,30);
locationLabel.text = #"Name:";
[self.view addSubview:locationLabel];
self.locationNameField = [[UITextField alloc] init];
self.locationNameField.frame = CGRectMake(15,50,290,30);
self.locationNameField.borderStyle = UITextBorderStyleBezel;
self.locationNameField.keyboardType = UIKeyboardTypeDefault;
self.locationNameField.delegate = self;
[self.view addSubview:self.locationNameField];
UIButton *saveLocationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
saveLocationButton.frame = CGRectMake(15,400,290,50);
[saveLocationButton setTitle:#"Save Location" forState:UIControlStateNormal];
[saveLocationButton addTarget:self action:#selector(saveLocation:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:saveLocationButton];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)saveLocation:(id)sender {
self.location.name = self.locationNameField.text;
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:self.user.createdLocations];
[tempArray addObject:self.location];
self.user.createdLocations = [[NSArray alloc] initWithArray:tempArray];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Location Added"
message:#"This location is now accessable in the locations tab"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[self.tabBarController.tabBar.items[1] setBadgeValue:[NSString stringWithFormat:#"%i",self.user.createdLocations.count]];
[self dismissViewControllerAnimated:YES completion:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"somethingAddedNotification" object:nil];
}];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Here is the LocationTableViewController code:
#import <UIKit/UIKit.h>
#import "User.h"
#interface LocationTableViewController : UITableViewController
#property (strong, nonatomic) NSArray *locations;
#property (strong, nonatomic) User *user;
#property (strong, nonatomic) id _observer;
- (void) addLocationPressed;
#end
#import "LocationTableViewController.h"
#import "LocationEditViewController.h"
#import "LocationViewController.h"
#interface LocationTableViewController ()
#end
#implementation LocationTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
self.title = #"Locations";
self.user = [[User alloc] init];
UIBarButtonItem *addLocationButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addLocationPressed)];
self.navigationItem.rightBarButtonItem = addLocationButton;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//Location *loc = [[Location alloc] init];
//self.user.createdLocations = #[loc];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void) addLocationPressed
{
LocationEditViewController *locationEditVC = [[LocationEditViewController alloc] init];
[self presentViewController:locationEditVC animated:YES completion:nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.user.createdLocations.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
cell.textLabel.text = [self.user.createdLocations[indexPath.row] name];
NSLog(#"%#", [self.user.createdLocations[indexPath.row] name]);
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
LocationViewController *locationVC = [[LocationViewController alloc] init];
locationVC.location = self.user.createdLocations[indexPath.row];
[self.navigationController pushViewController:locationVC animated:YES];
}
- (void)viewWillAppear:(BOOL)animated {
__observer = [[NSNotificationCenter defaultCenter] addObserverForName:#"somethingAddedNotification"
object:nil
queue:nil
usingBlock:^(NSNotification *notification)
{
[self.tableView reloadData];
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:__observer];
}
For additional information here is the user class where I am getting the array
#import <Foundation/Foundation.h>
#import "Location.h"
#interface User : NSObject
#property (strong, nonatomic) Location *profilePhoto;
#property (strong, nonatomic) NSString *location;
#property (strong, nonatomic) NSArray *createdLocations;
-(id) initWithTitle: (Location *) aLoc
detail: (NSString *) aDet
filename: (NSArray *) aLocList;
//-(id)initWithJSON;
//+(NSString *)getPathToArchive;
//+(User *)getUser;
//+(void)saveUser:(User *)aUser;
#end
#import "User.h"
#import "Location.h"
#implementation User
- (id)init;
{
self = [self initWithTitle: [[Location alloc] init]
detail: #"Temp"
filename: [[NSArray alloc] init]];
return self;
}
-(id) initWithTitle: (Location *) aLoc
detail: (NSString *) aDet
filename: (NSArray *) aLocList
{
self = [super init];
if (self) {
self.profilePhoto = aLoc;
self.location = aDet;
self.createdLocations = aLocList;
}
return self;
}
#end
and the Locations class if necessary
#import <Foundation/Foundation.h>
#interface Location : NSObject
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSString *detail;
#property (strong, nonatomic) NSString *filename;
#property (strong, nonatomic) NSString *thumbnail;
-(id) initWithTitle: (NSString *) aTitle
detail: (NSString *) aDetail
filename: (NSString *) aFilename
thumbnail: (NSString *) aThumbnail;
#end
#import "Location.h"
#implementation Location
-(id)init
{
self = [self initWithTitle:#"Title"
detail:#"Detail"
filename:#"placeholder.jpg"
thumbnail:#"placeholder.jpg"];
return self;
}
-(id)initWithTitle:(NSString *)aTitle
detail:(NSString *)aDetail
filename:(NSString *)aFilename
thumbnail:(NSString *)aThumbnail
{
self = [super init];
if (self) {
self.name = aTitle;
self.detail = aDetail;
self.filename = aFilename;
self.thumbnail = aThumbnail;
}
return self;
}
#end
Hopefully you guys can help me! Thanks again!!!
Have you set a breakpoint on...
[self.tableView reloadData];
...in your observer block? Does it get hit? Separately, Drew Crawford is wary of that API you are using (http://sealedabstract.com/code/nsnotificationcenter-with-blocks-considered-harmful/).
Like the comment said, this is definitely a problem, and may in fact be the root cause. You must use the __weak reference inside the block or else NSNotificationCenter owns a strong reference to self and self owns a strong reference to NSNotificationCenter, and you have a retain cycle.
- (void)viewWillAppear:(BOOL)animated {
__weak LocationTableViewController *weakSelf = self;
__observer = [[NSNotificationCenter defaultCenter] addObserverForName:#"somethingAddedNotification"
object:nil
queue:nil
usingBlock:^(NSNotification *notification)
{
[weakSelf.tableView reloadData];
}];
}
I have a SurroundViewController (CollectionView) which shows images loaded from a webserver. If you click on the image you will be navigated to a DetailViewController (TableView) which shows additional information to the image. Both Emebded in a NavigationController (see storyboard image).
My problem start when I do a refresh in the SurroundViewController, when coming back from the DetailViewController. Then it crashes with EXC_BAD_ACCESS on the performSelector line
WebApi.m
-(void)getSurroundStream {
NSString *URLString = [NSString stringWithFormat:#"%#/%#/view/%f/%f", kApiHost, kApiPath, self.sshare.coordinate.longitude, self.sshare.coordinate.latitude];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[self setAuthHeader:manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.sshare putViData:responseObject];
[self.delegate performSelector:#selector(didLoadFoo)]; // --> EXC_BAD_ACCESS
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.error vError:error message:operation.responseString url:URLString];
}];
}
I checked in the Debug Console:
2014-03-11 14:22:51.989 Foo[6923:60b] -[SurroundViewController refresh:] [Line 352] refreshing
2014-03-11 14:22:51.998 Foo[6923:60b] -[WebApi getSurroundImages] [Line 393] do surround composition
(lldb) po self.delegate
[no Objective-C description available]
Seems that the object which is not available. What I do not understand is. I am in the SurroundViewController and do activly a refresh by pull-to-refresh. So I am on the Surround View and the object should be available...
How do I fix this issue, that the App does not crash with EXC_BAD_ACCESS at the performSelector line?
Here's the code which is involved with the issue (necessary parts):
SurroundViewController.h
#import <UIKit/UIKit.h>
#import "WebApi.h"
#import "DetailViewController.h"
#import "SingletonClass.h"
#interface SurroundViewController : UICollectionViewController <WebApiDelegate>
#property (nonatomic, strong) WebApi *swebapi;
#property (nonatomic, strong) SingletonClass *sshare;
#end
SurroundViewController.m
#import "SurroundViewController.h"
#interface SurroundViewController ()
#property (nonatomic, strong) UIRefreshControl *refresh;
#end
#implementation SurroundViewController
-(void)vinit {
self.sshare = [SingletonClass sharedInstance];
self.swebapi = [WebApi sharedInstance];
self.swebapi.delegate = self;
}
- (void)viewDidLoad
{
[self vinit];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[super viewDidLoad];
[self addRefresh];
[self.swebapi getSurroundImages]; // will call delegate didComposition
}
- (void)viewDidAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// cell configuration
}
-(void)addRefresh {
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
self.refresh = refreshControl;
[self.collectionView addSubview:self.refresh];
}
-(void)refresh:(UIRefreshControl*)refresh {
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Refreshing..."];
[self.swebapi getSurroundImages];
}
-(void)didLoadFoo {
[self.swebapi doComposition];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"toDetailView" sender:indexPath];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"toDetailView"]) {
DetailViewController *dvc = [segue destinationViewController];
NSIndexPath *indexPath = sender;
dvc.idx = [self getItemOfSection:indexPath];
dvc.detailData = [[self.sshare coItem:dvc.idx] mutableCopy];
}
}
- (int)getItemOfSection:(NSIndexPath *)indexPath {
return (int)indexPath.item + ((int)indexPath.section * 4);
}
#end
WebApi.h
#import "AFHTTPRequestOperationManager.h"
#import "Errors.h"
#class WebApi;
#protocol WebApiDelegate <NSObject>
#optional
-(void)didLoadFoo;
#end
#interface WebApi : AFHTTPRequestOperationManager <SingletonDelegate>
#property (assign, nonatomic)id<WebApiDelegate> delegate;
#property (nonatomic, strong) Errors *error;
+(WebApi*)sharedInstance;
-(void)getSurroundStream;
-(void)getSurroundImages;
#end
WebApi.m
#import "WebApi.h"
#define kApiHost #"http://sample.com"
#define kApiPath #"sample"
#implementation WebApi
-(WebApi*)initWithBaseURL:url {
self = [super init];
if (self != nil) {
self.sshare = [SingletonClass sharedInstance];
self.error = [[Errors alloc] init];
}
return self;
}
+(WebApi*)sharedInstance
{
static WebApi *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kApiHost]];
});
return sharedInstance;
}
-(void)getSurroundStream {
NSString *URLString = [NSString stringWithFormat:#"%#/%#/view/%f/%f", kApiHost, kApiPath, self.sshare.coordinate.longitude, self.sshare.coordinate.latitude];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[self setAuthHeader:manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.sshare putViData:responseObject];
[self.delegate performSelector:#selector(didLoadFoo)]; // --> EXC_BAD_ACCESS
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.error vError:error message:operation.responseString url:URLString];
}];
}
-(void)getSurroundImages {
[self getSurroundStream];
}
#end
SingletonClass.h
#import <Foundation/Foundation.h>
#class Singleton;
#protocol SingletonDelegate <NSObject>
-(void)didRefreshToken;
#end
#interface SingletonClass : NSObject
#property (assign, nonatomic) id<SingletonDelegate> delegate;
#property (nonatomic, strong) NSMutableArray *viData;
#property (nonatomic, strong) NSMutableArray *coData;
#end
SingletonClasss.m
#import "SingletonClass.h"
#implementation SingletonClass
static SingletonClass *sharedInstance = nil;
// Get the shared instance and create it if necessary.
+ (SingletonClass *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
self.coData = [[NSMutableArray alloc] init];
self.viData = [[NSMutableArray alloc] init];
}
return self;
}
// We don't want to allocate a new instance, so return the current one.
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Equally, we don't want to generate multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
-(NSMutableDictionary *)coItem:(int)position {
NSAssert(self.coData.count > position, #"Position does not exists: coData.count: %lu > position: %d", (unsigned long)self.coData.count, position);
return self.coData[position];
}
#end
DetailViewController.h
#import <UIKit/UIKit.h>
#import "SingletonClass.h"
#import "WebApi.h"
#interface DetailViewController : UITableViewController <WebApiDelegate>
#property (nonatomic) int idx;
#property (nonatomic, strong) SingletonClass *sshare;
#property (nonatomic, strong) WebApi *swebapi;
#property (nonatomic, strong) NSMutableDictionary *detailData;
#end
DetailViewController.m
#import "DetailViewController.h"
#interface DetailViewController ()
#property (nonatomic, strong) NSArray *cellRows;
#end
#implementation DetailViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)vinit {
self.sshare = [SingletonClass sharedInstance];
self.swebapi = [WebApi sharedInstance];
self.swebapi.delegate = self;
NSAssert(self.detailData, #"detailData is not available");
}
- (void)viewDidLoad
{
[self vinit];
[self.navigationController setNavigationBarHidden:NO animated:NO];
[super viewDidLoad];
self.cellRows = #[#"cellLocation:", #"cellIntention:"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.cellRows.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"detailCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
SEL functionCall = NSSelectorFromString(self.cellRows[indexPath.row]);
[self performSelector:functionCall withObject:cell];
return cell;
}
- (void)cellLocation:(UITableViewCell*)cell {
// configuration of table cell
}
- (void)cellIntention:(UITableViewCell*)cell {
// configuration of table cell
}
#end
You are setting DetailViewController as delegate. Of course you will get EXC_BAD_ACCESS after it's deallocated.
You should use notifications instead delegates for shared instances.
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender and - (void)removeObserver:(id)notificationObserver are yours friends.
In your protocol, you set didLoadDoo as optional,
#protocol WebApiDelegate <NSObject>
#optional
-(void)didLoadFoo;
#end
so you need to secure the call to that method in your delegate
if ([self.delegate respondsToSelector:#selector(didLoadFoo)]) {
[self.delegate performSelector:#selector(didLoadFoo)];
}
As you are working with a singleton
+(WebApi*)sharedInstance
if your singleton.delegate is change somewhere else in your code (i.e. in your detailVC), it is change everyWhere !
edit :
After some more check, now we know that WebApi.delegate is change in detailVC, and the bug appear when we are back from detailVC because at this step detailVC is becoming nil and of course WebApi.delegate also.
So, the solution is to reset WebApi.delegate when we are back in SurroundViewController, and we can do that in :
SurroundViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.swebapi.delegate = self;
}
I'm new to iOS and just trying to get data to display in UITableView. I basically have a project set up about authors. I want to display author names etc. So i have an Author model and AuthorsViewController which is a datasource and delegate for UITableView etc. I'm using storyboard (MainStoryboard) tableview and managed to connect it to AuthorsViewController in the "Identity Inspector".
Image of Storyboard if it helps, thanks:
Here, first, is the model: Author.h
#import <Foundation/Foundation.h>
#interface Author : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *book;
#property (nonatomic) int year;
#end
Author.m
#import "Author.h"
#implementation Author
#end
Here is the AuthorsViewController.h
#interface AuthorViewController : UITableViewController
<UITableViewDataSource, UITableViewDelegate>
#end
And AuthorsViewController.m
#import "AuthorViewController.h"
#interface AuthorViewController ()
#property (nonatomic, strong) NSMutableArray *authors;
#end
#implementation AuthorViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_authors = [[NSMutableArray alloc] init];
Author *auth = [[Author alloc] init];
[auth setName:#"David Powers"];
[auth setBook:#"PHP Solutions"];
[auth setYear:2010];
[_authors addObject:auth];
auth = [[Author alloc] init];
[auth setName:#"Lisa Snyder"];
[auth setBook:#"PHP security"];
[auth setYear:2011];
[_authors addObject:auth];
auth = [[Author alloc] init];
[auth setName:#"Rachel Andrew"];
[auth setBook:#"CSS3 Tips, Tricks and Hacks"];
[auth setYear:2012];
[_authors addObject: auth];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_authors count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"AuthorCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell != nil)
{
cell = [[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Author *currentAuthor = [_authors objectAtIndex:[indexPath row]];
[[cell textLabel] setText: [currentAuthor name]];
NSLog(#"%#", [currentAuthor name]);
return cell;
}
#end
Since you made the table view with its cell in the storyboard, you don't need the if (cell == nil) clause at all. You also need to call [self.tableView reloadData] as the last line in viewDidLoad.