Weird Scrolling Issue Using UITableView - iphone

I am a working UINavigationController pushing two different UITableViews on and off the stack. All of my functionality is working correctly except scrolling. When either table is scrolled above the top row, or below the bottom row, it stays there exposing the margin above/below the table. I am looking for the table to "bounce" back so that only the table is visible and not the white space area beyond - just like any other iPhone app.
One of my UITableViews is being loaded by NIB and the other is being created programatically - both of which have the exact same result. I have tried all the bounce and scrolling settings in the Nib, but nothing seems to work.
Can someone tell me what I am doing wrong? Please let me know if I can be more specific in detailing my problem.
Thanks,
-Scott
I wanted to add a bit more information since I am still unable to figure out this problem. I should point out that the app I am trying to add this NavigationController to is NOT the main view. I have a SettingsViewController that uses a Nib to get loaded. Within that controller, I am wanting to create a new navigation based set of views. So From inside my SettingsViewController, if the user taps a button, I...
Within my button selector:
TextListViewController *controller = [[TextListViewController alloc] initWithStyle:UITableViewStylePlain];
controller.title = #"Test Table View";
navController = [[UINavigationController alloc] initWithRootViewController:controller];
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:navController animated:YES];
[controller release];
The above code works perfectly in every way except scrolling. I am able to process all the delegate methods and push new controllers onto the stack. The ONLY issue I am having is that the root controller (and every other controller pushed) will not scroll correctly.
In every case, it allows me to scroll past the top and bottom of the table and does not snap back like it should. It also does not have a free-wheel feel to it when you flick scroll. Instead, it stops as soon as you remove your finger no matter how fast you scrolled. Very odd.
I have tried creating another project using the Navigation template - and of course it works fine - but the template assumes the NavigationController's view is attached all the time - which is not my case. I am creating all of this on the fly within a standard ViewController.
Thanks for your help Kevin, but I am still looking for some help here.
Thanks,

Well, I fixed the issue and it had nothing to do with how I was adding the UITableView to the UINavigationController or in how I was instantiating the view. It turns out it was completely unrelated to any of that.
I am using a tight while loop to process my game loop. I needed a way to process OS messages in the loop to keep touches and other events from being blocked. Here is what I was using in the middle of my loop:
while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, TRUE) == kCFRunLoopRunHandledSource);
Up until this scrolling problem, I had no problems with this implementation at all. For whatever reason, this is breaking the scrolling functionality with UITableView.
I changed my game loop so the above code is not needed and everything started working.
Thanks...

Your UITableView is a UIScrollView, whose property bounces is the key. Here's the reference. By default it is YES, though. Did you check this one?

Your question and comments seem to suggest that you are attaching a UITableView to your UINavigationController in some way other than by pushing a UITableViewController. If you're not doing it that way, you really should.
In general, if you can push a child view controller onto a UINavigationController, then you can also use the child view controller by itself just to test it out. Try making your UITableViewController's view the root view of your window, and see if that does what you expect. That way you can isolate the problem to either the table view part or the navigation part.

I've been having the same issue as you which as you state above was related to the way you were calling the runloop:
while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, TRUE) == kCFRunLoopRunHandledSource);
Seems like DefaultMode is the wrong thing to be waiting on, I changed my code to wait on UITrackingRunLoopMode and the TableView worked correctly. In my case I used:
[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate distantFuture]];
And everything worked correctly.

Related

Iphone UIView switching: Previous view's subviews polluting newly switched-to view

I've been working for a while now on switching UIViews, and am trying a basic UIView switch, without using UINavigationController. I've looked into UINavigationControllers, but I have been wrestling with the below for so long that I want to explore it fully.
My project organisation is:
MainViewController.h/.m -> viewDidLoad: This method runs a NSTimer to update self.view with a few bits and bobs.
ConfigView.h/.m: -> viewDidLoad: nothing - just an idle view.
MainViewController.xib -> Main window NIB
ConfigView.xib -> ConfigView NIB
I have the following method in MainViewController.m execute on a UIButton press event:
- (IBAction)switchToConfigView:(id)sender {
if(self.configViewController == nil) {
ConfigView *cv = [[ConfigView alloc] initWithNibName:#"ConfigView" bundle:nil];
self.configViewController = cv;
[cv release];
}
[self.view addSubview:configViewController.view];
}
Now, when I switch to the ConfigView, the view is indeed displayed, but it appears that viewDidLoad: from MainViewController still executes, causing both views to effectively be merged. From the code, I can see that this is obviously the case, as the actual view switch itself is performed within the MainViewController context, and as you can see, simply a subview.
My question is, how do I neatly tuck away/pause all goings-on within MainViewController when switching the view by just adding another subview to it? My guess is that it's not possible, or rather it's a lot of leg work where some other methodology would be better applied. I suspect I need to implement the actual switch from the ConfigView.h/.m but I could be wrong.
Any light you can shed on the above would be grand.
thanks
swisscheese.
My suggestion is:
1) have a base, empty view;
2) add a subview to it; this would be your actual first view;
3) when you want to switch, remove the first subview from the base view and add the second one as a subview;
4) when you want to switch back, remove the second one and add the first one as a subview to the base view;
you can easily handle as many view as you like. it works.
alternatively, you can hide the view instead of removing it.
Believe me adding a whole new sub-view on another view is really a massive task and later on when the project becomes big it becomes a painful task to switch views.
This is my own personal experience as I did this big mistake in one of my previous project.
In all cases navigation control is the best choice for switching views rather than adding sub-views.
Please tell me that what difficulties are you facing in Navigation control and I can help about this.
Thanks,
If the NSTimer you've set up in MainViewController's viewDidLoad method is adding subview's to mainViewController's view with the addSubview method, those views will be placed on top of all other subviews, i.e. on top of the config view. So, you might try either invalidating the timer when presenting the config view.
You should really just use a UINavigationController, or present the config view controller with presentModalViewControllerAnimated. Why don't you want to?

Multiple View Controllers. Is there a maximum?

I am putting an iPad application together that allows a user to work their way through a virtual tour. They are able to move forward through screens on which some will have buttons to other material such as a video or more info.
If Keynote supported Hyperlinks then it would be well suited but as it doesn't I am trying to recreate the tour within Xcode.
I am a newbie but have spent time researching and have code to display the 'slides' and the capability to move forward and back through them. The slides are no more that an image view with a full screen graphic and buttons for the various options, some slides are simple and have nothing other than back and forward but others will have additional links
However doing it in this simplistic way means I am ending up with a huge number of view controllers and XIB files, currently at 75 which I know must be more than any app should have. However it does work although on occasions when running it on the device and not in the simulator it will bomb out.
My questions are is there a limit to the number of view controllers in one app and will having a large number cause the instability? I'm aware of other ways to handle the views such as having them in arrays and pushing them out a single view controller but this won't give me the flexibility to tailor slides for different content.
I'd welcome any help or advice and I hope have gone about posting this question in the right way (its my first)
Many Thanks
Kieron
The code I am using to manipulate the view is
-(IBAction)goBack {
[self dismissModalViewControllerAnimated:NO];
}
-(IBAction)goForward {
Slide5ViewController *screen = [[Slide5ViewController alloc] initWithNibName:nil bundle:nil];
screen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:screen animated:YES];
[screen release];
}
Kieron,
Why not have one "slide" view controller and a different image only? Use some sort of data structure to keep information about the buttons, images, and pathways for each slide, and then just keep re-using the same view controller for each slide?
The view controller can then dynamically load each image as it transitions between the currently visible view and the next instantiation of itself... It should be possible using only 1 view controller.
If you're comfortable with using Interface Builder, keep using the XIB files to lay everything out. However, instead of setting each "File's Owner" to a different view controller, set them all to the same one. Then, inside your IBAction methods (when the user pressed a button), use some logic to say "I am on this view right now, and the user pressed this button, so which one should I go to next?"
Then, call a method like loadNewSlide: that might look like this:
- (void) loadNewSlide:(NSInteger)slideNumber
{
// Make a string with the new XIB name
NSString* xibName = [NSString stringWithFormat:#"slide-%d",slideNumber];
// Create the next slide view controller (it doesn't matter if you create a slide view
// controller from within another slide view controller, remember, they are all just
// objects)
SlideViewController *newSlideViewController = [[SlideViewController alloc] initWithNibName:xibName bundle:nil];
// Change the view
UIWindow *theWindow = [self.view superview];
[self.view removeFromSuperview];
[theWindow addSubview:newSlideViewController.view];
// Release, the view stack now should be retaining the view controller instead
[newSlideViewController release];
}
This will work MUCH better than running "modally" with 75 view controllers (as you had previously suggested) because this will only keep 1 slide in memory at a time - whatever you are currently looking at - and then will load the next slide just in time to move to it.
Fist of all, what error is in the log?
Did you properly implemented viewDidUnload method of view controllers? View controllers should be able to unload loaded xib. Also, release data in didReceiveMemoryWarning.
Second, it could be better to use UINavigationController to handle view controllers stack instead of modal view controllers stack. You can hide navigation bar or customize it.

Two Views in 1 view controller

Basically, what I am trying to do is I have my TestViewController and there are 2 views under it, the first view will start with a tool bar and 2 buttons, one for imagepicker and the other one for mail sending, I am already done with that, and after pick the image from the photo library, the second view will be popped out(I want to do some image editing here) with a tool bar and 1 button back to the first view. I am doing something like [self.view addSubview:2ndview], and it works, but when I am trying to use -(IBAction)dismissDrawing:{[2ndview removeFromSuperview] }the program just shut down.
Can anyone help me? Really appreciate your help!
Look at my sample application I did last year. Basically you need a view that will act like a superview, then add views on top of it (if I remember correctly, it's been a while since I did it that way, now I just use multiple xibs).
It sounds like what you're trying to do would be better suited to a modal view.
See Human Interface Guidelines - Modal Views
Instead of adding a subview, try
[self presentModalViewController:secondView animated:YES];
Then in the dismissDrawing you can use
[self dismissModalViewControllerAnimated:YES];

UISplitViewController and complex view hierarchy

I'm doing an iPad tech demo and I'm running into a serious technical problem.
I have an app concept that leverages UISplitViewController, but NOT as the primary controller for the entire app.
The app flow could be described roughly as this:
Home screen (UIViewController)
List->Detail "Catalog" (UISplitViewController)
Super Detail Screen (UIViewController but could conceivable also be a child of SplitView).
The problem is in the flow between Home and Catalog. Once a UISplitViewController view is added to the UIWindow, it starts to throw hissy fits.
The problem can be summarized at this:
When a UISplitView generates a popover view, it appears to then be latched to its parent view. Upon removing the UISplitView from the UIWindow subviews, you will get a CoreGraphics exception and the view will fail to be removed.
When adding other views (presumably in this case, the home screen to which you are returning), they do not autorotate, instead, the UISplitView, which has failed to be removed due to a CG exception, continues to respond to the rotation instead, causing horrible rendering bugs that can't be just "dealt with". At this point, adding any views, even re-adding the SplitView, causes a cascade of render bugs.
I then tried simply to leave the SplitView ever present as the "bottom" view, and keeping adding and removing the Home Screen from on top of it, but this fails as SplitView dominates the Orientation change calls, and Home Screen will not rotate, even if you call [homeScreen becomeFirstResponder]
You can't put SplitView into a hierarchy like UINavigationController, you will get an outright runtime error, so that option is off the table. Modals just look bad and are discourages anyway.
My presumption at this moment is that the only proper way to deal with this problem is so somehow "disarm" UISplitViewController so that it can be removed from its parent view without throwing an unhandled exception, but I have no idea how.
If you want to see an app that does exactly what I need to do, check out GILT Groupe in the iPad app store. They pulled it off, but they seem to have programmed an entire custom view transition set.
Help would be greatly appreciated.
Apple states:
The split view controller’s view
should always be installed as the root
view of your application window. You
should never present a split view
inside of a navigation or tab bar
interface.
This does mean it should be root view and not subview of another view. Even though they add:
You should never present a split view inside of a navigation or tab bar interface
That does not mean you can add it as a subview of any other controller either. (sorry)
I have a feeling that what you are experiencing is the byproduct of trying to do so. I am actually surprised that GILT Groupe's app did not get rejected. Apple has a tendency to enforce these HIG guidelines rather strictly lately. They (as you found out already) cause a rather nasty runtime error when you attempt to add them to a NavigationController.
I've solved this for myself... actually worked around... by presenting all other possible full screen views as modals of the SplitView...
This is an unsavory way of doing things in my book, but Apple leaves you little choice if you want to leverage a SplitView only "sometimes" within an app.
I had some success by creating a second UIWindow. I associate the UISplitViewController with that, and switch it out with the main window when I want to show the splitview. It seems to work they way I wanted, except for a slight delay in rotations and a log message about "wait_fences".
Unless your developing for jail-broken devices then bending apples rules/wishes isn't a good idea. Like Jann and Jasconius state above this means keeping a splitView controller view root, not over-using modals (vague) and not using multiple windows.
Also, the Gilt app is only available in the US
I'v been trying to find a solution too and have ended up programatically removing views from the window like Tuannd talks about but the landscape rendering bug is unforgivable.
#Jasconius, What is the max number of modals are you are presenting at any time?
I am struggling with this same issue. I've been trying various things poking at the UISplitViewController as a black box and see how it reacts.
I seem to have come up with a solution to my case which seems to be working satisfactorily.
The key appears to be that the first view added to the UIWindow is the only view properly initialized. All the problems I've had tend to stem from incorrect notification of the orientation of the device. The first view added, apparently has this correctly configured.
In my case I didn't want the UISplitView as the first view. The following is working for me.
The app delegate application:didFinishLaunching method is special. Adding the view to the UIWindow must occur here. If it is done elsewhere it does not get configured properly.
Essentially the magic sauce, is have the split view be the first view added to the window. Its then ok to remove it as long as you retain the UISplitViewController. From then on you can swap other views in and out, including the UISplitView and most things seem to be ok.
I've still run into a few issues. Popovers on views other than the split view are confused on the views frames and location of toolbar buttons and will display in the wrong location. I place then in a specific location and that seems to handle that case.
If a popover on the split view is still displayed, and you try to view another view, the orientation of the second view is confused and shows up sideways. If that view is accessed before the popup is displayed, all is well. I've fixed this my manually dismissing the popover before switching to any other view.
Here's the code if it helps. All the controllers are instance variables of appDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// This also seems to work as good magic. Seems to set orientation and size properties that persist.
[window addSubview:splitViewController.view];
[splitViewController.view removeFromSuperview];
[self switchToNewViewController:firstController];
[window makeKeyAndVisible];
return TRUE;
}
- (void)switchToNewViewController:(UIViewController *)newViewController {
[popoverController dismissPopoverAnimated:FALSE];
if (newViewController != currentViewController) {
[currentViewController removeFromSuperview];
currentViewController = newViewController;
[window addSubView:newViewController.view];
}
}
Just wanted to say that I was running into these same issues, found this forum topic, and followed the advice from g051051 above. This is working perfectly for me. I am not seeing any glitch, and no messages about wait_fences in the console of the device.
I simply used IB to create two UIWindow objects in the main XIB, created as normal the UISplitViewController and then also an instance of my other controller derived from UIViewController (which I am using for full screen display). I have simply hooked them up by attaching the rootViewController for each UIWindow to its appropriate controller.
In application:didLaunch...: method I can decide which window to send the makeKeyAndVisible method and which to set to hidden. When the user want to switch back and forth I simply have to send makeKeyAndVisible to one and set the hidden property on the other, that's all there is to it.
As indicated all of the rotation related messages are sent to each controller appropriately, regardless of which one is associated with the currently visible window.
Anyway, works great for me, and actually quite easy to set up.

Why is self.navigationItem.hidesBackButton not working?

I have a UIViewController that is pushed onto a UINavigationController and is currently displayed. When I go to start some asynchronous task inside the view controller, I can set hidesBackButton on self.navigationItem to YES, and the back button is hidden correctly.
As soon as the task is finished, and I set hidesBackButton back to NO (on the UI thread, I might add, I've made sure of this), nothing happens. The back button remains hidden.
Has anyone seen this before? What drives me especially crazy is that in my application (the same application), in a different UINavigationController hierarchy, the exact same code works correctly!
Are you calling hidesBackButton = NO from a thread? All UI operations should be done on the main thread, otherwise they won't have any effect.
i have not been able to replicate your problem on my machine. however, i faced a similar issue with tableviews even when i was updating my ui on the main thread. but calling setNeedsDisplay fixed that issue.
Can you try this and see if this works:
[self.navigationController.navigationBar setNeedsDisplay];
I guess this should work, you need to do the same, BUT ON THE NAVIGATIONBAR instead. please let me know if this worked - as i cannot test my solution because i never get this problem :-)
Have you tried forcing the view to refresh by calling setNeedsDisplay?
Maybe the OS is not picking up the changes instantly and you need to force it.
Have you tried using the setHidesBackButton:animated: method instead? Perhaps that has a slightly different behavior.
In my case I simply had to give a title to the view, as in:
self.navigationItem.title = #"Menu";
Marinus
I have had a similar issue recently. I tried literally everything I found in SO and other forums- nothing worked.
In my case there was a modally shown UINavigationController with a simple root controller which would push one of two view controllers (A and B) on top of the controller stack when the button A or B was pressed, respectively. Controller B was the one which was not supposed to show the back button. But still, sometimes it did, sometimes it didn't.
After hours of debugging, I managed to track it down. Controller A was a UITableViewController. Each time I selected a cell in this controller, the delegate would pop Controller A off the stack. BUT. I made use of a UISearchDisplayController as well. Turned out that popping the view while the search controller was still active messed up something in the navigation controller that made it impossible to hide the back button in Controller B afterwards (well, it eventually stayed hidden between viewDidLoad and viewDidAppear: but then it always turned visible).
So the solution (rather workaround) was adding this line to where Controller A was dismissed:
controllerA.searchDisplayController.active = NO;
// ...
// [self.navigationController popViewControllerAnimated:YES];
Hope this spares someone a couple of hours.