iOS, Delay Display in UITableView with insertRowsAtIndexPaths:withRowAnimation: - uitableview

I have a custom NSObject class, doing some FTP work (display, transfer...).
This FTP class has a downloadFiles: method, which is launch inside a NSOperationQueue.
And I would like that method to provide some results as a data source in a UITableView. So I decided to delegate those results to the UITableViewController.
So the protocol for the FTP class :
#protocol FTPDelegate;
..
#interface FTP...
#property (nonatomic, weak) id <FTPDelegate> delegate;
...
#protocol FTPDelegate
-(void)FTPMessageReceived: (NSString *)message;
#end
Then I declared the custom UITableViewController as delegate of the FTP class:
self.myFTP = (FTP *) [self FTPManager];
self.myFTP.delegate = self;
So when the downloadFiles: method from the FTP class wants to display some message in the UITableView, it calls:
[[self delegate] FTPMessageReceived:#"dir created"];
The UITableViewController implements that method :
-(void)FTPMessageReceived: (NSString *)message {
NSUInteger index;
NSArray * indexArray;
[[self logLine] addObject: message]; // logLine is my data source
index = [[self logLine] count]-1;
NSLog(#"%u", index);
indexArray = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:index inSection:0]];
[[self logView] insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
}
The insert works, but the new row takes around 5 seconds to be displayed in the UITableView !!
And when I put some log inside the tableView:cellForRowAtIndexPath: method, it's display immediately.
And when I put the FTPMessageReceived code inside a button (for example), the UITableView displays the new row immediately !
Any suggestion?

It sounds like FTPMessageReceived: is being called on a background thread. Try calling it on the main thread with:
dispatch_async(dispatch_get_main_queue(), ^{
[[self delegate] FTPMessageReceived:#"dir created"];
});

Related

collectionView nil when [self.collectionView reloadData] is called, reloadData not called

I'm making an app that pulls images from both Flickr, and Imgur using their API.
After populating my model that stores the image URL and the title of the image, I want to reload the UI so that, the images populate the collectionview, but when its called, collectionview is nil.
This delegate method is called in the class responsible for fetching images using the APIs.
-(void)refreshUIOnMainThread
{
photosFromWeb = [libraryAPI getPhotos];
if([self.delegate respondsToSelector:#selector(reloadUIAfterImageDownload:)]) {
[self.delegate reloadUIAfterImageDownload:photosFromWeb];
}
}
The delegate method is defined in ViewController.m, the class where the UICollectionView delegate functions should be called upon a call to reloadData.
-(void)reloadUIAfterImageDownload:(NSArray*)photosFromWeb
{
allPhotos = photosFromWeb;
NSLog(#"reloadUIAfterDelegate: Number of Photos in Photo Model: %lu\n",
(unsigned long)[allPhotos count]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"about to reload collectionview...\n");
//collectionview is nil, so reloadData is not called???????
[self.collectionView reloadData];
});
}
Originally I thought the photo Model array was 0, meaning 0 cells would populate.
-(NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
NSLog(#"numberofItemsInSection: Number of Photos in Photo Model: %lu\n",
(unsigned long)[allPhotos count]);
return ([allPhotos count]);
}
but that's not the case.
NSLog(#"reloadUIAfterDelegate: Number of Photos in Photo Model: %lu\n",
(unsigned long)[allPhotos count]);
returns a count of 128 indicating that Photo objects are there, and in the debugger I find the collectionview has a nil value within the delegate method definition. Why could this be?
Repo: https://github.com/danielv775/Flickr-Image-Gallery/tree/master/Photo%20Gallery
These functions are in FlickrClient.m and ViewController.m
Ah I see the issue now. It lies in this line of LibraryAPI.m:
flickrClient = [[FlickrClient alloc]init];
vc = [[ViewController alloc]init];
flickrClient.delegate = vc;
You're creating a new instance of your view controller, so none of the IBOutlets are set up on this new instance. Instead, you need to set your delegate from ViewController.m like so:
- (void)viewDidLoad {
[super viewDidLoad];
LibraryAPI *libraryAPI = [LibraryAPI sharedInstance];
libraryAPI.flickrClient.delegate = self;
}
This assumes you have a flickrClient property on your LibraryAPI. You could also add a delegate property on LibraryAPI if you wanted.
NOTE: You also want to change your delegate property on flickrClient like so:
#property (weak, nonatomic) id <FlickrClientDelegate> delegate;
Delegates should not maintain strong references.

_tableView reloadData not working when i called method from another class

This table view is working fine when tableview is firstly loaded
but when I tried to reload data using [_tableView reloadData], suddenly list won't reload at all.
Here's code:
-(void)loadListLoop{
AppDelegate* delegate = [[UIApplication sharedApplication] delegate];
shuffleLbl.hidden=false;
[self.view bringSubviewToFront:shuffleLbl];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSString*playlistID=delegate.gPlaylistID;
NSString*token=[ud stringForKey:#"youtube_token"];
NSString *origin = [NSString stringWithFormat: #"https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=%#&key=AIzaSyBeFK_llQHRl7TyXoQxGkLDmIfKGzOPezM&access_token=%#",playlistID,token];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:origin]];
NSData *json = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSArray *array = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:nil];
_titleList=[array valueForKeyPath:#"items.snippet.title"];
_thumbnailList=[array valueForKeyPath:#"items.snippet.thumbnails.default.url"];
_idList=[array valueForKeyPath:#"items.snippet.resourceId.videoId"];
NSLog(#"%#",_titleList);
[[NSRunLoop currentRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
dispatch_sync(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
}
The method loadListLoop is called from another class using:
PlaylistDetailViewController *playlistDetail = [[PlaylistDetailViewController alloc] init];
[playlistDetail loadList];
Looks like loadListLoop is successfully called and everything before [_tableView reloadData]; is also successfully loaded.
I put NSLog inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
to see is app is at least trying to reload data but it seems its not working at all.
EDIT:
first,view controller that contains "-(void)loadListLoop" is container view.so target view controller should be on screen
EDIT2:
i defined outlet at .h file below
#import <UIKit/UIKit.h>
#interface PlaylistDetailViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>{
//IBOutlet UITableView *tableView;
IBOutlet UIButton *shuffleLbl;
}
-(void)exitLoopVoid;
-(void)loadListLoop;
#property (nonatomic,strong) IBOutlet UITableView *tableView;
#property (nonatomic,retain) NSMutableArray *titleList;
#property (nonatomic,retain) NSMutableArray *authorList;
#property (nonatomic,retain) NSMutableArray *thumbnailList;
#property (nonatomic,retain) NSMutableArray *idList;
-(IBAction)shuffle;
#end
IMPORTANT
EDIT3:
looks like some thing very weird is happening to NSMutableArray.
overwrite NSMutableArray at loadListLoop method(works fine and checked content with NSLog)
reload table(seems this is working fine too)
content inside NSMutableArray will rollback to old content
anyone has idea about this issue?
EDIT4:
1.overwrite NSMutableArray at loadListLoop method(success)
2.reload table and NSMutableArray will be null only at this method
3.rollback to data that i overwrites data at loadListLoop
I see a couple of problems.
First, the code you posted is a method loadListLoop. But the code that you posted is calling a different method loadList. The two are not connected.
Second, you say:
the method "loadListLoop" is called from another class using
PlaylistDetailViewController *playlistDetail =
[[PlaylistDetailViewController alloc] init];
[playlistDetail loadList];
That code is very wrong. It is creating a brand new instance of PlaylistDetailViewController that is not on-screen, and invoking the loadList method on that newly created view controller.
The view controller hasn't had a chance to display itself yet, so it's view properties will be nil. Plus, the view controller probably doesn't have any data in the model structure it uses to populate it's table view, so it won't have anything to display.
Further, if your view controller's view structure is defined in a storyboard, you can't use alloc/init to create new view controller instances.
At the point where you're trying to call loadList/loadListLoop, is the target view controller on screen? You need to explain your calling sequence.
use NSNotificationCenter for Update the table from one class to another class
1.do this in from where you have to call.
[[NSNotificationCenter defaultCenter] postNotificationName: #"UpdateTable" object:nil userInfo:userInfo];
2.And use this one for table view class. Write this in ViewDidLoad method.
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(refresh_method)
name:#"UpdateTable"
object:nil];
3.
-(void)refresh_method
{
//reload table here.
}

Singleton UIViewController

I'm trying to add some data from a random class to my viewController,
So to keep always the same data, i did a singleton on my UIViewController, but it doesnt work i never get the data on my tableview.
this what i added to my UIViewController :
+(id)sharedMBVC {
static MBViewController *sharedMBVC ;
#synchronized(self) {
if (!sharedMBVC)
sharedMBVC = [[MBViewController alloc] init];
return sharedMBVC;
}
}
and from my class i call it by doing this :
MBViewController *vc = [MBViewController sharedMBVC];
Do i have to set somewhere the content of my NSArrays that they are declared in my viewDidLoad of the viewcontroller ? or there is something else to do.
PS : i was doing in my class before vc = (MBViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; but now my uiviewcontroller its not a rootview anymore, thats why im trying to find other way to access to it, and i guess the best solution is to do a singleton
Can u help me guys
OK, so the problem you have is that the viewController that displays the arrays also "owns" the arrays. This means that (with your current setup) to be able to change the arrays you need to get hold of the viewController to be able to access the arrays.
You need to change this by removing the arrays from that viewController.
You can still do this with a singleton (if you prefer) but create a brand new class called something like ArrayManager.
This will contain the arrays and ALL the methods for updating the arrays.
So for instance if your viewController has a method called - (void)addObjectToArray:(id)object; then move this method to the ArrayManager singleton class.
Now in your displaying viewController you can do...
[[ArrayManager sharedInstance] getSomeDataFromTheArray];
And in the place that has to update the array you can do...
[[ArrayManager sharedInstance] addObjectToArray:someObject];
Now you don't need to worry about passing the viewController around at all.
This can be improved with various things. For instance, you maybe don't need a singleton at all and can just take this ArrayManager class and inject it into the places that need it by setting a property etc...
Also, you could possibly use CoreData to store the information.
Also, your singleton method is not correct. The way recommended by Apple is to use...
+ (ArrayManager *)sharedInstance
{
static dispatch_once_t once;
static ArrayManager *arrayManager;
dispatch_once(&once, ^{
arrayManager = [[ArrayManager alloc] init];
});
return arrayManager;
}
Rewriting your singleton...
.h file
#interface PTVData : NSObject
+ (PTVData *)sharedInstance;
- (void)addSensor:(NSString *)sensorName;
- (NSInteger)numberOfSensors;
- (NSString *)sensorAtIndex:(NSUInteger)index;
#end
.m file
#interface PTVData ()
#property (nonatomic, strong) NSMutableArray *sensors;
#end
#implementation PTVData
+ (PTVData)sharedInstance
{
static PTVData *sharedPTVData;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPTVData = [[PTVData alloc] init];
});
return sharedPTVData;
}
- (id)init
{
if (self = [super init]) {
_sensors = [[NSMutableArray alloc] initWithObject:#"None"];
}
return self;
}
- (void)addSensor:(NSString *)sensorName
{
if (sensorName
&& ![self.sensors containsObject:sensorName]) {
[self.sensors addObject:sensorName];
}
}
- (NSInteger)numberOfSensors
{
return self.sensors.count;
}
- (NSString *)sensorAtIndex:(NSUInteger)index
{
return self.sensors[index];
}
#end
By doing this you hide the actual array of sensors. It is only directly accessible through the PTVData class.
Now in your tableview methods you can do...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[PTVData sharedInstance] numberOfSensors];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...
cell.textLabel.text = [[PTVData sharedInstance] sensorAtIndex:indexPath.row];
return cell;
}
Well, I think, these arrays don't belong to the MBViewController from architectural point of view. I would separate them to a data layer (DataSource class of some sort, for instance) and keep a reference to the DataSource everywhere you need. Have a look at the Second iOS App tutorial by Apple. It contains a simple example of data layer implementation.
UPDATE:
Also, check out Fogmeister's answer. He explains a possible implementation of such object rather well :)
As for why singleton didn't work as you expected in this case, I believe, the reason could be the following:
If you get to the MBViewController via a segue (which, I think, you are), then a new instance of MBViewController is created every time. If you access your arrays from MBViewController using self.myArray, then you access this new MBViewController's myArray. While sharedMBVC keeps a reference to the shared instance, it's just ignored by the segue.
in my ViewController.h
#property PTVData *ptvdata;
ViewController.m
ViewDidload
ptvdata = [PTVData sharedPTVData];
_sensorsCollection = ptvdata.sensorsCollection;
then i have a method in my ViewController.m
- (void) addSensorToCollection:( NSString *)sensorName{
[[PTVData sharedPTVData] addSensorToCollection:sensorName];
_sensorsCollection = ptvdata.sensorsCollection;
[ self.tableView reloadData];
}
}
My PTVData.h
#property (nonatomic,retain) NSMutableArray *sensorsCollection;
+(id)sharedPTVData;
-(id) init;
- (void) addSensorToCollection:( NSString *)sensorName;
#end
my PTVData.m
#synthesize sensorsCollection = _sensorsCollection;
+ (id)sharedPTVData {
static PTVData *sharedPTVData = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPTVData = [[self alloc] init];
});
return sharedPTVData;
}
- (id)init {
if (self = [super init]) {
_sensorsCollection = [[NSMutableArray alloc]initWithObjects:#"None", nil];
}
return self;
}
- (void) addSensorToCollection:( NSString *)sensorName{
if (![_sensorsCollection containsObject:sensorName]&& sensorName!= nil) {
[_sensorsCollection addObject:sensorName];
}
}
Instead of initializing your arrays in viewDidLoad, do it in sharedMBVC function. This will ensure that arrays are not re-initialized every time the view loads.

Update Label of a Custom Subclass of UITableViewCell after navigation return?

I am trying to update the score of one of the labels on my custom cell after returning from a push navigation.
In my parent UITableViewController I have the code:
In ViewDidLoad
//These are mutable strings
self.disciplineScoreString1 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreString2 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreString3 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreArray = [[NSMutableArray alloc] init];
[self.disciplineScoreArray addObject:self.disciplineScoreString1];
[self.disciplineScoreArray addObject:self.disciplineScoreString2];
[self.disciplineScoreArray addObject:self.disciplineScoreString3];
So far so good.
In cellForRowAtIndexPath
//this is the custom subclass of UITableViewCell
DayTableViewCell *cell = (DayTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//this is the custom label
cell.disciplineScoreLabel.text = [self.disciplineScoreArray objectAtIndex:indexPath.row];
return cell;
Still so far so good. Right now the label in each of the 3 cells is blank.
I now segue to a UIViewController from the first cell and I return from the child UIViewController successfully setting the string value of self.disciplineScoreString1 to #"10"which I NSLog'ged in the parent UITableViewController.
How do I update the label for the first cell now? I have tried reload data in ViewWillAppear but its not working.
Thankyou
EDIT
This is the code in the Child ViewController
In viewWillDisappear:
[super viewWillDisappear:animated];
[self calculateDisciplineScore];
NSInteger currentVCIndex = [self.navigationController.viewControllers indexOfObject:self.navigationController.topViewController];
DisciplineTableViewController *parent = (DisciplineTableViewController *)[self.navigationController.viewControllers objectAtIndex:currentVCIndex];
parent.disciplineScoreString1 = self.disciplineScoreText;
You must be changing the string, instead of modifying it... consider this example
NSMutableArray *array = [NSMutableArray array];
NSMutableString *disciplineScoreString1 = [NSMutableString stringWithFormat:#"original string"];
[array addObject:disciplineScoreString1];
[disciplineScoreString1 appendString:#" hello"]; // OK.. you're modifying the original string
NSLog(#"%#", [array objectAtIndex:0]);
disciplineScoreString1 = [NSMutableString stringWithFormat:#"new string"]; // WRONG!!
NSLog(#"%#", [array objectAtIndex:0]);
The ouput is:
2014-02-15 06:36:46.693 Hello[19493:903] original string hello
2014-02-15 06:36:46.694 Hello[19493:903] original string hello
The second example is wrong because you're creating a new string, and the object in the array is still pointing to the old original string.
EDIT:
// try this
[parent.disciplineScoreString1 replaceCharactersInRange:NSMakeRange(0, parent.disciplineScoreString1.length) withString:self.disciplineScoreText];
// instead of
parent.disciplineScoreString1 = self.disciplineScoreText;
Your current approach is quite rigid and not very standard. You would normally set up some kind of delegate to pass data from a child to a parent. It is very rarely correct that a child should know how to reach into the parent's innards and change it's data.
I would personally start with something like this
#interface ChildViewController : UIViewController
#property (nonatomic, copy) void (^onCompletion)(ChildViewController *viewController, NSString *disciplineText);
#end
Now I'm assuming you are dismissing by just calling [self.navigationController popViewControllerAnimated:YES] from within the child? This is a little odd as it means that the childViewController can now only ever be presented in a navigationController, this makes it less reusable.
What I would do is at the point where you normally call popViewControllerAnimated: I would call the completion instead
if (self.onCompletion) {
self.onCompletion(self, self.disciplineText);
}
Now the object who provides the onCompletion can decide how this controller get's removed and it's told about the data that we finished with, which enabled the controller to do what it wants with it.
In your case the parent controller would provide the completion as it knows how the child is presented so it will know how to dismiss it. It also know that it may want to do something with the data the child finished with
// Where you present the child
childViewController.disciplineText = self.self.disciplineScoreArray[index];
__weak __typeof(self) weakSelf = self;
childViewController.onCompletion = ^(ChildViewController *viewController, NSString *disciplineText){
weakSelf.disciplineScoreArray replaceObjectAtIndex:index withObject:disciplineText];
[self.navigationController popViewControllerAnimated:YES];
[weakSelf.tableView reloadRowsAtIndexPaths:#[ [NSIndexPath indexPathForRow:index inSection:0] ]
withRowAnimation:UITableViewRowAnimationFade];
};

iOS XYPieChart datasource methods not being triggered

I am trying to implement the XYPieCharts but am currently unable to get it to display. While debugging I placed some log statements in the data source methods but none of these are being fired.
I have included the delegate (not being used) and datasource as such
#interface DwinklySalaryViewController : UIViewController <UITextFieldDelegate, XYPieChartDelegate, XYPieChartDataSource>
#property (nonatomic, strong) IBOutlet XYPieChart *salaryPieChart;
I have also checked that I have linked up this IBOutlet to the XYPieChart element in the Interface Builder. There is no delegate/datasource outlet in the Interface Builder so I have added that programatically as in the demo. This, and the rest of the options are set in the viewDidLoad method of the a view controller that contains the XYPieChart view.
[_salaryPieChart setDelegate:self];
[_salaryPieChart setDataSource:self];
I have then implemented the required methods for the datasource protocol:
- (NSUInteger)numberOfSlicesInPieChart:(XYPieChart *)pieChart
{
NSLog(#"Number of slices: %d", pieChartSlices.count);
return pieChartSlices.count;
}
- (CGFloat)pieChart:(XYPieChart *)pieChart valueForSliceAtIndex:(NSUInteger)index
{
NSLog(#"Value slice: %d", [[pieChartSlices objectAtIndex:index] intValue]);
return [[pieChartSlices objectAtIndex:index] intValue];
}
- (UIColor *)pieChart:(XYPieChart *)pieChart colorForSliceAtIndex:(NSUInteger)index
{
NSLog(#"Returning %#", [sliceColors objectAtIndex:(index % sliceColors.count)]);
return [sliceColors objectAtIndex:(index % sliceColors.count)];
}
Could anyone tell me what I've missed out that causes the data source methods not to trigger?
It appears that with the way XYPieChart works, you have you call reloadData on it at startup. It works ok in either viewDidLoad or viewDidAppear.

Resources