I'm trying to load a set of images from a web service to a UICollectionView and I'm using MKNetworkKit to handle the networking operations.
This is bit a of a strange problem because it works in one scenario and doesn't in another. Please bear with with me, this is bit of a long post.
I have a simple storyboard app with a UIViewController with a UICollectionView embedded as its main view (Can't use UICollectionViewController due to a UI change I'm gonna do later).
I have created a class which is a subclass of MKNetworkEngine to handle the method to retrieve the images from the web service.
ImageEngine.h
#import "MKNetworkEngine.h"
#interface ImageEngine : MKNetworkEngine
typedef void (^ImagesResponseBlock)(NSMutableArray *imageURLs);
- (void)allImages:(ImagesResponseBlock)imageURLBlock errorHandler:(MKNKErrorBlock)errorBlock;
#end
ImageEngine.m
#import "ImageEngine.h"
#implementation ImageEngine
- (void)allImages:(ImagesResponseBlock)imageURLBlock errorHandler:(MKNKErrorBlock)errorBlock
{
MKNetworkOperation *op = [self operationWithPath:#"All_Images.php"];
[op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
[completedOperation responseJSONWithCompletionHandler:^(id jsonObject) {
imageURLBlock(jsonObject[#"Images"]);
}];
} errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
errorBlock(error);
}];
[self enqueueOperation:op];
}
- (NSString *)cacheDirectoryName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0];
NSString *cacheDirectoryName = [documentsDirectory stringByAppendingPathComponent:#"SomeImages"];
return cacheDirectoryName;
}
#end
In the view controller class with the collection view,
#import "AppDelegate.h"
#import "GridViewController.h"
#import "ImageCell.h"
#interface GridViewController () <UICollectionViewDelegate, UICollectionViewDataSource>
#property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
#property (strong, nonatomic) NSMutableArray *images;
#property (strong, nonatomic) NSString *selectedImageUrl;
#end
#implementation GridViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[ApplicationDelegate.imageEngine allImages:^(NSMutableArray *images) {
self.images = images;
[self.collectionView reloadData];
} errorHandler:^(NSError *error) {
NSLog(#"MKNetwork Error: %#", error.localizedDescription);
}];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.images.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
ImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *thisImage = self.images[indexPath.row];
self.selectedImageUrl = [NSString stringWithFormat:#"%#%#", #"http://toonmoodz.osmium.lk/", thisImage[#"image"]];
[cell.imageView setImageFromURL:[NSURL URLWithString:self.selectedImageUrl]];
return cell;
}
#end
This works just fine. The images load as expected.
Then I did a small UI upgrade to the app using MBPullDownController. It simply adds a table view under the collection view. No changes to the networking code. Just a new subclass of MBPullDownController to embed the collection view and the table view in the main view controller.
But when I do that, the images don't load at all. I put a breakpoint inside the methods of the ImageEngine class to see if they get fired but it never comes to that. (The weird thing is this code actually worked fine just this morning. Now it doesn't and I have absolutely no idea why). It doesn't throw any errors or warnings either.
I have uploaded two projects demonstrating this issue so that it'll be easier for others to understand. If anyone can help me out, I'd be really grateful. I've been pulling my hair out for the past few hours on this.
Source of the version that's working correctly.
This is the source of the project that is with the issue. (When you run it, it'll show a blank white view. It looks like a plain view but it is the collection view. I loaded up a set of local images to see if its working and it does)
Thank you.
When I set breakpoint in GridViewController in viewDidLoad method, after executing po [[UIApplication sharedApplication] valueForKeyPath:#"delegate.imageEngine"] in console I see that imageEngine property is equal to nil.
It looks like application:didFinishLaunchingWithOptions is being executed after viewDidLoad in GridViewController.
I removed those two lines from your application:didFinishLaunchingWithOptions:
self.imageEngine = [[ImageEngine alloc] initWithHostName:#"toonmoodz.osmium.lk"];
[self.imageEngine useCache];
Then i've added imageEngine lazy loader to AppDelegate and its working http://cl.ly/image/1S172D2e050J
- (ImageEngine*) imageEngine {
if (_imageEngine == nil) {
_imageEngine = [[ImageEngine alloc] initWithHostName:#"toonmoodz.osmium.lk"];
[_imageEngine useCache];
}
return _imageEngine;
}
Related
There are a huge number of questions relating to this topic but I have not yet come across my use case so here goes.
This is my first couple weeks in OBJ-C so I have no clue what I am doing with some of this stuff...
What I Want
I do not particularly enjoy seeing so many classes in OBJ-C that overload the view controller classes with every and any function on this earth. It looks dirty and feels gross as far as OOP goes. In my use case I don't have a full screen table just a little one to hold 10 things. Therefore it's quite inappropriate to use a full UITableViewController. Instead, I want to have all my table delegate specific methods to be in a UITableView sub-class. NOT in a UITableViewController or a ViewController with a UITableView property. This should be mega simple yet...
The Problem
No matter what I do I cannot seem to get the method cellForRowAtIndexPath to fire. I know enough to know that this stuff relies heavily on the delegate and datasource assignment... however since I have a separate UITableView class that uses the <UITableViewDelegate, UITableViewDataSource> delegations I don't think I should have to do any sort of assignment at all!
What am I gonna write?? self.delegate = self ? or worse, in the ViewController that calls this UITableView class, self.tasksTable.delgate = self.tasksTable ? Eww... gross
Here is what I am doing in code.
The Code
TasksTableView.h
#import <UIKit/UIKit.h>
#interface TasksTableView : UITableView <UITableViewDelegate, UITableViewDataSource> {
NSArray *tasksData;
}
- (NSMutableArray *)getAllTasks;
#end
TasksTableView.m
#import "TasksTableView.h"
#import "NSObject+RemoteFetch.h" //<--I use this to fetch, obvs
#interface TasksTableView ()
#property (nonatomic, strong) NSString *cellId;
#end
#implementation TasksTableView
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self) {
_cellId = #"AllTasksTableCell";
tasksData = [self getAllTasks];
}
return self;
}
#pragma mark - Custom Table Functionality
- (NSMutableArray *)getAllTasks {
#try {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *TASKS_URL = [userDefaults objectForKey:#"tasksUrl"];
NSObject *fetcher = [[NSObject alloc] init];
NSDictionary *response = [fetcher fetchAPICall:TASKS_URL httpRequestType:#"GET" requestBodyData:nil];
return [response objectForKey:#"data"];
} #catch (NSException *exception) {
NSLog(#"could not get tasks, error: %#", exception);
return nil;
}
}
#pragma mark - UITableView DataSource Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [tasksData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//<-- NEVER GETS HERE
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellId];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_cellId];
}
cell.textLabel.text = [tasksData objectAtIndex:indexPath.row];
return cell;
}
#end
I am also having a hard time figuring out what to set as the datasource. In other languages you would typically set the DataSource object with self.DataSource = [self getAllTasks]... however all the tuturials I have done thus far all tend to use some weird ad-hoc NSArray or NSDictionary to then correlate the index of the table functions with the index of the array or dictionary keys... This confuses me greatly as to why I can't just set the DataSource object and have the table know to iterate over it's data.
My conclusion is that this isn't firing because it thinks the DataSource object is empty and there are no rows? (which it is, but like I said people seem to get Tables to work fine on YouTube doing this)
Thanks.
TasksTableView class is derived from UITableView class & You are implementing the UITableview delegates in the same class. This will not work.
Instead of creating a UITableView subclass. Create TasksTableView class as NSObject sub class. And pass the tableview object from where you added a tableview.
#interface TasksTableView : NSObject <UITableViewDelegate, UITableViewDataSource> {
NSArray *tasksData;
__weak UITableView *tableView;
}
And set that table view delegate to self(TasksTableView object) while init the TasksTableView Class
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self) {
_cellId = #"AllTasksTableCell";
tasksData = [self getAllTasks];
self.tableView.delgate = self;
self.tableView.datasource = self;
}
return self;
}
Now your delegate methods will trigger for that specific tableview
I would like to ask some tips for a project I'm working on. I'm a Xcode beginner, so maybe is more easy than what I'm thinking.
So, the application I want to create shows a collection of data between two TableViews and it shows an image in a view controller at the end.
I've implemented a Property Lists to manage the data between the TableViews and the ViewController.
Now, here my problem, I would like to show (in the last ViewController) an HTML file (stored in my resource folder) rather than just an image. Can someone help me to write down the code for that? I've been able to write the code for the image so far, which is:
ViewController.h
#import <UIKit/UIKit.h>
#interface MethodsViewController : UIViewController
#property UIImage *bookCover;
#property IBOutlet UIImageView *bookCoverView;
#end
ViewController.m
#pragma mark -
#pragma mark View Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
if (self.bookCover) {
[self.bookCoverView setImage:self.bookCover];
}
}
#end
SecondTableView.m
prepare for segue
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Fetch Book Cover
NSDictionary *book = [self.books objectAtIndex:[indexPath row]];
self.bookCover = [UIImage imageNamed:[book objectForKey:#"Cover"]];
// Perform Segue
[self performSegueWithIdentifier:#"MethodsViewController" sender:self];
}
#end
Well, you need to deal with UIWebView for this. Add UIWebView property to your class:
#property (nonatomic, strong) UIWebView *aWebView;
Next, you need to represent content of your HTML file as an NSString, and it will be a content for the main page.
Lets just call this parameter as webText, in future we will load it into the web view, but first, - we need to get it.
To get webText parameter as an NSString, try the following code:
NSError* error = nil;
NSString *path = [[NSBundle mainBundle] pathForResource: #"nameOfYourHTMLfile" ofType: #"html"];
NSString *webText = [NSString stringWithContentsOfFile: path encoding:NSUTF8StringEncoding error: &error];
Set frame to aWebView in storyboard, then you can load HTML like this:
[self.aWebView loadHTMLString: webText baseURL: nil];
RecipesTableViewController.m
#import "RecipesTableViewController.h"
#import "RecipeTableViewCell.h"
#import "IngredientsViewController.h"
#import "Recipe.h"
#import "RecipeDetailViewController.h"
#interface RecipesTableViewController () {
NSMutableArray *recipesArray;
}
#end
#implementation RecipesTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
//api from recipepuppy
NSString *recipeUrlString = [NSString stringWithFormat:#"http://www.recipepuppy.com/api/?i=%#",self.searchRecipe];
//adding percentage on the textfield when the user is searching
NSString *formattedString = [recipeUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
//download data
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString: formattedString]];
//put data into a dictionary
NSDictionary *recipeDictinary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
//then put the dictionary into an array
recipesArray = [[NSMutableArray alloc]init];
for (NSDictionary *recipeDict in [recipeDictinary objectForKey:#"results"]) {
Recipe *recipe = [[Recipe alloc]initWithTitle:[recipeDict objectForKey:#"title"] andRecipeIngredients:[recipeDict objectForKey:#"ingredients"] andImageURL:[NSURL URLWithString:[recipeDict objectForKey:#"thumbnail"]] andRecipeWebUrl:[recipeDict objectForKey:#"href"]];
[recipesArray addObject:recipe];
NSLog(#"%#", recipeDict);
}
}
#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 [recipesArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"recipeCell" forIndexPath:indexPath];
[cell drawTheCell:[recipesArray objectAtIndex:indexPath.row]];
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"recipeDetail"]) {
//NSIndexPath *indexPath =[self.tableView indexPathForSelectedRow];
RecipeDetailViewController *recipeDetail = segue.destinationViewController;
recipeDetail.title = #"Recipe";
}
}
#end
Short Story:
I’m making a recipe by ingredient for my class.
I have a UITableViewControllre parsing content from an api and and I have the objects of the api in an array. In that array I have “results” and in those results I have urls, title, ingredients, and image of recipe. I want to send the url to a WebView into another view controller but I just can’t. Whenever I select the recipe the app crashes to view the webview. I been stuck on this for threes days and I’m so frustrated and I know the problem is my linking to the webview because the array prints the url but is not displayed on the webview.
this is my table view controller where my api is and the prepare for segue to the view controller where the webview is at.
RecipeTableViewCell.m
#import <UIKit/UIKit.h>
#import "Recipe.h"
#interface RecipeTableViewCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UILabel *recipeUrl;
#property (strong, nonatomic) IBOutlet UILabel *recipeTitle;
#property (strong, nonatomic) IBOutlet UILabel *recipeIngredients;
#property (strong, nonatomic) IBOutlet UIImageView *recipeImage;
-(void)drawTheCell:(Recipe *)recipeObject;
#end
RecipeTableViewCell.m
-(void)drawTheCell:(Recipe *)recipeObject {
self.recipeTitle.text = recipeObject.title;
self.recipeIngredients.text = recipeObject.ingredients;
self.recipeUrl.text = recipeObject.recipeWebUrl;
NSData *imageData = [NSData dataWithContentsOfURL:recipeObject.imageURL];
self.recipeImage.image = [UIImage imageWithData:imageData];
#import "RecipeDetailViewController.h"
#import "RecipeTableViewCell.h"
#interface RecipeDetailViewController ()
#property (strong, nonatomic) IBOutlet UIWebView *recipeWebView;
#end
RecipeDetailViewController.m
#implementation RecipeDetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
Recipe *recipe = [[Recipe alloc] init];
NSURL *url = [NSURL URLWithString: recipe.recipeWebUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.recipeWebView loadRequest:request];
}
RecipeDetailViewController.h
#import <UIKit/UIKit.h>
#interface RecipeDetailViewController : UIViewController
#property (nonatomic, strong ) NSString *recipeWebUrlString;
this is my cell and in here the title, ingredients, and image is displayed and it works fine.
Skyler's answer's heading in the right direction, but it's missing a few critical pieces...
Yes, you need to pass the web url string in prepareForSegue: like he's suggesting, i.e.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"recipeDetail"])
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
RecipeTableViewCell *cell = (RecipeTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
RecipeDetailViewController *recipeDetail = segue.destinationViewController;
recipeDetail.title = #"Recipe";
recipeDetail.recipeWebUrlString = cell.recipeUrl.text;
}
}
but the problem is that you're not using that recipeWebUrlString to perform your url request.
Instead, you're creating an empty Recipe object in your .m and thus using an empty url to perform the web request, i.e.
Recipe *recipe = [[Recipe alloc] init];
NSURL *url = [NSURL URLWithString: recipe.recipeWebUrl];
Instead replace those two lines (^) with the following:
NSURL *url = [NSURL URLWithString:self.recipeWebUrlString];
in order to use the url you just passed in from the RecipesTableViewController.
There is a long winded answer here but I will try to keep it short and hopefully it makes sense.
First I don't see where you are calling performSegueWithIdentifier: which means you are likely doing a segue from clicking the cell directly to the next view via storyboard. This is great for a simple button press but not ideal for selecting a cell that you need to send information from. I would recommend calling the segue in didSelectRowAtIndexPath:. The segue on the storyboard should go directly from one view controller to another and not directly from a table cell. Don't forget to set the identifier again.
Something like this to call the segue in code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"recipeDetail" sender:nil]; //you could also pass the cell with if you want
}
Second in your prepare for segue you are not setting the URL you want and just setting the title of the next view controller. Looks like you were close to what you wanted because I can see you were already looking at the index path but commented it out. You should grab the cell there and set the url to the recipeDetail. You could also pass the cell via sender.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"recipeDetail"])
{
NSIndexPath *indexPath =[self.tableView indexPathForSelectedRow];
RecipeTableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
RecipeDetailViewController *recipeDetail = segue.destinationViewController;
recipeDetail.title = #"Recipe";
recipeDetail.recipeWebUrlString = cell. recipeUrl.text;
}
}
Third thing if all else fails start putting NSLogs everywhere. You can log the URL in the view did load in the next view and see that it isn't getting set. The next thing you should have been looking at where you are setting it, which appears to be no where as far as I can see =)
Also with all of that being said I would not relay on the text on the cell but instead grab the recipe from your array and pass that in the segue.
I hope that helps or at least gets you pointed in the right direction.
I'm building an iPad app that will have a table view in one sector of the window with other labels, images, buttons, etc elsewhere. I know how to create a tableView when using a tableViewController as the class of the scene, but can't figure out how to populate the table cells when the table view is embedded in a scene with a UIViewController class.
I have updated my .h file as follows:
#interface SKMainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
I'm returning data from an asynchronous API call into the viewController, but can't figure out how to initiate the required tableView methods (tableView:numberOfRowsAtIndexPath and tableView:cellForRowAtIndexPath). How can I trigger them to run once I've got my data back? Is there a simple method call I don't know about?
I've dissected a functioning UITableViewController class and don't see what triggers the population of the table cells.
EDITED: Adding large sections of code for your review. I've trimmed out the other imports and property declarations to simplify.
This is SKMainViewController.h:
#import "SKWelcomeViewController.h"
#import "SKAgenda.h"
#import "SKAgendaManager.h"
#import "SKAgendaCommunicator.h"
#import "SKAgendaManagerDelegate.h"
#import "SKAgendaTableViewCell.h" // I'm using a custom cell
#interface SKMainViewController : UIViewController <SKFlipsideViewControllerDelegate, UIPopoverControllerDelegate, NSURLConnectionDelegate, SKGreetingManagerDelegate, SKWeatherManagerDelegate, UITableViewDataSource, UITableViewDelegate>
{
NSArray *_agenda;
SKAgendaManager *_aManager;
}
#pragma mark Agenda Detail
#property (strong, nonatomic) NSArray *agendaItems;
#property (strong, nonatomic) IBOutlet UITableView *agendaTableView;
#end
This is SKMainViewController.m:
#import "SKMainViewController.h"
#interface SKMainViewController ()
#end
#implementation SKMainViewController
- (void)viewDidLoad
{
[self startFetchingAgenda:_agendaItems];
}
-(void)startFetchingAgenda:(NSNotification *)notification
{
NSInteger deviceID = [[NSUserDefaults standardUserDefaults] integerForKey:#"deviceID"];
if(deviceID == 0)
{
// todo: add error handling here
NSLog(#"Bad stuff happened");
}
[_aManager fetchAgendaForDeviceID:deviceID];
}
-(void)didReceiveAgenda:(NSArray *)agendaItems
{
NSLog(#"Received these agenda items: %#", agendaItems);
_agendaItems = agendaItems;
if(agendaItems.count == 0)
{
// set defaults to populate a single cell
NSLog(#"No Items!");
}
else
{
// populate data and present
NSLog(#"Some Items!");
agendaTableView.delegate = self; // Error Here: use of undeclared identifier 'agendaTableView'
agendaTableView.dataSource = self; // Error Here: use of undeclared identifier 'agendaTableView'
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_agendaItems count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.agendaSpinner startAnimating];
self.agendaSpinner.hidden = NO;
static NSString *tableIdentifier = #"agendaTableCell";
SKAgendaTableViewCell *cell = (SKAgendaTableViewCell *)[tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if(cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"agendaTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// set the data related to the agenda item
SKAgenda *agendaDetails = [_agendaItems objectAtIndex:indexPath.row];
cell.agendaTitle.text = agendaDetails.title;
cell.agendaDescription.text = agendaDetails.description;
NSString *eventTimes = [NSString stringWithFormat:#"%# - %#", agendaDetails.start, agendaDetails.end];
cell.agendaTimes.text = eventTimes;
//todo: add functionality for background and text colors. Will need to use RGB colors instead of HEX. Change in webiste.
// todo: accommodate for no events in the agenda
[self.agendaSpinner stopAnimating];
self.agendaSpinner.hidden = YES;
return cell;
}
Thanks.
Just set the data source and delegate:
myTableView.delegate = self;
myTableView.dataSource = self;
Where self is your SKMainViewController that adopts the UITableViewDataSource and UITableViewDelegate protocols.
If at any time you'd like to reload your table data:
[myTableView reloadData];
Yes, the simple method is reloadData. This causes the table view to call its data source methods. You need to put it in a completion block (or delegate that's called after the data is received) if you're using an asynchronous api.
I have been following this tutorial on YouTube (part 1 and part 2).
I have completed both videos and have hooked up the view controller with the parent view controller using this code:
- (IBAction)searchButtonClicked:(id)sender {
NSLog(#"It works.");
SearchViewController *searchViewControl = [self.storyboard instantiateViewControllerWithIdentifier:#"SearchControllerNav"];
[self presentViewController:searchViewControl animated:YES completion:nil];
}
This code indeed works since this is the same format that I use for my other modal view controllers, so i know that's not the problem.
Anyway, when I tap on the search button in the view controller, it should pop up the SearchViewController. However, the app crashes instead and it gives me this error message:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "jp7-vt-IdA-view-Jer-xW-qlD" nib but didn't get a UITableView.'
I am using Storyboards for this app.
Is there something that I'm missing? Thank you in advance.
A side question: I'm also getting a warning, saying Comparison between pointer and integer ('BOOL *' (aka 'signed char *') and 'int') whenever isFiltered == YES is shown. Is there anyway to fix it?
Here is the code for SearchViewController:
SearchController.h
#import <UIKit/UIKit.h>
#interface SearchViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate> {
}
- (IBAction)cancelButtonTapped:(id)sender;
#property (weak, nonatomic) IBOutlet UISearchBar *mySearchBar;
#property (weak, nonatomic) IBOutlet UITableView *myTableView;
#property (nonatomic, strong) NSMutableArray *itemsInCloudApp;
#property (nonatomic, strong) NSMutableArray *filteredList;
#property BOOL *isFiltered;
#end
SearchViewController.m
#import "SearchViewController.h"
#interface SearchViewController ()
#end
#implementation SearchViewController
#synthesize mySearchBar, myTableView, itemsInCloudApp, filteredList, isFiltered;
- (void)viewDidLoad
{
[super viewDidLoad];
// Set title.
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
titleLabel.text = #"Search";
titleLabel.adjustsFontSizeToFitWidth = YES;
titleLabel.clipsToBounds = YES;
titleLabel.numberOfLines = 1;
titleLabel.font = [UIFont fontWithName:#"Avenir-Medium" size:18];
titleLabel.textColor = [UIColor blackColor];
titleLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight;
titleLabel.textAlignment = NSTextAlignmentCenter;
[titleLabel sizeToFit];
self.navigationItem.titleView = titleLabel;
// Alloc and init list.
itemsInCloudApp = [[NSMutableArray alloc]initWithObjects:#"http://www.apple.com/", #"http://www.trijstudios.com/", #"http://www.google.com/", #"http://www.squarespace.com/", #"http://www.youtube.com/", nil];
}
- (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.
if (isFiltered == YES) {
return [filteredList count];
} else {
return [itemsInCloudApp count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
if (isFiltered == YES) {
cell.textLabel.text = [filteredList objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [filteredList objectAtIndex:indexPath.row];;
} else {
cell.textLabel.text = [itemsInCloudApp objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [itemsInCloudApp objectAtIndex:indexPath.row];
}
return cell;
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchText.length == 0) {
// Set bollean flag
isFiltered = NO;
} else {
// Set boolean flag
isFiltered = YES;
// Alloc and init our fliteredData
filteredList = [[NSMutableArray alloc] init];
// Fast enumeration
for (NSString *name in itemsInCloudApp) {
NSRange nameRange = [name rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (nameRange.location != NSNotFound) {
[filteredList addObject:name];
}
}
}
// Reload tableView
[myTableView reloadData];
}
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[mySearchBar resignFirstResponder];
}
- (IBAction)cancelButtonTapped:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
NOTE: There are a few edits that I made to fit my needs.
have you tried changing your #interface SearchViewController : UITableViewController to #interface SearchViewController : UIViewController
I strongly suspect that either you have not attached your UITableview as View in XIB or your class should be derived UIViewController instead of UITableviewController class..
I had a similar error. I was able to resolve it using #Dinesh's suggestion, but I didn't like this because I was afraid that there could be some unintended consequences.
What I figured out was that when I looked at the scene hierarchy in the storyboard, I noticed that I had this structure (sorry, I don't know how to format this - it's supposed to be a tree structure):
View Controller
View
Table View
When I took out the View that sat in the middle, my problem went away. However, before doing so, you need to delete any outlets that might exist between the view and either the view controller or the table view. After you make sure that these are gone, follow these final steps:
Drag the Table View so that it is a direct descendant of the View Controller.
Delete the View
Command-Drag from the View Controller to the Table View, thereby creating a new outlet directly between the two.
Also, leave the .h file as a subclass of UITableView (not UIView).
Anyway, that solved the issue for me. If anyone comes across this, I hope that it helps.
For you side question regrading the warning, the warning comes because you have made the BOOL isFiltered as a pointer.
For your first problem, you needed to check the storyboard. I am sure that your file owner's view is connected to a UIView. To solve this, you must drag UITableView and view must be connected to the UITableView.
For your second problem, declare BOOL as
#property(assign,nonatomic) BOOL isFiltered;
I encountered this when building a iOS7 Universal app with a simple, dumb error: I'd built part of the iPhone app only, but had the scheme set to iPad simulator. After getting the error and looking here, I saw my mistake, switched the scheme to iPhone, and the app ran with the proper storyboard for the proper simulator. Hope that helps.