Image downloading progress bar not smooth using afnetworking in iOS - ios

I am currently downloading the image using afnetworking, but the progress bar is not smooth in the first time, but when I run this code second time, the progress bar is smooth, here is my code to download images.
progress bar works like go up, down than smooth, but when I run code the second time it works smooth
progressBar.progress = 0.0;
self.imageDownloads=[[NSMutableArray alloc]init];
[self.imageDownloads addObject:[[ImageDownload alloc] initWithURL:[NSURL URLWithString:#""]];
for (int i=0; i < self.imageDownloads.count; i++)
{
ImageDownload *imageDownload = self.imageDownloads[i];
imageDownload.filename = [NSString stringWithFormat:#"MyImage%d",i];
[self downloadImageFromURL:imageDownload];
}
Here is my code to download images
- (void)downloadImageFromURL:(ImageDownload *)imageDownload
{
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [docsPath stringByAppendingPathComponent:imageDownload.filename];
NSURLRequest *request = [NSURLRequest requestWithURL:imageDownload.url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
imageDownload.totalBytesRead = totalBytesRead;
imageDownload.totalBytesExpected = totalBytesExpectedToRead;
[self updateProgressView];
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSAssert([responseObject isKindOfClass:[NSData class]], #"expected NSData");
NSData *responseData = responseObject;
[responseData writeToFile:filePath atomically:YES];
// Because totalBytesExpected is not entirely reliable during the download,
// now that we're done, let's retroactively say that total bytes expected
// was the same as what we received.
imageDownload.totalBytesExpected = imageDownload.totalBytesRead;
[self updateProgressView];
NSLog(#"finished %#", imageDownload.filename);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", imageDownload.filename);
}];
[operation start];
}
- (void)updateProgressView
{
double totalTotalBytesRead = 0;
double totalTotalBytesExpected = 0;
for (ImageDownload *imageDownload in self.imageDownloads)
{
// note,
// (a) totalBytesExpected is not always reliable;
// (b) sometimes it's not present at all, and is negative
//
// So, when estimating % complete, we'll have to fudge
// it a little if we don't have total bytes expected
if (imageDownload.totalBytesExpected >= 0)
{
totalTotalBytesRead += imageDownload.totalBytesRead;
totalTotalBytesExpected += imageDownload.totalBytesExpected;
}
else
{
totalTotalBytesRead += imageDownload.totalBytesRead;
totalTotalBytesExpected += (imageDownload.totalBytesRead > kDefaultImageSize ? imageDownload.totalBytesRead + kDefaultImageSize : kDefaultImageSize);
}
}
if (totalTotalBytesExpected > 0)
[progressBar setProgress:totalTotalBytesRead / totalTotalBytesExpected animated:YES];
else
[progressBar setProgress:0.0 animated:NO];
}

This code is from an answer back in 2013. I would suggest
Don’t use the deprecated AFHTTPRequestOperation, instead use NSURLSession download task-based solution. If you want to use AFNetworking, they have a mechanism to do that.
Don’t update/calculate percentages yourself, but rather nowadays you’d use NSProgress for the individual downloads which are children to some parent NSProgress. You can have your UIProgressView observe that. The net effect is that you end up just updating the child NSProgress instances, and your parent’s progress view is updated automatically.
For example, imagine that I have a parent UIProgressView called totalProgressView and I have a NSProgress that it is observing:
#interface ViewController () <UITableViewDataSource>
#property (nonatomic, strong) NSProgress *totalProgress;
#property (nonatomic, strong) NSMutableArray <ImageDownload *> *imageDownloads;
#property (nonatomic, weak) IBOutlet UIProgressView *totalProgressView;
#property (nonatomic, weak) IBOutlet UITableView *tableView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.totalProgress = [[NSProgress alloc] init];
self.totalProgressView.observedProgress = self.totalProgress;
self.tableView.estimatedRowHeight = 50;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.imageDownloads = [NSMutableArray array];
}
...
#end
Then to start the downloads, I create a series of image downloads, add their individual NSProgress instances as children of the above totalProgress:
- (IBAction)didTapStartDownloadsButton {
NSArray <NSString *> *urlStrings = ...
NSURL *caches = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:true error:nil] URLByAppendingPathComponent:#"images"];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
self.totalProgress.totalUnitCount = urlStrings.count;
for (NSInteger i = 0; i < urlStrings.count; i++) {
NSURL *url = [NSURL URLWithString:urlStrings[i]];
NSString *filename = [NSString stringWithFormat:#"image%ld.%#", (long)i, url.pathExtension];
ImageDownload *imageDownload = [[ImageDownload alloc] initWithURL:url filename:filename];
[self.imageDownloads addObject:imageDownload];
[self.totalProgress addChild:imageDownload.progress withPendingUnitCount:1];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
[imageDownload updateProgressForTotalBytesWritten:downloadProgress.completedUnitCount
totalBytesExpectedToWrite:downloadProgress.totalUnitCount];
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [caches URLByAppendingPathComponent:filename];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
//do whatever you want here
}];
[task resume];
}
[self.tableView reloadData];
}
Where
// ImageDownload.h
#import Foundation;
NS_ASSUME_NONNULL_BEGIN
#interface ImageDownload : NSObject
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) NSString *filename;
#property (nonatomic) NSProgress *progress;
#property (nonatomic) NSUInteger taskIdentifier;
- (id)initWithURL:(NSURL *)url
filename:(NSString * _Nullable)filename;
/**
Update NSProgress.
#param totalBytesWritten Total number of bytes received thus far.
#param totalBytesExpectedToWrite Total number of bytes expected (may be -1 if unknown).
*/
- (void)updateProgressForTotalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
#end
NS_ASSUME_NONNULL_END
and
static const long long kDefaultImageSize = 1000000; // what should we assume for totalBytesExpected if server doesn't provide it
#implementation ImageDownload
- (id)initWithURL:(NSURL *)url filename:(NSString *)filename {
self = [super init];
if (self) {
_url = url;
_progress = [NSProgress progressWithTotalUnitCount:kDefaultImageSize];
_filename = filename ?: url.lastPathComponent;
}
return self;
}
- (void)updateProgressForTotalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
int64_t totalUnitCount = totalBytesExpectedToWrite;
if (totalBytesExpectedToWrite < totalBytesWritten) {
if (totalBytesWritten <= 0) {
totalUnitCount = kDefaultImageSize;
} else {
double written = (double)totalBytesWritten;
double percent = tanh(written / (double)kDefaultImageSize);
totalUnitCount = written / percent;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.progress.totalUnitCount = totalUnitCount;
self.progress.completedUnitCount = totalBytesWritten;
});
}
#end
That yields individual progress bars for the individual downloads, and the progress bar associated with the totalProgress is updated automatically for you, yielding:
Now, obviously, you don't need both the children UIProgressView and the parent one, too, so that's up to you. But the idea is
set up a hierarchy of NSProgress;
tell the UIProgressView to observe whatever NSProgress you want; and
just have your download update the child NSProgress values and the rest will happen automatically for you.

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

SDWebImage displaying wrong images in UITableView

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
}

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.

Uploading Multiple Files with AFNetworking - UIViewController not Deallocating

I have to upload multiple files, track their progress & subscribe to completion & failure blocks in order to show relevant message at the end of operation.
I wrote my own AFHTTPClient wrapper and created following method.
- (void) uploadFiles:(NSArray*)files
path:(NSString*)path
parameters:(NSDictionary*)parameters
progressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block
success:(void (^)(AFHTTPRequestOperation *, id))success failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure
{
NSMutableURLRequest *request =
[self multipartFormRequestWithMethod:#"POST"
path:path
parameters:parameters
constructingBodyWithBlock:
^(id <AFMultipartFormData>formData) {
for (CRLMultiPartFile *file in files) {
NSAssert(file.name, #"Name cannot be nil");
NSAssert(file.file, #"Nothing found to upload");
NSAssert(file.filename, #"FileName cannot be nil");
NSAssert(file.mimeType, #"Must set Mime-Type for %#", file.filename);
[formData appendPartWithFileData:file.file name:file.name fileName:file.filename mimeType:file.typeString];
}
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setUploadProgressBlock:block];
[operation setCompletionBlockWithSuccess:success failure:failure];
[self enqueueHTTPRequestOperation:operation];
}
The view controller that calls this method does not get deallocated and therefore all the images contained are retained in memory as well thus resulting in memory leaks and eventually memory warning.
Doing the profiling reveals that at the end of the whole operation, the view controller has a refCount of 1.
When I comment out the call to uploading the files, all works fine.
Here is the code in the controller. It uses the progress block to update elements on the UI.
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
ContactModel *model = (ContactModel*)[self.contacts lastObject];
[params setObject:model.phone forKey:#"receiver"];
__block typeof(self) sSelf = self;
[[JMClient sharedClient] uploadFiles:files
path:#"picture_share/"
parameters:params
progressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
CGFloat progPercent = ceilf(((CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite) * 100);
CGFloat widthToCut = (progPercent * sSelf.progWidth) / 100;
CGRect frame = sSelf.progresViewBG.frame;
frame.size.width = (sSelf.progWidth - widthToCut);
frame.origin.x = (sSelf.progOrigin + widthToCut);
sSelf.progresViewBG.frame = frame;
sSelf.progLabel.text = [NSString stringWithFormat:#"%i%%", (int)progPercent];
frame = sSelf.progTipView.frame;
frame.origin.x = (sSelf.progresViewBG.frame.origin.x - frame.size.width/2);
sSelf.progTipView.frame = frame;
frame = sSelf.progLabel.frame;
frame.origin.x = (sSelf.progresViewBG.frame.origin.x - frame.size.width/2);
sSelf.progLabel.frame = frame;
} success:^(AFHTTPRequestOperation *success, id reponse) {
CGRect frame = sSelf.progresViewBG.frame;
frame.size.width = 0;
frame.origin.x = sSelf.progOrigin;
sSelf.progresViewBG.frame = frame;
[sSelf.cancelButton setImage:[UIImage imageNamed:#"trnsfr_prgss_complt.png"] forState:UIControlStateNormal];
[sSelf performSelector:#selector(hideAwayProgressBars) withObject:nil afterDelay:3];
} failure:^(AFHTTPRequestOperation *failure, NSError *error) {
[Mediator showMessage:TGLocalizedString(kMessageKeyForUploadingFailed)];
[sSelf performSelector:#selector(hideAwayProgressBars) withObject:nil afterDelay:3];
}];
self.operation = [[self.client.sharedClient.operationQueue operations] lastObject];
- (void) hideAwayProgressBars
{
[[NSNotificationCenter defaultCenter] postNotificationName:kNotifcationKeyForPhotoUploadComplete object:nil];
}
The notification is received by the parent controller, that removes this controller's view from superview and sets it to nil.
P.S. CRLMultiPartFile is a custom class to hold attributes of the files to be uploaded
if you are using ARC, you should use __weak instead of __block, so you don't capture self inside the block.

Wrapper around ASIHTTPRequest

I am using ASIHTTPRequest framework for making network calls in my iOS applications. But I don't want to use it directly in all the controllers in my application. So I thought of writing a layer around ASIHTTPRequest. My code use this layer to use ASIHTTPRequest. The benefit wil be in future I should be able to replace this framework with some other framework and my code will be unchanged just the layer would change. I want to know what should be the strategy to do so. Should I subclass my class from ASIHTTPRequest class, or implement my own class. What should be the method I should consider implementing.
Currently I am implementing it like this.
My wrapper is
MyRequestHandler.h : NSObject
#property ASIHTTPRequest *asiHttpReq;
-(void) sendAsyncGetRequest
{
self.asiRequest = [ASIHTTPRequest requestWithURL:self.url];
if(self.tag != 0){
self.asiRequest.tag = self.tag;
}
[self.asiRequest setDelegate:self];
[self.asiRequest startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request{
MyResponseObj *respone = <From request obj>
if([delegate respondsToSelector:#selector(requestFinished:)]){
[delegate performSelector:#selector(requestFinished:) withObject:response];
}
}
And in my viewcontroller I would do this:
MyViewController.h : UIViewContoller
#property MyRequestHandler *reqHandler;
-(void) fireRequest
{
NSString* requestUrl = <create URL>;
if(requestUrl){
// [self showLoadingIndicatorView];
// Proceed for request.
NSURL *url = [NSURL URLWithString:requestUrl];
reqHandler = [MyRequestHandler requestWithURL:url];
reqHandler.tag = 1000;
[reqHandler setDelegate:self];
[reqHandler sendAsyncGetRequest];
}
}
- (void)requestFinished:(MyResponse*) responseData{
// Do Your parsing n all here.
}
- (void)requestFailed:(MyResponse*) responseData{
// Handle the error here.
}
Is this the right way to do it. The problem here is as I have created property of myrequesthandler in viewcontroller I can only make one request at a time, and loosing the capability of ASIHTTPRequest of making multiple request simultaneously.
Can you suggest me how to approach problems like this.
This is what I'm using:
#import "ASIFormDataRequest.h"
#interface RequestPerformer : NSObject {
id localCopy; // to avoid exec_bad_access with arc
ASIHTTPRequest *getRequest;
ASIFormDataRequest *postRequest;
}
#property (nonatomic, retain) id delegate;
#property (nonatomic, readwrite) SEL callback;
#property (nonatomic, readwrite) SEL errorCallback;
- (void)performGetRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector;
- (void)performPostRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary dataDictionary:(NSDictionary *)dataDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector;
#end
//
#import "RequestPerformer.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#implementation RequestPerformer
#synthesize delegate;
#synthesize callback, errorCallback;
- (void)performGetRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector {
localCopy = self;
self.delegate = requestDelegate;
self.callback = requestSelector;
self.errorCallback = errorSelector;
NSMutableString *requestStringData = [[NSMutableString alloc] init];
if (stringDictionary)
for (NSString *key in [stringDictionary allKeys])
[requestStringData appendFormat:#"%#=%#&", key, [stringDictionary objectForKey:key]];
NSString *resultString = [requestStringData substringToIndex:[requestStringData length]-1];
getRequest = [[ASIFormDataRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#?%#", string, [resultString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];
[getRequest setDelegate:self];
[getRequest setRequestMethod:#"GET"];
//NSLog(#"request url = %#", [getRequest.url absoluteString]);
[getRequest startAsynchronous];
}
- (void)performPostRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary dataDictionary:(NSDictionary *)dataDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector {
localCopy = self;
self.delegate = requestDelegate;
self.callback = requestSelector;
self.errorCallback = errorSelector;
NSURL *url = [NSURL URLWithString:string];
postRequest = [[ASIFormDataRequest alloc] initWithURL:url];
[postRequest setDelegate:self];
[postRequest setRequestMethod:#"POST"];
if (stringDictionary)
for (NSString *key in [stringDictionary allKeys])
[postRequest setPostValue:[stringDictionary objectForKey:key] forKey:key];
if (dataDictionary)
for (NSString *key in [dataDictionary allKeys])
[postRequest setData:[dataDictionary objectForKey:key] forKey:key];
//NSLog(#"request url = %#", [postRequest.url absoluteString]);
[postRequest startAsynchronous];
}
#pragma mark - ASIHTTPRequest Delegate Implementation
- (void)requestFinished:(ASIHTTPRequest *)crequest {
NSString *status = [crequest responseString];
if (self.delegate && self.callback) {
if([self.delegate respondsToSelector:self.callback])
[self.delegate performSelectorOnMainThread:self.callback withObject:status waitUntilDone:YES];
else
NSLog(#"No response from delegate");
}
localCopy = nil;
}
- (void)requestFailed:(ASIHTTPRequest *)crequest {
if (self.delegate && self.errorCallback) {
if([self.delegate respondsToSelector:self.errorCallback])
[self.delegate performSelectorOnMainThread:self.errorCallback withObject:crequest.error waitUntilDone:YES];
else
NSLog(#"No response from delegate");
}
localCopy = nil;
}
#end
To use it, just import RequestPerformer.h in your UIViewController and do smth like:
[requestManager performGetRequestWithString:tempString stringDictionary:stringDictionary dataDictionary:dataDictionary delegate:self requestSelector:#selector(requestSucceeded:) errorSelector:#selector(requestFailed:)];
Parameters:
(NSString *)string - url string, where to post your request;
(NSDictionary *)stringDictionary - dictionary, which contains all the
text information (such as name, id etc.);
(NSDictionary *)dataDictionary - dictionary, which contains all data information (such as photos, files, etc.);
(id)requestDelegate - delegate to perform selectors below;
(SEL)requestSelector - selector, which will be executed while successfully request;
(SEL)errorSelector - selector, which will be executed, while error occurred.
In above answer (demon9733), wrapper class has been created delegate property with retain. In general, delegate property should be assign to remove retain cycle.

Resources