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];
Related
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 have this simple code in the didSelectRowAtIndexPath method:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
NSString *selected = #"test";
NSLog(#"You choose: %#", selected);
}
This is my ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
IBOutlet UITableView *tableData;
}
#end
When i run the App, the TableView display all data, but then i cliked in a cell the method above doesn't run (?)
Have you set the delegate for the tableview in your storyboard, select your table view right click and drag to the view controller and you should see the option to set dataSource delegate and tableviewdelegate - I forget the exact names.
e.g.: thats part of my code, but should show you what you need to do. You need to hand over the data somewhere.
NSDictionary *oneDict = [_noteBookArray objectAtIndex:indexPath.row];
NSString *touchString = [oneDict objectForKey:#"value"];
NSString *dateString = [oneDict objectForKey:#"timestamp"];
//call the method who does it
[_noteBookViewController setContentAndTimeStampWith:touchString and:dateString];
//set the present View Controller active (the controller who contains the values)
[self presentViewController:_noteBookViewController animated:YES completion:nil];
method from the notebookviewcontroller
- (void)setContentAndTimeStampWith:(NSString *)contentString and:(NSString *)timeStampString
{_textInputView.text = contentString;
_timeStampString = timeStampString;}
got it?
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;
}
I have a UITableView with some names in it. I have built my app from the MasterViewController template that Apple provides. I'm trying to store the name of the selected cell in a NSString and then access it in the other class that handles the new ViewController that appears when the cell is tapped. In there I use that string as the title of the view.
In MasterViewController.h
#property (nonatomic, retain) NSString *theTitle;
In MasterViewController.m
#synthesize theTitle;
- (void)tableView: (UITableView*)tableview didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath animated:YES];
theTitle = cell.textLabel.text;
}
In the new ViewController.m
#import "MasterViewController.m"
- (void)viewDidLoad
{
MasterViewController* MasterViewControllerAccess = [[MasterViewController alloc] init];
self.title = MasterViewControllerAccess.theTitle;
NSLog("%#", [NSString stringWithFormat:#"%#", MasterViewControllerAccess.theTitle]);
}
The new ViewController is linked to the cell in the IB. When I press the cell theTitle returns NULL. But if I log it directly in the didSelectRowAtIndexPath:method it returns the real names. This means that something wrong occurs between the different classes. What's wrong?
You are instantiating a new instance of MasterViewController, instead you need to access the MasterViewController instance that already exists. Consider following Apple's example of setting the detail item (ie from master to detail). I can't see any reason to set it the way you are doing it. In any case, if you are using a navigation controller:
#import "MasterViewController.h" // don't import .m files. Always import .h files
- (void)viewDidLoad
{
MasterViewController* MasterViewControllerAccess = (MasterViewController*)self.navigationController.viewControllers[0]
self.title = MasterViewControllerAccess.theTitle;
NSLog("%#", [NSString stringWithFormat:#"%#", MasterViewControllerAccess.theTitle]);
}
In viewDidLoad of the pushed view controller, you allocate a new instance
of MasterViewController, which is completely different and unrelated to the
existing master view controller (which has been loaded from the storyboard or nib file).
Therefore MasterViewControllerAccess.theTitle is nil.
As said in the above comments, it is usually easier to pass the information the other way
around (from master to detail view controller), e.g. in prepareForSegue as in
the template application.
I searched extensively for delegation tutorials, but could not get it to work. Here is the story and the code.
DetailViewController has a UITextField and a UIButton. When you press the button you get to another PricelistViewController with a simple sectioned table. Tapping a row in that table should return the text from the row's title to the first view and insert it into the text field. But it doesn't. Here is the code:
PricelistViewController.h (the second view):
#class PricelistViewController;
#protocol PricelistViewControllerDelegate <NSObject>
#required
//- (void)theSaveButtonOnThePriceListWasTapped:(PricelistViewController *)controller didUpdateValue:(NSString *)value;
- (void)theSaveButtonOnThePriceListWasTapped:(NSString *)value;
#end
#interface PricelistViewController : UITableViewController
#property (nonatomic, weak) id <PricelistViewControllerDelegate> delegate;
#property (retain, nonatomic) NSMutableArray * listOfSections;
#end
PricelistViewController.m
#synthesize delegate;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dictionary = [listOfSections objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"ProductSections"];
NSString *selectedProduct = [array objectAtIndex:indexPath.row];
[self.delegate theSaveButtonOnThePriceListWasTapped:[NSString stringWithFormat:#"%#", selectedProduct]];
[self.navigationController popViewControllerAnimated:YES];
}
This is the code in DetailViewController.h (the first view with a text field and the button):
#import "PricelistViewController.h"
#interface DetailViewController : UIViewController <UITextFieldDelegate, PricelistViewControllerDelegate>
DetailViewController.m (this I how I am trying to change the text in the field):
- (void)theSaveButtonOnThePriceListWasTapped:(NSString *)value
{
NSLog(#"Text, sent here: %#", value);
NSLog(#"Text was sent here.");
self.detailDescriptionLabel.text = value;
}
detailDescriptionLabel is the UITextField for the text to display.
Can somebody check the code and help? I work on this matter two days with no luck!
Firstly why are you forward referencing (#class) your class in the header file of the PriceListViewController? This isn't needed.
Secondly are you using ARC, if you are your array property should be of type nonatomic, strong. If you're not your delegate property should be nonatomic, assign. You seem to be mixing your terminology
Also where and how are you initialising your array?
I figured it out.
In the PricelistViewController.m I changed the way I call the method (added If statement):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dictionary = [listOfSections objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"ProductSections"];
NSString *selectedProduct = [array objectAtIndex:indexPath.row];
if (delegatePrice && [delegatePrice respondsToSelector:#selector(theSaveButtonOnThePriceListWasTapped:didUpdateValue:)])
{
[delegatePrice theSaveButtonOnThePriceListWasTapped:self didUpdateValue:selectedProduct];
}
AND THE MOST IMPORTANT: I refused to use the Storyboard and instead made a old good .xib file for my PricelistViewController view and it worked right away!