what im trying to do is parse an XML data and show it in my UITableView but the thing is it shows like after 2-3 seconds, im trying to include a UIActivityIndicator when the data loads and also im trying to include a gcd, but the thing is im new at this stuff so im really confuse on what to do or where do i posible put the gcd code.
that is my .m file.
#implementation ViewController
#synthesize listTableView;
dispatch_queue_t myQueue;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[xmlParser listPopulated]count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"CustomCell";
dataFileHolder *currentData = [[xmlParser listPopulated] objectAtIndex:indexPath.row];
CustomCellXMLClass *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[CustomCellXMLClass alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellXMLSample" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSString *nameLabel = [currentData nameOfCat];
NSString *dataToCacheLabel = [myCache objectForKey:nameLabel];
if(nameLabel != nil){
dataToCacheLabel = [NSString stringWithString:nameLabel];
if (dataToCacheLabel != nil) {
[myCache setObject:dataToCacheLabel forKey:nameLabel];
[cell.nameLabel setText:dataToCacheLabel];
}
}
NSString *detailLabel = [currentData descriptionOfCat];
NSString *stringToCache = [myCache objectForKey:detailLabel];
if (detailLabel != nil) {
stringToCache = [NSString stringWithString:detailLabel];
if (stringToCache != nil) {
[myCache setObject:stringToCache forKey:detailLabel];
[cell.detailLabel setText:stringToCache];
}
}
NSString *imageURL = [currentData imageLink];
NSData *dataToCache;
if (imageURL != nil) {
dataToCache = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
if (dataToCache != nil) {
[myCache setObject:dataToCache forKey:imageURL];
[cell.imageShow setImage:[UIImage imageWithData:dataToCache]];
}
else {
NSURL *imageURL = [NSURL URLWithString:#"http://i178.photobucket.com/albums/w255/ace003_album/190579604m.jpg"];
dataToCache = [NSData dataWithContentsOfURL:imageURL];
[myCache setObject:dataToCache forKey:imageURL];
[cell.imageShow setImage:[UIImage imageWithData:dataToCache]];
}
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 78;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
NSString *title = #"Sample View";
return title;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.activityIndicator startAnimating];
self.activityIndicator.hidesWhenStopped = YES;
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(myQueue, ^ {
xmlParser = [[XMLParser alloc]loadXMLByURL:#"http://www.irabwah.com/mobile/core.php?cat=0"];
[self.activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
});
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Depending on how XMLParser works your current CGD block is ok for that. You wouldn't usually use performSelectorOnMainThread to return the UI interaction to the main thread but that's just a common convention thing.
Most of your problem could be coming from dataWithContentsOfURL: which you're calling on the main thread in cellForRowAtIndexPath:. You might want to take a look at SDWebImage to help with that, but you could do it yourself with GCD too.
cellForRowAtIndexPath will not be called just because your XML finished loading. In your code block, I suggest before calling stopAnimating that you call reloadData on your UITableView; this will trigger a series of calls, first to numberOfRowsInSection and then to cellForRowAtIndexPath repeatedly. Try putting breakpoints in these functions so you can see the flow of execution.
Related
I had a problem with tableview which is when the table first shows everything looks great but if I scroll up and and down, NSLog(#"page name : %#", cell.pageName.text); output value will be duplicate and it will cause the scrolling to be lagged. This is my code :
#import "HomeTVC.h"
#import "facebook.h"
#import "HomeTVCell.h"
#import <SDWebImage/UIImageView+WebCache.h>
#interface HomeTVC ()<UITableViewDataSource, UITableViewDelegate>
{
NSDictionary *userPageLikesParams;
NSArray *pagesInfo;
}
#end
#implementation HomeTVC
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate = self;
self.tableView.dataSource = self;
facebook *fb = [[facebook alloc] init];
userPageLikesParams = #{#"fields": #"about,name,created_time,picture",#"limit": #"10"} ;
[fb getUserPagelikes:userPageLikesParams completionHandler:^(NSDictionary *pagesResult) {
if (pagesResult != nil) {
pagesInfo = pagesResult[#"data"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"table count : %d", (int)pagesInfo.count);
if (pagesInfo == nil) {
return 0;
} else {
return pagesInfo.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellIdentifier";
HomeTVCell *cell = (HomeTVCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[HomeTVCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSURL *imageURL = [NSURL URLWithString:[pagesInfo[indexPath.row] valueForKeyPath:#"picture.data.url"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
// UIImage *pageProfileImage = [UIImage imageWithData:imageData];
// NSLog(#"pages info : %#", pagesInfo[indexPath.row]);
// NSLog(#"pages info image URL : %#", imageURL);
// cache the image using sdwebimage
cell.pageProfilePic.layer.backgroundColor=[[UIColor clearColor] CGColor];
cell.pageProfilePic.layer.borderWidth= 2.0;
cell.pageProfilePic.layer.masksToBounds = YES;
cell.pageProfilePic.layer.borderColor=[[UIColor whiteColor] CGColor];
cell.pageProfilePic.layer.cornerRadius= 30.0;
[cell.pageProfilePic sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:#"placeholder.jpg"]];
//cell.pageProfilePic.image = pageProfileImage;
cell.pageName.text = pagesInfo[indexPath.row][#"name"];
NSLog(#"page name : %#", cell.pageName.text);
return cell;
}
Thanks in advance.
I integrate pubnative library in my project , but it load only one app in my table view and i don't want tihis. I want to populate my table view with all partener apps that i have. When i lunch the app date are diffrent every time .
-(void)viewDidLoad
{
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
PNAdRequestParameters *parameters = [PNAdRequestParameters requestParameters];
[parameters fillWithDefaults];
parameters.ad_count = #5;
parameters.app_token = #"My app token";
parameters.icon_size=#"400x400";
self.request = [PNAdRequest request:PNAdRequest_Native
withParameters:parameters
andCompletion:^(NSArray *ads, NSError *error)
{
if(error)
{
NSLog(#"Pubnative - Request error: %#", error);
}
else
{
NSLog(#"Pubnative - Request end");
weakSelf.ads = [[NSMutableArray alloc] initWithArray:ads];
weakSelf.model = [ads firstObject];
[self.tableView reloadData];
}
}];
[self.request startRequest];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[PNTableViewManager controlTable:self.tableView];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[PNTableViewManager controlTable:nil];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *result = nil;
PNBannerTableViewCell *bannerCell = [tableView dequeueReusableCellWithIdentifier:bannerCellID];
if(!bannerCell)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"PNBannerTableViewCell"
owner:self options:nil];
bannerCell = [topLevelObjects objectAtIndex:0];
}
bannerCell.model = self.model;
result = bannerCell;
return result;
}
You should then assign a different ad each time
- (UITableViewCell *)tableView:(UITableView *)tableView cell ForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *result = nil;
PNBannerTableViewCell *bannerCell = [tableView dequeueReusableCellWithIdentifier:bannerCellID];
if(!bannerCell)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"PNBannerTableViewCell"
owner:self options:nil];
bannerCell = [topLevelObjects objectAtIndex:0];
}
// We assign an add to the table
bannerCell.model = self.ads[indexpath.row % [self.ads count]];
result = bannerCell;
return result;
}
I created an api using kimono and here is my code.
#import "PlayerRankingsTableViewController.h"
#import "RankingsTableViewCell.h"
#define kBgQueue dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define kPlayerRankingsURL [NSURL URLWithString:#"https://www.kimonolabs.com/api/bg6tcuuq?apikey=xgp4nU6xA9UcBWSe0MIHcBVbAWz5v4wR"]
#interface PlayerRankingsTableViewController () {
}
#property (nonatomic, strong) NSArray *playerRankings;
#end
#implementation PlayerRankingsTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:
kPlayerRankingsURL];
[self performSelectorOnMainThread: #selector(initializePlayerRankingsArray:)
withObject:data waitUntilDone:YES];
});
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be rexrcreated.
}
- (NSArray *)initializePlayerRankingsArray:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *myPlayerRankings = [[json objectForKey:#"results"]objectForKey:#"collection1"];
self.playerRankings = myPlayerRankings;
NSLog(#"%#", self.playerRankings);
return self.playerRankings;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.playerRankings count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RankingsTableViewCell *cell = (RankingsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) {
cell = (RankingsTableViewCell *)[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
NSDictionary *rankings = [self.playerRankings objectAtIndex: indexPath.row];
NSString *rank = [rankings objectForKey:#"rank"];
NSString *name = [rankings objectForKey:#"name"];
NSString *points = [rankings objectForKey:#"points"];
[cell.playerRank setText:rank];
cell.playerName.text = name;
cell.playerPoints.text = points;
return cell;
}
#end
I think there is nothing wrong with data parsing process, because the console displays my data parsed from the web correctly.
However, when I ran the app, I saw nothing but a empty table.
Again, I think this might be something simple and new to programming.
Thank you in advance, and sorry for being such a burden.
In initializePlayerRankingsArray: you shouldn't be returning the array because nothing it there to receive it. You have already set self.playerRankings, so that is enough.
What is missing from this method is [self.tableView reloadData]; (which should be the last line after self.playerRankings is set).
I'm working on a sharing extension to simply grab a link, choose a few names to share it to, and Share. The data layer isn't added yet, only the UI to display some names in a tableview (using a custom cell) and I'm pulling in the shared URL from the extension context. All of the code in the VC is below. All views are set up in the Storyboard. Two UIButtons, Two UILabels, One TableView and a UIView to hold it all, so I can easily round the corners.
The issue I'm having is that the _linkLabel that I'm using the display the URL doesn't visually update for nearly 10 seconds! What.In.The.World. What I'm a doing here that's causing this?
I'm logging out the URL in the callback from hasItemConformingToTypeIdentifier and it happens as soon as the extension appears, but doesn't update the label??!! Helps. Please.
#import "ShareViewController.h"
#import "UserCell.h"
#interface ShareViewController ()
#end
#implementation ShareViewController
- (void)viewDidLoad{
self.view.alpha = 0;
_friends = [#[#"Ronnie",#"Bobby",#"Ricky",#"Mike"] mutableCopy];
_containerView.layer.cornerRadius = 6.f;
_selectedIndexPaths = [[NSMutableArray alloc] init];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIView animateWithDuration:0.5 animations:^{
self.view.alpha = 1;
}];
}
- (void)viewDidAppear:(BOOL)animated{
//pull the URL out
NSExtensionItem *item = self.extensionContext.inputItems[0];
NSItemProvider *provider = item.attachments[0];
if ([provider hasItemConformingToTypeIdentifier:#"public.url"]) {
[provider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSURL *url = (NSURL*)item;
_linkLabel.text = url.absoluteString;
NSLog(#"Link: %#", url.absoluteString);
}];
}
else{
NSLog(#"No Link");
}
}
#pragma mark - UITableView Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UserCell *cell = (UserCell*)[tableView cellForRowAtIndexPath:indexPath];
if([_selectedIndexPaths containsObject:indexPath]){
[_selectedIndexPaths removeObject:indexPath];
cell.selected = NO;
}
else{
cell.selected = YES;
[_selectedIndexPaths addObject:indexPath];
}
NSLog(#"Share to %i friends", (int)[_selectedIndexPaths count]);
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//Later, calc height based on text in comment
return 44;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [_friends count];
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"UserCell";
UserCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[UserCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.selected = ([_selectedIndexPaths containsObject:indexPath]) ? YES : NO;
cell.nameLabel.text = [_friends objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)dismiss {
[UIView animateWithDuration:0.34 animations:^{
self.view.alpha = 0;
} completion:^(BOOL finished) {
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
}];
}
#end
Delays in updates to UI elements is a classic sign of trying to update the UI from outside the main queue. Which is what is happening here. You have this:
[provider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSURL *url = (NSURL*)item;
_linkLabel.text = url.absoluteString;
NSLog(#"Link: %#", url.absoluteString);
}];
Except that NSItemProvider does not guarantee that the completion handler will be called on the same queue that you started on. You're almost guaranteed to be on a different queue here, so you're getting this weird delay. You need to dispatch back to the main queue to perform the update:
[provider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = (NSURL*)item;
_linkLabel.text = url.absoluteString;
NSLog(#"Link: %#", url.absoluteString);
});
}];
i been trying to implement a GCD on my project which shows the fetch data from an XML, even though its my first time doing it i've succesfully(maybe) implemented in on nameLabel and detailLabel which are both string, but the imageLabel(commented part of the code) doesn't give anything when i try to implement the same GCD as both strings, i dont know whats happening but when i run the project it gives an unknown exception, i would like to know if how to implement a GCD on the commented part of the code so i will be able to show the image in the imageView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"CustomCell";
dataFileHolder *currentData = [[xmlParser listPopulated] objectAtIndex:indexPath.row];
CustomCellXMLClass *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[CustomCellXMLClass alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellXMLSample" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(myQueue, ^{
NSString *nameLabel = [currentData nameOfCat];
NSString *dataToCacheLabel = [myCache objectForKey:nameLabel];
if(nameLabel != nil){
dataToCacheLabel = [NSString stringWithString:nameLabel];
if (dataToCacheLabel != nil) {
[myCache setObject:dataToCacheLabel forKey:nameLabel];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.nameLabel setText:dataToCacheLabel];
});
}
}
NSString *detailLabel = [currentData descriptionOfCat];
NSString *stringToCache = [myCache objectForKey:detailLabel];
if (detailLabel != nil) {
stringToCache = [NSString stringWithString:detailLabel];
if (stringToCache != nil) {
[myCache setObject:stringToCache forKey:detailLabel];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.detailLabel setText:stringToCache];
});
}
}
// NSString *imageURL = [currentData imageLink];
// NSData *dataToCache;
// if (imageURL != nil) {
//
// dataToCache = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
// if (dataToCache != nil) {
//
// [myCache setObject:dataToCache forKey:imageURL];
// [cell.imageShow setImage:[UIImage imageWithData:dataToCache]];
//
// }
// else {
//
// NSURL *imageURL = [NSURL URLWithString:#"http://i178.photobucket.com/albums/w255/ace003_album/190579604m.jpg"];
// dataToCache = [NSData dataWithContentsOfURL:imageURL];
// [myCache setObject:dataToCache forKey:imageURL];
// [cell.imageShow setImage:[UIImage imageWithData:dataToCache]];
// }
//
// }
[self.activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
});
return cell;
}
You need to dispatch the parts where you update the cell back to the main thread like you are doing for the first two. This part: dispatch_async(dispatch_get_main_queue(),...) It'll probably look something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CustomCell";
static NSInteger usageCount = 0;
dataFileHolder *currentData = [[xmlParser listPopulated] objectAtIndex:indexPath.row];
CustomCellXMLClass *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[CustomCellXMLClass alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellXMLSample" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Give it a new tag every time, so we can tell if this is "our" use of the cell
cell.tag = ++usageCount;
myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(myQueue, ^{
NSString *nameLabel = [currentData nameOfCat];
NSString *dataToCacheLabel = [myCache objectForKey:nameLabel];
if(nameLabel != nil){
dataToCacheLabel = [NSString stringWithString:nameLabel];
if (dataToCacheLabel != nil) {
[myCache setObject:dataToCacheLabel forKey:nameLabel];
dispatch_async(dispatch_get_main_queue(), ^{
if (cell.superview && usageCount == cell.tag)
{
[cell.nameLabel setText:dataToCacheLabel];
}
});
}
}
NSString *detailLabel = [currentData descriptionOfCat];
NSString *stringToCache = [myCache objectForKey:detailLabel];
if (detailLabel != nil) {
stringToCache = [NSString stringWithString:detailLabel];
if (stringToCache != nil) {
[myCache setObject:stringToCache forKey:detailLabel];
dispatch_async(dispatch_get_main_queue(), ^{
if (cell.superview && usageCount == cell.tag)
{
[cell.detailLabel setText:stringToCache];
}
});
}
}
NSURL *fallbackImageURL = [NSURL URLWithString:#"http://i178.photobucket.com/albums/w255/ace003_album/190579604m.jpg"];
NSString *imageURL = [currentData imageLink];
NSArray* urls = imageURL ? #[ imageURL, fallbackImageURL ] : #[ fallbackImageURL ];
for (NSURL* url in urls)
{
UIImage* cachedImage = [myCache objectForKey: url];
if (!cachedImage)
{
NSData* imageData = [NSData dataWithContentsOfURL:url];
cachedImage = [UIImage imageWithData:dataToCache];
[myCache setObject:cachedImage forKey:url];
}
if (cachedImage)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (cell.superview && usageCount == cell.tag)
{
[cell.imageShow setImage: uiImage];
}
});
break;
}
}
});
return cell;
}
In the long term, you should probably also consider any of a number of asynchronous loading approaches instead of dataWithContentsOfURL: which will block the background thread waiting for the data to be fully received. But that's a second-order issue here, and not what you asked about.
EDIT: Edited for #Rob's comments. Check the cache for a pre-cached image before fetching, and guard against writing values into reused or invisible cells (assuming you're not using tag for something else.) But really, just use an asynchronous image download manager.