I am using SDWebImage to show image in UICollectionView. I am getting productImageUrl and productId as server response. Able to show the image in Custom-cell, now what I want is:
1) Display the image in large view with a UIButton(buyButton) on another UIViewController named ProductDetailViewController.(Image is showing on the ProductDetailViewController but the way i am passing image url from ProductCollectionViewController is not right I think, please review the code and suggest me some better way to do it )
2) On button click a call will be made to the server with the productId which I got earlier as Server Response.(How would I pass the dictId to ProductDetailViewController so that I can make a call to the server).
3) Getting only two key-value of an Object as response, so its ok to parse it in multiple dictionary for multiple value. But If the response contain multiple value, what will be the optimized way to parse the response.
Here is the code which i have tried.
(Sorry for long unoptimized code, still in learning phase)
ProductCollectionViewController.m
#import "ProductCollectionViewController.h"
#import "ProductCell.h"
#import "UIImageView+WebCache.h"
#import "ProductDetailViewController.h"
#interface ProductCollectionViewController ()
#property(strong, nonatomic) NSMutableArray *productList;
#end
#implementation ProductCollectionViewController
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
static NSString * const reuseIdentifier = #"Cell";
-(void)viewDidLoad
{
[super viewDidLoad];
[self getProductList];
}
-(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)getProductList
{
NSURL * url = [NSURL URLWithString:#"xxxx.yyyy.zzzz"];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject];
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
if (!error)
{
NSDictionary *responseJson = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSArray *rsBody = responseJson[#"rsBody"];
_productList = [NSMutableArray new];
for(NSDictionary *dict in rsBody)
{
NSMutableDictionary *dictUrl=[[NSMutableDictionary alloc]init];
NSMutableDictionary *dictProductId =[[NSMutableDictionary alloc]init];
[dictUrl setValue:[dict valueForKey:#"productImageUrl"] forKey:#"url"];
[dictId setValue:[dict valueForKey:#"productId"] forKey:#"id"];
[_productList addObject:dictUrl];
[_productList addObject:dictId];
}
NSLog(#"urls for image: %#",_productList );
[self.collectionView reloadData];
}}];
[dataTask resume];
}
#pragma mark <UICollectionViewDataSource>
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _productList.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ProductCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
NSURL *imageUrl = [[_productList objectAtIndex:indexPath.row]valueForKey:#"url"];
[cell.productImageView sd_setImageWithURL:imageUrl placeholderImage:[UIImage imageNamed:#"placeholder.jpg"]];
NSString *id =[[_productList objectAtIndex:indexPath.row] valueForKey:#"id"];
cell.productPrice.text= id;
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showProduct"]) {
NSArray *indexPaths = [self.collectionView indexPathsForSelectedItems];
ProductDetailViewController *destViewController = segue.destinationViewController;
NSIndexPath *indexPath = [indexPaths objectAtIndex:0]
destViewController.productName =[[_productList objectAtIndex:indexPath.row]valueForKey:#"url"];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
}
}
#end
ProductDetailViewController.h
`#import <UIKit/UIKit.h>
#interface ProductDetailViewController : UIViewController
- (IBAction)buyButton:(id)sender;
- (IBAction)closeButton:(id)sender;
#property (weak, nonatomic) IBOutlet UIImageView *productImage;
#property (weak, nonatomic) NSString *productName;
#end`
ProductDetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.productImage.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.productName]]];
//code to get productId
}
- (IBAction)buyButton:(id)sender {
//code to make server call with productId.
}
Server Response Format in JSON
{"rsBody":
[{"productId":11,
"productImageUrl":"http:xxxx"},
{"productId":9,
"productImageUrl":"http:"xxxx"}]}
For your first question, this line
self.productImage.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.productName]]];
is blocking the main thread. Meaning the app will go to the server download the entire image before updating the screen or allowing interactions, which is bad.
NSURL *url = [[NSURL alloc]initWithString:self.productName];
dispatch_queue_t imageFetchQ = dispatch_queue_create("image fetcher", NULL);
dispatch_async(imageFetchQ, ^{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
self.productImage.image=image;
}
});
});
Try the block above instead. It will fetch the product image on a different thread.
Question two: Two transfer data between view controllers do what you're doing in prepareForSegue setup the public properties of the destination view controller.
Question three: Optimum way is to create an NSObject class that you read the data from the dictionary into properties on that class through some method called like setupFromDictionary.
Here you would have an Object called product with a productID property and productImageURL property. That way you're not constantly calling valueForKey or objectForKey on some dictionary.
Related
I want to avoid tableview/collectionview cells with duplicate values.
Here is my code :
NSURL *imageURL = [NSURL URLWithString:[finalImage objectAtIndex:indexPath.row]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, strong) NSArray *imagesArray; //Array of URL for images
#property (nonatomic, strong) NSMutableDictionary *dataDictionary; // Using to store downloaded data
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//Some images for test
self.imagesArray = #[#"https://cdn.pixabay.com/photo/2017/05/31/18/38/sea-2361247__480.jpg", #"https://cdn.pixabay.com/photo/2017/06/02/11/49/still-life-2366084__480.jpg", #"https://cdn.pixabay.com/photo/2017/06/04/20/31/sheep-2372148__480.jpg", #"https://cdn.pixabay.com/photo/2017/06/04/15/08/architecture-2371294__480.jpg", #"https://cdn.pixabay.com/photo/2017/05/18/21/54/tower-bridge-2324875__480.jpg", #"https://cdn.pixabay.com/photo/2017/05/16/21/24/gorilla-2318998__480.jpg", #"https://cdn.pixabay.com/photo/2017/05/24/11/40/desert-2340326__480.jpg", #"https://cdn.pixabay.com/photo/2017/05/21/15/14/balloon-2331488__480.jpg", #"https://cdn.pixabay.com/photo/2017/05/19/15/16/countryside-2326787__480.jpg", #"https://cdn.pixabay.com/photo/2017/04/09/09/56/avenue-2215317__480.jpg"];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"cell"];
self.dataDictionary = [NSMutableDictionary new];
}
- (void)asyncImageForIndexPath: (NSIndexPath *)indexPath onCompletion: (void(^)(UIImage *image))completionBlock {
NSURL *imageURL = [NSURL URLWithString:[self.imagesArray objectAtIndex:indexPath.row]];
if ([self.dataDictionary objectForKey:[NSString stringWithFormat:#"%ld", (long)indexPath.row]]) {
completionBlock([UIImage imageWithData:[self.dataDictionary objectForKey:[NSString stringWithFormat:#"%ld", (long)indexPath.row]]]);
}
//Sending async request not to block main thread and storing it into the dictionary
//After storing, reloading table view on main thread. So the function is called again, but as we have stored NSData for image, method won't send request
[[[NSURLSession sharedSession] dataTaskWithURL:imageURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error) {
[self.dataDictionary setObject:data forKey:[NSString stringWithFormat:#"%ld", indexPath.row]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}] resume];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 250;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.imagesArray.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
//Downloading image separated into another method
[self asyncImageForIndexPath:indexPath onCompletion:^(UIImage *image) {
cell.imageView.image = image;
}];
return cell;
}
Try out the code, and let me know if that helps. If you have any questions, feel free to ask
enter image description here .Im new to IOS. I am making an app where I want to get data from there in UITableView.
I have seen many blogs and post related to getting data in custom style, but I don't get my answer. I want to show an image in UIImageView and some labels values in label from service. Im using built in service to get data.
There are many post related to static data loading on custom. Can anyone guide how can I load data in my own custom style UItable VIEW FROM SERVICE?
Somewhat I can understand your question.My answer is here
FindHomeViewController.m
#import "FindHomeViewController.h"
#import "DataTableViewController.h"
#interface FindHomeViewController ()
#end
#implementation FindHomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)Search:(id)sender {
//Getting response from server
NSDictionary *parameters = #{
#"country": #"UAE",
#"city": #"Dubai",
#"propertytype": #"Office",
#"propertystatus": #"Available",
#"propertyarea" : #"Kanal",
#"minprice" : #"800",
#"maxprice" : #"900"
};
NSData *data = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.pk.house/app_webservices/get_properties.php"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json;charset=UTF-8" forHTTPHeaderField:#"content-type"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *dataTask = [session uploadTaskWithRequest: request
fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(data != nil)
{
NSError *parseError = nil;
//If the response is in dictionary format
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
NSArray *arr=[dictionary valueForKey:#"property_data"];
NSLog(#"arr:%#",arr);
//Updating UIMain Thread
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
DataTableViewController *vc = [sb instantiateViewControllerWithIdentifier:#"DataTableViewController"];
vc.arrResprev = [arr mutableCopy];
[self.navigationController pushViewController:vc animated:YES];
});
}
else
NSLog(#"Data returned the parameter is nil here");
}];
[dataTask resume];
}
See my Custom Cell Image View
CustomeCell.h
#import <UIKit/UIKit.h>
#interface CustomCell : UITableViewCell
#property (nonatomic,strong) IBOutlet UILabel *nameLabel;
#property (nonatomic,strong) IBOutlet UILabel *priceLabel;
#property (nonatomic,strong) IBOutlet UILabel *locationLabel;
#property (nonatomic,strong) IBOutlet UIImageView *imgvwRes;
#end
CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
DataTableViewController.h
#import <UIKit/UIKit.h>
#interface DataTableViewController : UIViewController<UITableViewDelegate,UITableViewDataSource>
#property (strong, nonatomic) IBOutlet UITableView *tvCustomers;
#property (strong, nonatomic) NSMutableArray *listOfCustomers;
#property (strong, nonatomic) NSMutableArray *arrResprev;
#end
DataTableViewController.m
#import "DataTableViewController.h"
#import "CustomCell.h"
#interface DataTableViewController ()
#end
#implementation DataTableViewController
#synthesize tvCustomers,arrResprev,listOfCustomers;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
listOfCustomers = [[NSMutableArray alloc]init];
listOfCustomers = arrResprev;
[tvCustomers reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return listOfCustomers.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 134;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = (CustomCell *)[tvCustomers dequeueReusableCellWithIdentifier:#"cell"];
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
if(cell == nil){
cell = nib[0];
}
cell.nameLabel.text = [NSString stringWithFormat:#"%#",[[listOfCustomers objectAtIndex:indexPath.row]objectForKey:#"dealer_name"]];
cell.priceLabel.text = [NSString stringWithFormat:#"%#",[[listOfCustomers objectAtIndex:indexPath.row]objectForKey:#"price"]];
cell.locationLabel.text = [NSString stringWithFormat:#"%#",[[listOfCustomers objectAtIndex:indexPath.row]objectForKey:#"location"]];
NSString *strImgURL = [NSString stringWithFormat:#"%#",[[listOfCustomers objectAtIndex:indexPath.row]objectForKey:#"images"]];
NSError* error = nil;
NSURL *fileURL = [NSURL fileURLWithPath:strImgURL];
NSData* data = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingUncached error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
} else {
NSLog(#"Data has loaded successfully.");
}
UIImage *img = [[UIImage alloc] initWithData:data];
cell.imgvwRes.image = omg;
return cell;
}
For this you need to follow both Appcoda and mikesknowledgebase tutorials .
One shows how to customize UITableViewCell and other shows how to populate UITableView with data fetched from server. You will have to do it in steps.
First, design the Custom UITableViewCell.
Then, follow Mike's tutorial to learn how to set data on cell from API call.
You will use NSURLSession to make API calls.
Follow Link to learn how to make an API call.
Go through these links.
We can only help you in debugging where little amount of code will work. But cannot post code for the complete functionality.
Hope these links will help you.
I have to download file from server on respective button click in UITableViewCell.When user taps Button to download in one cell download should be started.After completion of downloading I'm saving in core data.Up to this All went well.But while downloading current file if user taps to download another file in Cell it also should download then save to core data and make available to play.And i have different url's in each table cell.If user taps multiple buttons should download them and save to core data.Here my code.
NSString *url=[[chatHistoryArr objectAtIndex:sender.tag]valueForKey:#"voice"];
NSLog(#"%#",url);
//NSURL *voiceUrl=[NSURL URLWithString:url];
tempDict=[[NSMutableDictionary alloc]init];
[tempDict setValue:[[chatHistoryArr objectAtIndex:sender.tag]valueForKey:#"msg_id"] forKey:#"msg_id"];
[tempDict setValue:[[chatHistoryArr objectAtIndex:sender.tag]valueForKey:#"to"] forKey:#"to"];
[tempDict setValue:[[chatHistoryArr objectAtIndex:sender.tag]valueForKey:#"from"] forKey:#"from"];
[tempDict setValue:[[chatHistoryArr objectAtIndex:sender.tag]valueForKey:#"time"] forKey:#"time"];
UIImageView* animatedImageView = [[UIImageView alloc] initWithFrame:cell.playButton.bounds];
animatedImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],
[UIImage imageNamed:#"2.png"],
[UIImage imageNamed:#"3.png"],
[UIImage imageNamed:#"4.png"], nil];
animatedImageView.animationDuration = 3.0f;
animatedImageView.animationRepeatCount = 10;
[animatedImageView startAnimating];
[cell1.playButton addSubview: animatedImageView];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
[manager setResponseSerializer:[AFHTTPResponseSerializer serializer]];
// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/octet-stream",#"video/3gpp",#"audio/mp4",nil];
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error)
{
NSLog(#"Error: %#", error);
} else
{
NSData *data=[[NSData alloc]initWithData:responseObject];
//Here I'm saving file to local storage then updating UI.
[self sentMsgSaveWithData:data orUrl:#"" withBool:YES withMsg_ID:#"" withDict:tempDict];
}
}];
[dataTask resume];
Here I managed to download only one file at a time and after completion of that if user taps another cell then only I'm downloading it.But I have to download multiple files on multiple button taps in Cell.
I have been struggling lot to implement this.Please give some suggestions.
Thanks in advance.
In MVC patterns, cell is a view and should not handle data parse and downloading things. It's better to do it in your model. But for simple it is often put in controller.
holding your model arrays in controller and pass data to cell
configure your cell's button action to controller (delegate or block or notification ...)
put downloading code in your controller and update your model status and reload tableView after completion.
cell.h
#import <UIKit/UIKit.h>
#import "YourCellProtocol.h"
typedef NS_ENUM(NSInteger, YourCellStatus) {
YourCellStatusNormal,
YourCellStatusDownloading,
YourCellStatusCompleted
};
#interface YourCell : UITableViewCell
#property (nonatomic, weak) id<YourCellProtocol> delegate;
#property (nonatomic, assign) YourCellStatus status;
#property (nonatomic, weak) id yourDataUsedToShownInUI;
#end
cell.m
#import "YourCell.h"
#interface YourCell ()
#property (nonatomic, strong) UIButton *myButton;
#end
#implementation YourCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// init all your buttons etc
_myButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_myButton addTarget:self action:#selector(myButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:_myButton];
}
return self;
}
- (void)setStatus:(YourCellStatus)status {
//update your cell UI here
}
- (void)myButtonPressed {
// tell your controller to start downloading
if (self.status != YourCellStatusNormal) {
[self.delegate didPressedButtonInYourCell:self];
}
}
#end
controller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellID = #"YourCellID";
YourCell *cell = (YourCell *)[tableView dequeueReusableCellWithIdentifier:CellID];
if (cell == nil) {
cell = [[YourCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellID];
}
cell.delegate = self;
YourModel *model = self.yourDataArray[indexPath.row];
cell.yourDataUsedToShownInUI = model.dataToShownInUI;
if (model.downloading) {
cell.status = YourCellStatusDownloading;
} else if (model.completed) {
cell.status = YourCellStatusCompleted;
} else {
cell.status = YourCellStatusNormal;
}
//other configs ...
return cell;
}
- (void)didPressedButtonInYourCell:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
YourModel *model = self.yourDataArray[indexPath.row];
model.downloading = YES;
//start downloading
//...
// in download completion handler, update your model status, and call
//[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I am very new at Objective C and Xcode but I am learning a little bit every day!:)
I am trying to build an application that will show a webb api in my tableview, but it does not show.. when I NSLog it, it shows that my search worked and that it got the data i was looking for but it will not show in my tableView unfortunately..
If someone as time to look at the code and try to figure out what is wrong that would be great, or if someone hade a similar problem, just throw it out there so i can check if i did the same thing:)
Best regards. Filip
(Sorry for my bad english, I`m from Sweden and in a hurry..)
- my .m file
#import "FoodTableViewController.h"
#interface FoodTableViewController ()
#property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
#property(nonatomic)NSMutableArray *foodNames;
#end
#implementation FoodTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.foodNames = [#[]mutableCopy];
self.searchBar.delegate = self;
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchbar
{
NSString *urlString = [NSString stringWithFormat:#"http://matapi.se/foodstuff?query=%#",self.searchBar.text];
NSURL *URL = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *parseError;
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&parseError];
dispatch_async(dispatch_get_main_queue(),^{
for(int i=0;i<json.count;i++){
NSString *foodName = json[i][#"name"];
[self.foodNames addObject:foodName];
NSLog(#"Added: %#",foodName);
NSLog(#"FOODLIST LENGTH: %d",self.foodNames.count);
}
});
}];
[task resume];
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
// Return the number of rows in the section.
return self.foodNames.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
if(self.foodNames[indexPath.row]){
NSLog(#"Cell text %#",self.foodNames[indexPath.row]);
cell.textLabel.text = self.foodNames[indexPath.row];
}else{
cell.textLabel.text = #"Loading..";
}
return cell;
}
#end
- my .h file
#import <UIKit/UIKit.h>
#interface FoodTableViewController : UITableViewController<UISearchBarDelegate>
#end
dataTaskWithRequest:... is an asynchronous method, which means that the results are not available at the time of your reloadData call. You'll need to add that call after all your data is added to the array. In part...
for(int i=0;i<json.count;i++){
NSString *foodName = json[i][#"name"];
[self.foodNames addObject:foodName];
NSLog(#"Added: %#",foodName);
NSLog(#"FOODLIST LENGTH: %d",self.foodNames.count);
}
[self.tableView reloadData];
I want to make a application which will display images into UICollectionView.
Images will be downloaded from server and then shows into collectionView.
I am using custom collectionView layout into xib file.
At a time, 20 images is receiving from server.
Problem: I can't show newly downloaded images into collectionView.
Here is my code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
BOOL reloaded = NO;
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
dispatch_queue_t queue = dispatch_queue_create("com.justTest.anotherSingleApplication", NULL);
dispatch_async(queue, ^{
//code to be executed in the background
NSString *imageName1 = [[NSString alloc]initWithFormat:#"http://www.abc.com/images/thumb/%#", cellData];
NSString *url_Img1 = imageName1;
UIImage *aImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_Img1]]];
dispatch_async(dispatch_get_main_queue(), ^{
//code to be executed on the main thread when background task is finished
[cell.cellImage setImage:aImage];
});
});
if (indexPath.row == self.imageArray.count - 1 && !reloaded) {
getOnScrollImages *getImage = [[getOnScrollImages alloc] init]; // class to get image name from server
NSMutableArray *astring = (NSMutableArray *)[getImage getImageNameFromServer:#"list" board:#"111" pin:#"122345"]; // method to get image name from server
[self setNewTestArray:astring]; //adding newly downloaded image name into array
reloaded = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
return cell;
}
Any suggestion please?
NOTE: I am just starting developing iOS application, this may be a very silly question.
Use asynchronously fetch to get data from server and display it in collectionView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourDataModel *model = self.dataArray[indexPath.row];
YourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if ([self checkWhetherImageAlreadyExist]) {
[cell.imageView setImage:model.image];
} else {
//show placeholder to avoid nothing in your UI, or your user gets confused
[cell.imageView setImage:placeholderImage];
[self startDownloadImageForIndexPath:indexPath];
}
}
- (void)startDownloadImageForIndexPath:(NSIndexPath *)indexPath
{
//YourImageDownloader is a class to fetch data from server
//imageDownloadsInProgress is a NSMutableDictionary to record the download process, which can avoid repeat download
YourImageDownloader *downloader = [self.imageDownloadsInProgress objectForKey:indexPath];
if (downloader == nil) {
YourDataModel *model = self.dataArray[indexPath.row];
//configure downloader
downloader = [[YourImageDownloader alloc] init];
[downloader setURL:model.url];
[downloader setCompletionHandler:^{
//download the image to local, or you can pass the image to the block
model.image = [UIImage imageWithContentsOfFile:model.localPath];
YourCell *cell = [self.mCollectionView cellForItemAtIndexPath:indexPath];
[cell.imageView setImage:model.image];
//remove downloader from dictionary
[self.imageDownloadsInProgress removeObjectForKey:indexPath];
}];
//add downloader to dictionary
[self.imageDownloadsInProgress setObject:downloader forKey:indexPath];
//start download
[downloader startDownload];
}
}
Use a class to download the image. If you have many images in one collection view, you may consider to save these images to local in case of memory warning. if now many, just leave the image in memory and display it in your collection view.
the code followed is save the image to local and read image data from local when displaying.
in .h:
#import <Foundation/Foundation.h>
#interface PortraitDownloader : NSObject
#property (nonatomic, copy) NSString *portraitName;
#property (nonatomic, copy) void (^completionHandler)(void);
- (void)startDownload;
- (void)cancelDownload;
#end
in .m
#import "PortraitDownloader.h"
#import <CFNetwork/CFNetwork.h>
#import "NSString+ImagePath.h" // it's a category to get the image local path
#interface PortraitDownloader ()
#property (nonatomic, strong) NSMutableData *activeDownload;
#property (nonatomic, strong) NSURLConnection *portraitConnection;
#end
#implementation PortraitDownloader
- (void)startDownload
{
self.activeDownload = [NSMutableData data];
NSString *urlstr = [NSString serverPortraitPathWithPortrait:self.portraitName];
NSURL *url = [NSURL URLWithString:urlstr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.portraitConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)cancelDownload
{
[self.portraitConnection cancel];
self.portraitConnection = nil;
self.activeDownload = nil;
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.activeDownload appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// Clear the activeDownload property to allow later attempts
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// save to local path
NSString *localSavePath = [NSString localPortraitPathWithPortrait:self.portraitName];
[self.activeDownload writeToFile:localSavePath atomically:YES];
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
// call our delegate and tell it that our icon is ready for display
if (self.completionHandler) {
self.completionHandler();
}
}
#end
if you want to leave your image in-memory, just modify the completion block as:
in .h
typedef void (^Completion_handle) (UIImage *image);
#interface PortraitDownloader : NSObject
#property (nonatomic, copy) Completion_handle myCompletionBlock;
and in .m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// get image from data
UIImage *image = [UIImage imageWithData:self.activeDownload];
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
// call our delegate and tell it that our icon is ready for display
if (self.myCompletionBlock) {
self.myCompletionBlock(image);
}
}
and also modify methods startDownloadImageForIndexPath, save the image to your model to retain it
This method expects to have answers immediately:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
when your code doesn't respond fast enough to it, the app will usually display nothing, or sometimes just crash (depending on what you've setup)
A common design pattern is to store the info that will be supplied to the collectionView in a class variable (it doesn't have to be a property, but it often times is). You always store SOMETHING in that variable, even if it is old or stale data.
Then you have the methods defined in the UICollectionViewDataSource protocol pull what they need directly from the class variables, with no delay.
Other methods can fetch and retrieve and sling updated data around, and once they finish you call reloadData: on the collectionView to update the interface.
assuming the asynchronous calls you are using are successfully retrieving data eventually, they are probably too slow for what the UICollectionViewDataSource protocol methods are expecting.
A suggestion for how to get started would be to move the code fetching your data to separate methods, and then stage the data in a class variable or two which the collectionView can reliably draw from.
You can try it with static data loaded into the bundle at first if you need, and then move into asynchronous pulls from the web too.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
UIImageView *imgView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"profile_pic.png"]];
NSMutableDictionary *contactData=[NSMutableDictionary new];
contactData = [self.collectionData objectAtIndex:indexPath.row];
imgView.image=[contactData objectForKey:#"image"];
[cell addSubview:imgView];
return cell;
}