SDWebImage displaying wrong images in UITableView - ios

In my iOS app, I'm displaying images inside multiple UITableViewCells. However, it's not displaying the correct images in each cell.
First I load some content from a Feedly stream with the method below:
- (void)loadStreams {
NSString *feedName = [NSString stringWithFormat:#"%#-id", self.category];
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *accessToken = [standardUserDefaults objectForKey:#"AccessToken"];
NSString *feedId = [standardUserDefaults objectForKey:feedName];
NSString *feedPartial = [feedId stringByReplacingOccurrencesOfString:#"/" withString:#"%2F"];
NSString *feedUrl = [NSString stringWithFormat:#"https://sandbox.feedly.com/v3/streams/%#/contents", feedPartial];
NSLog(#"The Feedly url is: %#", feedUrl);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:feedUrl]];
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest addValue:accessToken forHTTPHeaderField:#"Authorization"];
request = [mutableRequest copy];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)[responseObject objectForKey:#"items"];
self.continuation = [responseObject objectForKey:#"continuation"];
NSMutableArray *tempStreams = [[NSMutableArray alloc] init];
for (NSDictionary *dic in jsonArray) {
NSLog(#"Dic contains: %#", dic);
NSDictionary *originArray = [dic objectForKey:#"origin"];
NSDictionary *visualArray = [dic objectForKey:#"visual"];
NSArray *alternateArray = [dic objectForKey:#"alternate"];
NSDictionary *alternate = [alternateArray objectAtIndex:0];
NSString *image = [visualArray objectForKey:#"url"];
NSString *title = [dic objectForKey:#"title"];
NSString *author = [dic objectForKey:#"author"];
NSString *date = [dic objectForKey:#"published"];
NSDictionary *contentum = [dic objectForKey:#"content"];
NSString *content = [contentum objectForKey:#"content"];
NSString *owner = [originArray objectForKey:#"title"];
NSString *givenid = [dic objectForKey:#"id"];
NSString *href = [alternate objectForKey:#"href"];
NSDate *publisher = [NSDate dateWithTimeIntervalSince1970:([date doubleValue] / 1000.0)];
NSString *published = publisher.timeAgoSinceNow;
NSDictionary *data = [[NSDictionary alloc] initWithObjectsAndKeys:title, #"title", image, #"imageurl", published, #"published", owner, #"owner", content, #"content", givenid, #"givenid", href, #"href", author, #"author", nil];
Stream *stream = [[Stream alloc] initWithDictionary:data];
[tempStreams addObject:stream];
}
self.streams = [[NSMutableArray alloc] initWithArray:tempStreams];
tempStreams = nil;
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Services"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
[operation start];
}
This passes the data to an object called Stream, which consists of the code below:
Stream.h
#import <Foundation/Foundation.h>
#interface Stream : NSObject
#property (strong, nonatomic)NSString *name;
#property (strong, nonatomic)NSString *thumbnail;
#property (strong, nonatomic)NSString *photo;
#property (strong, nonatomic)NSString *published;
#property (strong, nonatomic)NSString *content;
#property (strong, nonatomic)NSString *givenid;
#property (strong, nonatomic)NSString *linky;
#property (strong, nonatomic)NSString *author;
- (id)initWithName:(NSString *)aName
thumbnail:(NSString *)aThumbnail
photo:(NSString *)aPhoto
published:(NSString *)aPublished
content:(NSString *)aContent
givenid:(NSString *)aId
linky:(NSString *)aLinky
author:(NSString *)aAuthor;
- (id)initWithDictionary:(NSDictionary *)dic;
#end
Stream.m
#import "Stream.h"
#implementation Stream
//The designed initializer
- (id)initWithName:(NSString *)aName
thumbnail:(NSString *)aThumbnail
photo:(NSString *)aPhoto
published:(NSString *)aPublished
content:(NSString *)aContent
givenid:(NSString *)aId
linky:(NSString *)aLinky
author:(NSString *)aAuthor{
self = [super init];
if (self) {
self.name = aName;
self.thumbnail = aThumbnail;
self.photo = aPhoto;
self.published = aPublished;
self.content = aContent;
self.givenid = aId;
self.linky = aLinky;
self.author = aAuthor;
}
return self;
}
- (id)initWithDictionary:(NSDictionary *)dic {
self = [self initWithName:dic[#"title"] thumbnail:dic[#"imageurl"] photo:dic[#"imageurl"] published:dic[#"published"] content:dic[#"content"] givenid:dic[#"givenid"] linky:dic[#"href"] author:dic[#"author"]];
return self;
}
- (id)init {
self = [self initWithName:#"Undifined" thumbnail:#"Undifined" photo:#"Undifined" published:#"Undifined" content:#"Undifined" givenid:#"Undifined" linky:#"Undifined" author:#"Undifined"];
return self;
}
#end
And in the end I build a cell like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * reuseIdentifier = #"programmaticCell";
MGSwipeTableCell * cell = [self.tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (!cell) {
cell = [[MGSwipeTableCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
}
CGFloat brightness = [UIScreen mainScreen].brightness;
cell.textLabel.text = [self.streams[indexPath.row] name];
cell.detailTextLabel.text = [self.streams[indexPath.row] published];
NSString *imageUrl = [NSString stringWithFormat: #"%#", [self.streams[indexPath.row] photo]];
NSLog(#"Image is: %# and path is: %d", imageUrl, indexPath.row);
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:imageUrl]
placeholderImage:[UIImage imageNamed:#"tile-blue.png"] options:indexPath.row == 0 ? SDWebImageRefreshCached : 0];
cell.delegate = self; //optional
return cell;
}
What happens though, is that it displays the wrong image in a lot of cells and sometimes the same image for a couple of cells. What am I doing wrong here?

These are symptoms of cell reuse. There are two issues you will have to deal with.
(1) you should reset your cell's content before it is reused. To do this you can override prepareForReuse in the cell and nil out the relevant properties (such as cell.imageView). If you don't do this, you will see the old image -after- the cell has been recycled, before SDWebImage has assigned a new image.
(2) as SDWebImage image retrieval is async, the image may arrive after the cell has scrolled off the screen (and recycled with new content. You need to check whether the image is still relevant before assigning it to the imageView. I am not sure if this is possible with the SDWebImage UIImageView category method. You may have to dissect SDWebImage a little . You can get more control over the process using the SDWebImageManager method:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
You could use it something like this (in CellForRowAtIndexPath)
[[SDWebImageManager defaultManager] downloadImageWithURL:url
options:0
progress:nil
completed:
^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
//image is still valid for this cell
cell.image = image;
}
}];

Push a unique id on the stack before your closure and check it when your closure completes
prepareForReuse
Like this:
func updateArtistImage(url: URL) {
let _eventId = self.event?.id
SDWebImageManager.shared().loadImage(with: url, options: [], progress: nil) { (image, data, error, cacheType, finished, url) in
if self.event!.id == _eventId {
if error == nil {
self.artistImageView.image = image
} else {
self.artistImageView.image = UIImage(named: "error_image")
}
}
}
}
and this:
override func prepareForReuse() {
super.prepareForReuse()
self.artistImageView.image = nil
}

Related

Getting data from a service Objective-C

am new to iOS, Getting issue with displaying data from below service data
[{
"Name": Rahul,
"FatherName": Ravinder,
"Designation": Engineering,
"Profession": Software Eng,
"Height": "5 ft 3 in",
"Weight": "134.5 lbs"
}]
below is the code what i have tried. Please help me to find the issue. Thanks In Advance.
NameDetails.m
---------------
- (void)viewDidLoad {
[super viewDidLoad];
[self callService:[appDelegate.signUpdata objectForKey:#"id"]];
}
-(void)callService:(NSString *)userid
{
[Utility showIndicator:nil view1:self.view];
JsonServicePostData = [[JsonServiceCls alloc] init];
JsonServicePostData.delegate = self;
[JsonServicePostData Getdata:userid];
}
-(void)DidFinishWebServicesPostData
{
[Utility hideIndicator];
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
_txtName.text=[dict objectForKey:#"Name"];
_txtFName.text=[dict objectForKey:#"FatherName"];
_txtDesg.text=[dict objectForKey:#"Designation"];
_txtprof.text=[dict objectForKey:#"Profession"];
_txtHeight.text=[dict objectForKey:#"Height"];
_txtWeight.text=[dict objectForKey:#"Weight"];
}
}
+(void)makeHttpGETresponceParsingwithSerVer:(NSString *)strServer withCallBack:(void(^)(NSDictionary *dicArr,NSError *error))handler
{
NSURL *urlServer = [NSURL URLWithString:strServer];
NSURLRequest *request = [NSURLRequest requestWithURL:urlServer];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
handler(res,error);
}];
[postDataTask resume];
}
then call your method In viewDidLoad...
[RestClient makeHttpGETresponceParsingwithSerVer:#"YOUR_URL" withCallBack:^(NSDictionary *responceDic, NSError *error) {
_txtName.text =[responceDic objectForKey:#"Name"];
_txtFName.text =[responceDic objectForKey:#"FatherName"];
_txtDesg.text =[responceDic objectForKey:#"Designation"];
_txtprof.text =[responceDic objectForKey:#"Profession"];
_txtHeight.text =[responceDic objectForKey:#"Height"];
_txtWeight.text =[responceDic objectForKey:#"Weight"];
}];
// RestClient is the class name as it is a class method, You can use instance method.
Hi The better approach is for this kind of API call activity you have to go with AFNetworking - https://github.com/AFNetworking/AFNetworking
Its Pretty simple and more powerful. Once you get the json response you have to go for Model Approach.
#import <UIKit/UIKit.h>
#interface NameDetails : NSObject
#property (nonatomic, strong) NSString * designation;
#property (nonatomic, strong) NSString * fatherName;
#property (nonatomic, strong) NSString * height;
#property (nonatomic, strong) NSString * name;
#property (nonatomic, strong) NSString * profession;
#property (nonatomic, strong) NSString * weight;
-(instancetype)initWithDictionary:(NSDictionary *)dictionary;
-(NSDictionary *)toDictionary;
#end
#import "RootClass.h"
NSString *const kRootClassDesignation = #"Designation";
NSString *const kRootClassFatherName = #"FatherName";
NSString *const kRootClassHeight = #"Height";
NSString *const kRootClassName = #"Name";
NSString *const kRootClassProfession = #"Profession";
NSString *const kRootClassWeight = #"Weight";
#interface RootClass ()
#end
#implementation RootClass
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
-(instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if(![dictionary[kRootClassDesignation] isKindOfClass:[NSNull class]]){
self.designation = dictionary[kRootClassDesignation];
}
if(![dictionary[kRootClassFatherName] isKindOfClass:[NSNull class]]){
self.fatherName = dictionary[kRootClassFatherName];
}
if(![dictionary[kRootClassHeight] isKindOfClass:[NSNull class]]){
self.height = dictionary[kRootClassHeight];
}
if(![dictionary[kRootClassName] isKindOfClass:[NSNull class]]){
self.name = dictionary[kRootClassName];
}
if(![dictionary[kRootClassProfession] isKindOfClass:[NSNull class]]){
self.profession = dictionary[kRootClassProfession];
}
if(![dictionary[kRootClassWeight] isKindOfClass:[NSNull class]]){
self.weight = dictionary[kRootClassWeight];
}
return self;
}
/**
* Returns all the available property values in the form of NSDictionary object where the key is the approperiate json key and the value is the value of the corresponding property
*/
-(NSDictionary *)toDictionary
{
NSMutableDictionary * dictionary = [NSMutableDictionary dictionary];
if(self.designation != nil){
dictionary[kRootClassDesignation] = self.designation;
}
if(self.fatherName != nil){
dictionary[kRootClassFatherName] = self.fatherName;
}
if(self.height != nil){
dictionary[kRootClassHeight] = self.height;
}
if(self.name != nil){
dictionary[kRootClassName] = self.name;
}
if(self.profession != nil){
dictionary[kRootClassProfession] = self.profession;
}
if(self.weight != nil){
dictionary[kRootClassWeight] = self.weight;
}
return dictionary;
}
The above one is Model Class.
Your JSON look like a array. So you need to iterate the Dictionary values on it. Other than that you may pass it directly.
Now in your ViewController class initiate the mutable array
and pass the response like
NSArray *arrayData = ResponseFromAFNETWORKING
for (NSDictionary *data in arrayData) {
NameDetails *modelFeed = [[NameDetails alloc] initFromDictinary:data]
[self.YourMutableDictionary addObject:modelFeed]
}
self.updateDisplay:self.YourMutableDictionary[0] // If not array No iteration, you can prepare the model and pass it directly
----------------------------------------
- (void)updateDisplay:(NameDetails *)feed {
_txtName.text =feed.Name;
_txtFName.text =feed.FatherName;
_txtDesg.text =feed.Designation;
_txtprof.text =feed.Profession;
_txtHeight.text =feed.Height;
_txtWeight.text =feed.Weight;
}
Hope this will help. This is a robust and elastic approach, thread safe mechanism too

Save UICollectionViewCell progress state

I've already asked the same question about saving pressed button state and I thought that I should do in the same way to save progress state on cells but my tries are unsuccessful.
What I'm doing now: I select some UICollectionViewCell's and then press "download" button and then downolad action starts. Every cell I selected shows UIProgressView and everything is ok untill I scroll my UICollectionView up or down. When I do it another cells have progress view too but they mustn't! I know that I must save indexPath of selected cells in NSMutableArray and then in cellForItemAtIndexPath check if current cell indexPath is in my array and then show or hide my cell's subviews. I do that but it only works with cell selection! What should I do to save progress view state on each cell this progress really there?
Here is my code:
In cellForItemAtIndexPath:
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ([selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
cell.selectedBG.hidden = NO;
cell.selectedImg.hidden = NO;
}
else
{
cell.selectedBG.hidden = YES;
cell.selectedImg.hidden = YES;
cell.progressView.hidden = YES;
}
In didSelectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
...
// Add the selected item into the array
[selectedIds addObject:selectedId];
[selectedVideos addObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( ![selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray addObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = NO;
collectionCell.selectedImg.hidden = NO;
[collectionCell setSelected:YES];
}
}
}
In didDeselectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
...
// Delete the selected item from the array
[selectedIds removeObject:selectedId];
[selectedVideos removeObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = YES;
collectionCell.selectedImg.hidden = YES;
[collectionCell setSelected:NO];
}
}
And this is how I show my progress:
-(NSString *)progress:(long long )val1 : (long long )val2 : (AVMVideoCell *)cell : (NSString *)name : (NSIndexPath *)path{
float progress = ((float)val1) / val2;
NSString *prog = [[NSNumber numberWithFloat:progress*100] stringValue];
if (prog != nil){
if(cell.isSelected){
cell.selectedImg.hidden = YES;
cell.progressView.hidden = NO;
}
}
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)path.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]])
{
[cell.progressView setProgress:progress animated:YES];
}
if([prog intValue]==100){
cell.progressView.hidden = YES;
}
return prog;
}
EDIT: AVMVideoCell.m
#import "AVMVideoCell.h"
#implementation AVMVideoCell
{
NSString *fullUrl;
}
#synthesize imageView;
#synthesize selectedBG;
#synthesize progressLabel;
#synthesize progressView;
#synthesize selectedImg;
#synthesize progLayer;
-(void) setVideo:(AVMDataStore *)video {
if(_video != video) {
_video = video;
}
NSString *durString = [NSString stringWithFormat:#"%#",[self timeFormatted:_video.duration]];
if((_video.filePreview != nil) && ![_video.filePreview isEqualToString:#""]){
fullUrl = [NSString stringWithFormat:#"http://example.com%#",_video.filePreview];
}
NSURL *imgURL = [NSURL URLWithString:fullUrl];
[self.imageView setImageWithURL:imgURL
placeholderImage:[UIImage imageNamed:#"yesterday.png"] options:0 usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
self.duration.text = durString;
}
- (NSString *)timeFormatted:(int)totalSeconds
{
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d:%02d:%02d",hours, minutes, seconds];
}
#end
EDIT 2: Explanation about progress
My progressView is not an IBOutlet it's a #property (nonatomic,strong) UIProgressView *progressView; (AVMVideoCell.h)
I allocate and initialize it in cellForItemAtIndexPath:
cell.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0,0, 150.0f, 1.0f)];
cell.progressView.trackTintColor = [UIColor colorWithWhite:255 alpha:0.5f];
cell.progressView.progressTintColor = [UIColor whiteColor];
[cell.progLayer addSubview:cell.progressView];
cell.progressView.hidden = YES;
cell.progressView.tag = indexPath.row+500;
This is where I call progress change showing:
-(void)downloadStart:(NSString*)fileUrl : (NSString*)name : (AVMVideoCell *) cell : (NSIndexPath *)path{
NSURL *URL = [NSURL URLWithString:fileUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSString *fileName = [NSString stringWithFormat:#"%#.mp4",name]; //set full file name to save
AFHTTPRequestOperation *downloadRequest = [[AFHTTPRequestOperation alloc] initWithRequest:request]; //create download request
[downloadRequest setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathToFile = [NSString stringWithFormat:#"%#/%#", [paths firstObject],fileName]; // path to 'Documents'
NSString *pathOfFile = [[paths objectAtIndex:0] stringByAppendingPathComponent:fileName];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:pathOfFile append:NO];
BOOL success = [data writeToFile:pathToFile atomically:YES];
if(success){
[self checkIfExists:name : cell :path];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"file downloading error : %#", [error localizedDescription]);
UIAlertView * alert=[[UIAlertView alloc]initWithTitle:#"Error" message:[NSString stringWithFormat:#"%#",error] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil ];
[alert show];
cell.progressView.hidden = YES;
}];
// Step 5: begin asynchronous download
[downloadRequest start];
[downloadRequest setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
[self progress:totalBytesRead :totalBytesExpectedToRead :cell :name : path]; //here I pass val1 and val 2
}];
}
When I select an items in collection view, I gather their model's objects, get each id and make array of urls, then in for..in loop i pass urls one by one and then start async download. You can see how I download and call progress method above.
I need to know if you have a reuse issue or a model issue.
So first try this :
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
...
if (![selectedIds.containsObject:selectedId])
{
// Add the selected item into the array
[selectedIds addObject:selectedId];
[selectedVideos addObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( ![selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray addObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = NO;
collectionCell.selectedImg.hidden = NO;
[collectionCell setSelected:YES];
}
}
else {
// Delete the selected item from the array
[selectedIds removeObject:selectedId];
[selectedVideos removeObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = YES;
collectionCell.selectedImg.hidden = YES;
[collectionCell setSelected:NO];
}
}
and remove the didDeselect delegate.
Let me know what happens then.
EDIT :
Ok try this now:
// Lazy instantiation of the progressView
- (UIProgressView *)progressView
{
if (!_progressView)
{
_progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0,0, 150.0f, 1.0f)];
_progressView.trackTintColor = [UIColor colorWithWhite:255 alpha:0.5f];
_progressView.progressTintColor = [UIColor whiteColor];
_progressView.hidden = YES;
[self.contentView addSubview:_progressView];
}
return _progressView;
}
// Here we remove the progressView on reuse
-(void)prepareForReuse
{
[super prepareForReuse];
[self.progressView removeFromSuperview];
self.progressView = nil;
}
Also remove what you did with the progressView with in the cellForItemAtIndexPath method.
You can create class for cell's models, store these models in array in your viewController and use them to get/set all states from/for cells.
Something like this:
#interface CellModel : NSObject
#property(nonatomic) BOOL selected;
#property(nonatomic) NSUInteger progress;
#end
In viewController:
#interface MyViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic) NSArray* models;
#end

duplicate data on refresh ios

I've got my reload function reloading data properly, but I'm getting duplicate data. I've tried nil-ing out the changelist in the below spots and had no luck. Should I be nil-ing out the jsonObject? Or am I just putting it in the wrong spot.
Thanks for any help.
- (void)viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to refresh"];
[refresh addTarget:self action:#selector(refreshmytable:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refresh;
NSURLSessionConfiguration *config =
[NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:self
// delegate:nil
delegateQueue:nil];
[self fetchFeed];
}
- (void)refreshmytable:(UIRefreshControl *)refreshControl{
[self fetchFeed]; //Added 12:12 9.16.14
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Updating"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm a"];
NSString *updated = [NSString stringWithFormat:#" Last Update: %#", [formatter stringFromDate:[NSDate date]]];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:updated];
[refreshControl endRefreshing];
[self.tableView reloadData]; //Added this 11:32 9.16.14
}
- (void)fetchFeed
{
NSString *userEID = MAP_getUsername();
//NSLog(userEID);
NSString *requestString1 = [#"URL" stringByAppendingString:userEID];
NSString *requestString2 = #"&status=pending";
NSString *requestString = [requestString1 stringByAppendingString:requestString2];
//NSLog(requestString);
/*NSString *requestString = #"http://URL";
*/
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:req
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
self.changeList = jsonObject[#"List"];
//self.changeList=nil; //tried to add here to remove duplicate data
NSLog(#"%#", self.changeList);
//- add code here to populate BNRItemStore with the change order list.
// - following code should be rewritten in fetchFeed that will load BNRItemStore.
if (self.changeList.count>0) {
for (int i = 0; i < self.changeList.count; i++) {
NSDictionary *coItem = self.changeList[i];
[[BNRItemStore sharedStore]
addItemWithApproverEid:coItem[#"approverEid"]
assignmentGroup:coItem[#"assignmentGroup"]
changeOrder:coItem[#"changeOrder"]
subcategory:coItem[#"subCatagory"]
title:coItem[#"title"]
];
}
}
//NSLog(#"sizeof(NSInteger) = %#", #(sizeof(NSInteger)));
//- end comment
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
//self.changeList=nil; //trying to null out list for refresh non duplicate data
// NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// NSLog(#"%#", json);
}];
[dataTask resume];
}
Added BNRITEM.h class
#class BNRItem;
#interface BNRItemStore : NSObject
#property (nonatomic, readonly) NSArray *allItems;
// Notice that this is a class method and prefixed with a + instead of a -
+ (instancetype)sharedStore;
- (BNRItem *)addItemWithApproverEid:(NSString *)aEid
assignmentGroup:(NSString *)aGroup
changeOrder:(NSString *)changeOrder
subcategory:(NSString *)subcategory
title:(NSString *)title;
#end
added BNRitem.m class
interface BNRItemStore ()
#property (nonatomic) NSMutableArray *privateItems;
#end
#implementation BNRItemStore
+ (instancetype)sharedStore
{
static BNRItemStore *sharedStore;
// Do I need to create a sharedStore?
if (!sharedStore) {
sharedStore = [[self alloc] initPrivate];
}
return sharedStore;
}
I believe the issue is in this code:
[[BNRItemStore sharedStore]
addItemWithApproverEid:coItem[#"approverEid"]
assignmentGroup:coItem[#"assignmentGroup"]
changeOrder:coItem[#"changeOrder"]
subcategory:coItem[#"subCatagory"]
title:coItem[#"title"]
You keep adding data to BNRItemStore but you don't remove the old one, there in nothing to do with self.changeList.
You need some way to remove all data before you add the new one, so on the beginning of the method fetchFeed you can call something like this:
[[BNRItemStore sharedStore] removeAllData];
Note I don't know that class BNRItemStore so removeAllData method probably doesn't exists, maybe there is another method to delete all data or maybe you nnd to implement one.
// Extended
I cannot see all of the method in .m file so I don't know where the data are stored by I believe it's stored in privateItems variable, maybe there is some method to remove all object from that array but it's not declared as public.
You can add a method definitions after
+ (instancetype)sharedStore;
in BNRITEM.h:
-(void)removeAllData;
And in BNRITEM.h implement it like that:
-(void)removeAllData {
[self.privateItems removeAllObjects];
}
And as I said before call [[BNRItemStore sharedStore] removeAllData]; at the beginning of fetchFeed method.

Populating my tableview with parsed json in a string

Very new to iOS, a bit lost on what to do. I actually HAVE all that I need, I just need help putting said information into my tableview now. I am debugging and everything is correct when I hover over the variables in my "init" function, but when I go down to the function that handles the cells something is just not clicking. Very frustrating having the information you need but don't know how to put it where you want.
I hate coming to SO for these kind of questions but I have exhausted all other options. Before posting on here, I have tried everything. I tried to google other projects that I can reference, did not find any that was iOS 7 or relevant to my project. I apologize for this elementary question, and thank you in advance.
MasterViewController.m
#interface MasterViewController () {
__block NSString * jsonString;
}
#end
#implementation MasterViewController
#synthesize coursesController = _coursesController;
-(void)getJSONString
{
jsonString = [JSONHelper JSONgetString:#"http://iam.colum.edu/portfolio/api/course?json=True"];
}
-(void)initCourses
{
[_coursesController.masterCoursesList
removeLastObject];
NSError *coursesError = nil;
NSArray *coursesNameList =
[NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingMutableContainers
error: &
coursesError];
if(coursesError)
NSLog(#"[%# %#] JSON error: %#", NSStringFromClass([self class]),
NSStringFromSelector(_cmd), coursesError.localizedDescription);
NSArray *coursesNames = [coursesNameList objectAtIndex:0];
for (NSString *courseName in coursesNameList) {
NSString *stringToUse = [courseName substringFromIndex:8];
//initialize variables
NSString *name = courseName;
NSInteger *courseid = 00;
NSString *imageUrl = nil;
//add current building to list
[_coursesController masterCoursesListWithName:name AndcourseID:courseid AndimageURL:imageUrl];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CoursesCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Courses *courseAtIndex = [self.coursesController objectInListAtIndex:indexPath.row];
[[cell textLabel] setText:courseAtIndex.courseName];
return cell;
}
CoursesDataController.m
#interface CoursesDataController()
-(void)initializeDefaultDataList;
#end
#implementation CoursesDataController
#synthesize masterCoursesList = _masterCoursesList;
-(void)initializeDefaultDataList
{
NSMutableArray *coursesList = [[NSMutableArray alloc] init];
Courses *course = [[Courses
alloc]initWithName:#"Loading Now..." AndcourseID:00];
[coursesList addObject:course];
}
-(void)setMasterCoursesList:(NSMutableArray *)newCourseList
{
if(_masterCoursesList != newCourseList)
{
_masterCoursesList = [newCourseList mutableCopy];
}
} //this function is created when you create a NSMutableArray MasterCoursesList
-(id)init
{
if (self = [super init])
{
[self initializeDefaultDataList];
return self;
}
return nil;
}
-(NSUInteger) countOfList
{
return [self.masterCoursesList count];
}
-(Courses *)objectInListAtIndex:(NSInteger)theIndex
{
return [self.masterCoursesList objectAtIndex:theIndex];
}
-(void)masterCoursesListWithName:(NSString *)cName
AndcourseID:(NSInteger *)cID
AndimageURL:(NSString *)cURL
{
Courses *courses;
courses = [[Courses alloc] initWithName:cName
AndcourseID:cID
AndimageURL:cURL];
[self.masterCoursesList addObject:courses];
}
-(void)courseName:(NSString *)cName
AndcourseID:(NSInteger *)cID
{
Courses *courses;
courses = [[Courses alloc] initWithName:cName AndcourseID:cID];
[self.masterCoursesList addObject:courses];
}
#end
CoursesDataController.h
#import <Foundation/Foundation.h>
#class Courses;
#interface CoursesDataController : NSObject
#property(nonatomic, copy) NSMutableArray *
masterCoursesList;
-(NSUInteger) countOfList;
-(Courses *)objectInListAtIndex:(NSInteger)theIndex;
- (void)masterCoursesListWithName:(NSString *)cName
AndcourseID:(NSInteger *)cID
AndimageURL:(NSString *)cURL;
- (void)courseName:(NSString *)cName
AndcourseID:(NSInteger *)cID;
#end
JSONHelper.m
+(NSString *)JSONgetString:(NSString *)query
{
NSString* searchURL = [NSString stringWithFormat:query];
NSError* error = nil; //error for NSUSRLConnection
NSURLResponse* response = nil;
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
NSURL* URL = [NSURL URLWithString:searchURL];
[request setURL:URL];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[request setTimeoutInterval:30];
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error)
{
NSLog(#"Error performing request %#", searchURL);
return 0;
}
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"jsonString:%#", jsonString);
return jsonString;
}
I have a lot of code posted, however there are only a few lines that really matter for my question. When I hover over "name" in the following line of code [_coursesController masterCoursesListWithName:name AndcourseID:courseid AndimageURL:imageUrl];
I get what I need (it loops through each JSON array and displays each one). But in the function that handles my cells, when I hover over courseName it doesn't say anything.
Courses *courseAtIndex = [self.coursesController objectInListAtIndex:indexPath.row];
[[cell textLabel] setText:courseAtIndex.courseName];
EDITS:
numberOfRowsInSection function in MasterViewController.m
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.coursesController countOfList];
}
Try this,
in CoursesDataController.m
-(void)initializeDefaultDataList
{
self.masterCoursesList = [[NSMutableArray alloc] init];
Courses *course = [[Courses
alloc]initWithName:#"Loading Now..." AndcourseID:00];
[self.masterCoursesList addObject:course];
}

iOS - Facebook Open Graph Api -- ensuring that photos are user generated

I am attempting to achieve the following:
This necessitates the open graph request to look something like this:
(https://developers.facebook.com/docs/opengraph/howtos/adding-photos-to-stories/)
POST /me/cookbook:eat?
recipe=http://www.example.com/recipes/pizza/&
image[0][url]=http://www.example.com/recipes/pizza/pizza.jpg&
image[0][user_generated]=true&
image[1][url]=http://www.example.com/recipes/pizza/pizza2.jpg&
image[1][user_generated]=true&
access_token=VALID_ACCESS_TOKEN
However, using this code: (from the scruptious tutorial here:
https://developers.facebook.com/docs/tutorials/ios-sdk-tutorial/publish-open-graph-story/ )
I do not know how to set the user generated flag to true. Because of this, the photo appears small.
- (void)postOpenGraphActionWithPhotoURL:(NSString*)photoURL
{
// First create the Open Graph meal object for the meal we ate.
id<SCOGMeal> mealObject = [self mealObjectForMeal:self.selectedMeal];
// Now create an Open Graph eat action with the meal, our location,
// and the people we were with.
id<SCOGEatMealAction> action =
(id<SCOGEatMealAction>)[FBGraphObject graphObject];
action.meal = mealObject;
if (self.selectedPlace) {
action.place = self.selectedPlace;
}
if (self.selectedFriends.count > 0) {
action.tags = self.selectedFriends;
}
if (photoURL) {
NSMutableDictionary *image = [[NSMutableDictionary alloc] init];
[image setObject:photoURL forKey:#"url"];
NSMutableArray *images = [[NSMutableArray alloc] init];
[images addObject:image];
action.image = images;
}
// Create the request and post the action to the
// "me/<YOUR_APP_NAMESPACE>:eat" path.
[FBRequestConnection startForPostWithGraphPath:#"me/<YOUR_APP_NAMESPACE>:eat"
graphObject:action
completionHandler:
^(FBRequestConnection *connection, id result, NSError *error) {
NSString *alertText;
if (!error) {
alertText = [NSString stringWithFormat:
#"Posted Open Graph action, id: %#",
[result objectForKey:#"id"]];
} else {
alertText = [NSString stringWithFormat:
#"error: domain = %#, code = %d",
error.domain, error.code];
}
[[[UIAlertView alloc] initWithTitle:#"Result"
message:alertText
delegate:nil
cancelButtonTitle:#"Thanks!"
otherButtonTitles:nil]
show];
}
];
}
here, mealobject conforms to the FBGraphObject protocol. Using the same protocol that I am inheriting from, how do I set the user generated flag to be true? The documentation makes no mention of anything "user_generated".
Or should I not use the protocol and manually format the string as per the desired post parameters?
EDIT:
I tried to manually do this using a string instead of the FBOpenGraph object, and I succeeded in replicating this same functionality.
However, I have been unable to get multiple photos to appear or set them to be user generated.
SCProtocols.h:
#import <Foundation/Foundation.h>
#import <FacebookSDK/FacebookSDK.h>
#protocol SCOGMeal<FBGraphObject>
#property (retain, nonatomic) NSString *id;
#property (retain, nonatomic) NSString *url;
#end
#protocol SCOGEatMealAction<FBOpenGraphAction>
#property (retain, nonatomic) id<SCOGMeal> meal;
#end
You should be able to replace your code:
if (photoURL) {
NSMutableDictionary *image = [[NSMutableDictionary alloc] init];
[image setObject:photoURL forKey:#"url"];
NSMutableArray *images = [[NSMutableArray alloc] init];
[images addObject:image];
action.image = images;
}
with:
if (photoURL) {
NSMutableDictionary *image = [[NSMutableDictionary alloc] init];
[image setObject:photoURL forKey:#"url"];
[image setObject:#"true" forKey:#"user_generated"]; // <======= ***add this line***
NSMutableArray *images = [[NSMutableArray alloc] init];
[images addObject:image];
action.image = images;
}

Resources