UITableView scroll to refresh makes cells jump - objective-c

I have implemented pull to refresh in a table view that is a subview to my main view like so:
UITableViewController *tableViewController = [[UITableViewController alloc] init];
tableViewController.tableView = self.tableView;
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
tableViewController.refreshControl = self.refreshControl;
This updates the table and everything fine, the problem I'm having is when I touch the table to drag the cells seem to randomly shift up above the screen. This happens when I touch and drag from the middle of the screen down, or if I pull before the the cells shift back to their normal position. Here are some screen shots to better paint a picture. Keep in mind that all of these are taken after I drag down, yet the cells shift upward.
Should look like:
actually looks like these after the cells jump:

For anyone that may be having this issue or an issue like this I found a way to get the scroll to refresh to work very smoothly and not cause the issues I was having. I'm using an actual UITableViewController now instead of an embedded UITableView which helped with some of the scroll issues, but I still had an issue where instead of animating the closing of the refresh icon (table sliding up to cover the spinning circle) it just shut instantly and seemed very jarring. I put my refresh logic in the background, let it finish and then finished refreshing. My code structure is as follows:
- (void)viewDidLoad {
//set up refreshcontrol to call my refresh method
}
- (void)refresh {
[self performSelectorInBackground:#selector(getUpdatedInfo) withObject:nil];
}
Updated info runs and my TableViewController is a delegate of my class that gets refreshed information. When all the information is received and the update is completed I call the delegate method in my refreshing TableViewController class that puts the received array into the table, updates the table, and ends refreshing.
- (void)didFinishingUpdatingWithArray:(NSArray *)array {
//configure table sections
//Save the data
[self.tableview reloadData];
[self.refreshControl endRefreshing];
}

Related

Instant / Autosave in IOS

For background: I'm a Windows automation and data translation "expert" (or so they say grins) in my day job. I've been dabbling with Objective-C coding off and on since I bought my first Mac in 2004.
I'm working on an IOS app. My data container class knows how to save and load from disc, and each object responds to an instance method of -(void)saveToImpliedFilename{} or -(void)save:(NSString *)filename {}. There's a static call to load the data files from storage and create distinct data objects from them (they're fairly lightweight objects, so I'm not worried about loading several at a time). The app's domain is such that many of them won't ever be loaded at once anyway.
+(NSArray *)loadData {}
That's all working fine and wonderful. In storage the objects are stored as Xml and life is good.
Where I'm having trouble is when trying to modify the tutorials so that two things happen for me:
Quick note: I'm using the tutorial as a basis for POC coding, then I'll go back and start over with the "real" coding, reusing my data objects and some of the other utility I've built along the way.
Here's my list of goals and the issues:
I want the table view to tell the data objects to save at pretty much every "edit" event. The only one I can consistently get to work is reorganizing the table's order. (the save button and adding a new entry works fine)
entering a new entry into the list creates a nice modal editor with a save and a cancel button which work wonderfully. But if I edit an existing entry, I can't reproduce the save buttons' behaviors. Each time I try, the buttons' events no longer fire. I can't figure out where I'm going wrong.
I'm using the "Editable Table View" project from this tutorial series as my basis: http://www.aboutobjects.com/community/iphone_development_tutorial/tutorial.html
In the following code, the [self isModal] test is where the save/cancel buttons are made visible and wired up. Bringing up the new-entry screen is apparently the only time it's modal. I tried wiring this stuff up so that the buttons were created all the time, but again, the events never fire for either one. The next block below is where the editable table view is called explicitly with the NEW functionality, but the nonModal view of the same tableview is called by the select event on the selector table.
So...
// code snipped for the new/modal editor
- (void)viewDidLoad {
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
// If the user clicked the '+' button in the list view, we're
// creating a new entry rather than modifying an existing one, so
// we're in a modal nav controller. Modal nav controllers don't add
// a back button to the nav bar; instead we'll add Save and
// Cancel buttons.
//
if ([self isModal]) {
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(save)];
[[self navigationItem] setRightBarButtonItem:saveButton];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancel)];
[[self navigationItem] setLeftBarButtonItem:cancelButton];
}
// do stuff here to display my object...
}
// this code is called from the selection table to explicitly add a new data object.
- (void)add {
vhAddVehicleViewController *controller = [[vhAddVehicleViewController alloc] initWithStyle:UITableViewStyleGrouped];
id vehicle = [[Vehicle alloc] init];
[controller setVehicle:vehicle];
[controller setListcontroller:self];
UINavigationController *newNavController = [[UINavigationController alloc] initWithRootViewController:controller];
[[self navigationController] presentViewController:newNavController animated:YES completion:nil];
}
// this is where it's called on the table selection to show the same view without the save/cancel buttons.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
vhAddVehicleViewController *controller = [[vhAddVehicleViewController alloc] initWithStyle:UITableViewStyleGrouped];
NSUInteger index = [indexPath row];
id vehicle = [[self vehicles] objectAtIndex:index];
[controller setVehicle:vehicle];
[controller setTitle:[vehicle Vehiclename]];
[[self navigationController] pushViewController:controller animated:YES];
}
I'm assuming the issue is that presenting it makes it modal, where as pushing it doesn't...? That's fine. But when I take out the test for modal to try to keep the buttons working, no joy. The buttons draw and click when tapped, but the events don't fire.
HALP! :-)
Thanks much.
-- Chris (I logged in with my Google account so at the top of the page I'm showing as "user1820796") shrug
You forgot to call [super viewDidLoad];
Update
Try removing the cancel button that goes on the left side when pushing the view controller. See if save starts working. I think the problem is you should not add a left button to the navigation bar when the view controller is pushed.
Which method signature are you using?
- (void)save
{
NSLog(#"Saving");
}
Or
- (void)save:(id)sender
{
NSLog(#"Saving");
}
I still think this was related to push/popping the view rather than presenting the view. I switched it all to presentation and it's working how I want now.
Thanks for the assistance guys. Quite a different paradigm than I'm used to on the GUI stuff, but I'm getting there.
thanks!

Adding a SubView does not display before pushViewController

Similar to this question: Adding subview, gets delayed?
But I don't think you can pushViewController in a separate thread so is this really impossible?
Here is what I'm trying to do:
I have a TableView and when a cell is pressed, I want to call
[self.view addSubview:LoadingView]
to display an overlay with a spinner. Then I call
self.navigationController.navigationBar.hidden = NO;
[self.navigationController pushViewController:newGameViewController animated:YES];
However, the subview only displays for a split second (~1-4 seconds after the cell selection occurs while it waits for the new viewcontroller to initialize).
Is there any way to get some sort of loading indicator to occur at the instant the cell is selected?
Okay. What about this. In your didSelectRowAtIndexPath start your spinner (via addSubview ...) and start loading your stuff from the server. If that's finished remove the spinner an push your new view controller onto the stack. Make sure the user can't touch any other cell during that time. By the way. From a users perspective I'd find it mor intuitive if the new controller is loaded immediately and displays some waiting message.
Or the other way around: The newGameViewController displays the spinner and starts loading the data from the server in a background thread. When the data is complete, remove the spinner and display the data. That way the user could even go back if she doesn't want to wait.
You do not need to add code before pushing newGameViewController.
Inside viewDidLoad of newGameViewController, write the code of displaying spinner. To get the updated UI, just insert a delay before calling a web API.
inside GameViewController.m
-(void) viewDidLoad
{
[self.view addSubview:LoadingView];
[self performSelector:#selector(callWebAPI) afterDelay:0.1];
}
-(void) callWebAPI
{
//Handle network activity here..
}

UITableView Refresh Query

I'm using a UITableView control to show some data that can be edited by the user. To edit the details the user taps on an edit button which pushes a new view onto the stack. The user edits the data, taps a save button and the data is saved to a plist and the view popped off the stack. Even though the plist has been updated the UITableView still shows the old data. This can be corrected by adding a call to reloadData in the viewWillAppear method. However when the view is first loaded the data is displayed correctly, by adding the reload statement does this mean a double bind? If so, how can this be avoided?
I found the following code (here) which forces a refresh without explicitly calling reloadData:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
int orientation = [[UIDevice currentDevice] orientation];
if(orientation != UIDeviceOrientationUnknown)
[self willRotateToInterfaceOrientation:orientation duration:0];
}
Can anyone explain how/why this works?
The trick from your link is a dirty hack. Not only does it reload the data, but also forces the table to redraw. It tells your app that the device is getting a new orientation, so your table gets redrawn, along with other UI elements.
The standard way of refreshing a single row or a specific set of rows in your UITableView is calling its reloadRowsAtIndexPaths:withRowAnimation: method: doing so calls through to your data source to get data for only the row(s) that have been updated, preventing the full reload.
Do this:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//remove all objects from yourTableViewDataSourceArray
[yourTableViewDataSourceArray removeAllObjects];
//add new records from plist
yourTableViewDataSourceArray = plist request of data here
//reload table now
[yourtableView reloadData];
}

How to make a UITextView not take the focus when initializing?

I'm using code to create a detailed view pushed when you press a row of an UITableView, but theres a problem.
The detailed view contain an UITextView and when a detailedView is called (only first time) this make the UITableView row pressed to lose its pressed state. It shouldn't ! It should lose the pressed state only when returning from the detailed view to the list view.
As soon as I remove the UITextView from my code, no problem !
I think it's something like UITextView taking focus?
Is there any way to avoid this ? By subclassing or such?
Hmmm not seeing this in the sandbox I just wrote.
Created a simple navigation-based project.
Added a view controller to the project with XIB; added a UITextField to the XIB.
Made following code changes to the root view controller:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
in cellForRowAtIndexPath:
cell.text = #"Push me";
in didSelectRowAtIndexPath:
SimpleViewController *detailViewController = [[SimpleViewController alloc] initWithNibName:#"SimpleView" bundle:nil];
in viewDidLoad:
self.navigationItem.title = #"Home";
Selecting the "Push me" row highlights the row and pushes the SimpleViewController onto the stack. Selecting the "Home" back button pops the view off the stack, returning to the table view and deselecting/un-highlighting the selected row. This is true whether or not the textfield in the SimpleViewController is the first responder at the time of the back navigation.

Reload data from table view after come back from another view

I have a problem in my application. Any help will be greatly appreciated. Basically it is from view A to view B, and then come back from view B.
In the view A, it has dynamic data loaded in from the database, and display on the table view. In this page, it also has the edit button, not on the navigation bar. When user tabs the edit button, it goes to the view B, which shows the pick view. And user can make any changes in here. Once that is done, user tabs the back button on the navigation bar, it saves the changes into the NSUserDefaults, goes back to the view A by pop the view B.
When coming back to the view A, it should get the new data from the UIUserDefaults, and it did. I used NSLog to print out to the console and it shows the correct data. Also it should invoke the viewWillAppear: method to get the new data for the table view, but it didn't. It even did not call the tableView:numberOfRowsInSection: method. I placed a NSLog statement inside this method but didn't print out in the console.
As the result, the view A still has the old data. the only way to get the new data in the view A is to stop and start the application.
Both view A and view B are the subclass of UIViewController, with UITableViewDelegate and UITableViewDataSource.
Here is my code in the view A :
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"enter in Schedule2ViewController ...");
// load in data from database, and store into NSArray object
//[self.theTableView reloadData];
[self.theTableView setNeedsDisplay];
//[self.theTableView setNeedsLayout];
}
In here, the "theTableView" is a UITableView variable. And I try all three cases of "reloadData", "setNeedsDisplay", and "setNeedsLayout", but didn't seem to work.
In the view B, here is the method corresponding to the back button on the navigation bar.
- (void)viewDidLoad {
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:#selector(savePreference)];
self.navigationItem.leftBarButtonItem = saveButton;
[saveButton release];
}
- (IBAction) savePreference {
NSLog(#"save preference.");
// save data into the NSUSerDefaults
[self.navigationController popViewControllerAnimated:YES];
}
Am I doing in the right way? Or is there anything that I missed?
When a view is first loaded, it calls the viewDidLoad method. If you create a stack, drill down into it (from A to B) and then return (B to A) the viewDidLoad does not get called again on A. What you may want to try is passing A into B (by passing in self) and call the viewDidLoad method to get the new data and then reloadData method on the the tableView to refill the table view.
What you may want to try is taking the data fetching and setting functionality out of the viewDidLoad method and place it in its own getData method. At the end of the getData method, you could place a [self.tableView reloadData]; to reset/refill the table view. From class B, you could call [self getData] and minimize the amount of work you would do in class B. This would help increase reuse-ability of that code and may prevent side effects from calling the viewDidLoad method.
You could also use viewDidAppear. It is called every time the screen appears. For performance reasons, set a flag so you don't repeat the same functionality in viewDidLoad with viewDidAppear for the first screen view.