UISplitViewController non root, forcing custom rotation methods, makes master view dissappear - objective-c

I'm trying to add a split view inside of a tab bar, and since the split view isn't the root, it doesn't properly get the rotation notifications, so the delegate's methods are never called to add the button to the toolbar in the detail view.
I've rigged it up so I can generate the popover when rotated, but when this method is called, the view dissappears from the landscape mode, and if you activate it and then rotate back into landscape, it's a black empty box where the master view used to be. How do I get rid of this occuring?
-(void) displayPopover:(id)sender
{
//Toggle the popover: if it's showing, hide it
if (popoverController != nil && [popoverController isPopoverVisible])
{
[popoverController dismissPopoverAnimated:NO];
}
else
{
//Create a Popover displaying the master view
if (popoverController == nil)
{
popoverController=[[UIPopoverController alloc] initWithContentViewController:self->rootController];
popoverController.popoverContentSize=CGSizeMake(300, 500);
}
[popoverController presentPopoverFromBarButtonItem:[detailController.toolbar.items objectAtIndex:0] permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}

You will need to remove all the objects from window using:
[appdelegate window ] subviews] objectAtIndex:0] removeFromSuperview];
Then add your splitview to the window, you can get the view callbacks.

I would recommend either finding a way to get your SplitViewController to be root, or creating a custom subclass of the UISplitViewController that allows for non-root placement.
I really like what Matt Gemmell did here: http://mattgemmell.com/2010/07/31/mgsplitviewcontroller-for-ipad
Using a custom subclass like Matt's will allow you to benefit from all the same delegate methods that a SplitView as root would allow. I used it in a project where I wanted my SplitView to appear as a modal - almost impossible with a traditional UISplitViewController.

so your split view has rotation enabled (shouldAutorotateToInterfaceOrientation:) now you have to make sure that the tab controller has also rotation enabled (should be the appDelegate, am I right?) AND you have to make sure that every other view that is in your TabBar has also rotation enabled!
so if your TabBar contains 2 tabs you have to set the rotation in 3 classes.

Related

After bouncing of table to top App get crash [duplicate]

Here's how the scroll views work: One scroll view is paging enabled in the horizontal direction. Each 'page' of this scroll view contains a vertically scrolling UITableView. Without modification, this works OK, but not perfectly.
The behaviour that's not right: When the user scrolls up and down on the table view, but then wants to flick over to the next page quickly, the horizontal flick/swipe will not work initially - it will not work until the table view is stationary (even if the swipe is very clearly horizontal).
How it should work: If the swipe is clearly horizontal, I'd like the page to change even if the table view is still scrolling/bouncing, as this is what the user will expect too.
How can I change this behaviour - what's the easiest or best way?
NOTE For various reasons, a UIPageViewController as stated in some answers will not work. How can I do this with cross directional UIScrollViews (/one is a table view, but you get the idea)? I've been banging my head against a wall for hours - if you think you can do this then I'll more than happily award a bounty.
According to my understanding of the question, it is only while the tableView is scrolling we want to change the default behaviour. All the other behaviour will be the same.
SubClass UITableView. UITableViews are subClass of UIScrollViews. On the UITableView subClass implement one UIScrollView's UIGestureRecognizer's delegate method
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
//Edit 1
//return self.isDecelerating;
//return self.isDecelerating | self.bounces; //If we want to simultaneous gesture on bounce and scrolling
//Edit 2
return self.isDecelerating || self.contentOffset.y < 0 || self.contentOffset.y > MAX(0, self.contentSize.height - self.bounds.size.height); // #Jordan edited - we don't need to always enable simultaneous gesture for bounce enabled tableViews
}
As we only want to change the default gesture behaviour while the tableView is decelerating.
Now change all 'UITableView's class to your newly created tableViewSubClass and run the project, swipe should work while tableView is scrolling. :]
But the swipe looks a little too sensitive while tableView is scrolling. Let's make the swipe a little restrictive.
SubClass UIScrollView. On the UIScrollView subclass implement another UIGestureRecognizer's delegate method gestureRecognizerShouldBegin:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
if (abs(velocity.y) * 2 < abs(velocity.x)) {
return YES;
}
}
return NO;
}
We want to make the "swipe is clearly horizontal". Above code only permits gesture begin if the gesture velocity on x axis is double than on y axis. [Feel free to increase the hard coded value "2" if your like. The higher the value the swipe needs to be more horizontal.]
Now change the `UiScrollView' class (which has multiple TableViews) to your ScrollViewSubClass. Run the project. :]
I've made a project on gitHub https://github.com/rishi420/SwipeWhileScroll
Although apple doesn't like this method too much:
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
I've found a great way to accomplish this.
This is a complete solution for the problem. In order to scroll the UIScrollView while your UITableView is scrolling you'll need to disable the interaction you have it.
- (void)viewDidLoad
{
[super viewDidLoad];
_myScrollView.contentSize = CGSizeMake(2000, 0);
data = [[NSMutableArray alloc]init];
for(int i=0;i<30;i++)
{
[data addObject:[NSString stringWithFormat:#"%d",i]];
}
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.view addGestureRecognizer:tap];
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
[_myTableView setContentOffset:_myTableView.contentOffset animated:NO];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = YES;
}
To sum up the code above, if the UITableView is scrolling, set userInteractionEnabled to NO so the UIScrollView will detect the swipe. If the UITableView is scrolling and the user taps on the screen, userInteractionEnabled will be set to YES.
Instead of using UIScrollView as a container for these multiple table views, try using a UIPageViewController.
You can even integrate this into your existing view controller setup as a child view controller (directly replacing the UIScrollView).
In addition, you'll likely want to implement the required methods from UIPageViewControllerDataSource and possibly one or more of the methods from UIPageViewControllerDelegate.
Did you try the methods : directionalLockEnabled of both your table and scroll and set them up to horizontal for one and vertical for the other ?
Edit :
1)
What you want to do is very complicate since the touch wait some time (like 0.1s) to know what your movement will be. And if your table is moving, it will take your touch immediately whatever it is (because it's suppose to be reactive movement on it).
I don't see any other solution for you but to override touch movement from scratch to detect immediately the kind of mouvement you want (like if the movement will be horizontal) but it will be more than hard to do it good.
2)
Another solution I can advise you is to make your table have left and right margin, where you can touch the parent scroll (pages thing so) and then even if your table is scrolling, if you touch here, only your paging scroll will be touched. It's simpler, but could not fit with your design maybe...
Use UIPageViewController and in the -viewDidLoad method (or any other method what best suits your needs or design) get UIPageViewController's UIScrollView subview and assign a delegate to it. Keep in mind that, its delegate property won't be nil. So optionally, you can assign it to another reference, and then assign your object, which conforms to UIScrollViewDelegate, to it. For example:
id<UIScrollViewDelegate> originalPageScrollViewDelegate = ((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]).delegate;
[((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]) setDelegate:self];
So that you can implement UIScrollViewDelegate methods with ease. And your UIPageViewController will call your delegate's -scrollViewDidScroll: method.
By the way, you may be obliged to keep original delegate, and respond to delegate methods with that object. You can see an example implementation in ViewPagerController class on my UI control project here
I faced the same thing recently. My UIScrollview was on paging mode and every page contained a UITableView and like you described it worked but not as you'd expected it to work. This is how solved it.
First I disabled the scrolling of the UIScrollview
Then I added a UISwipeGestureRecognizer to the actual UITableView for left and right swipes.
The action for those swipes were:
[scroll setContentOffset:CGPointMake(currentPointX + 320, PointY) animated:YES];
//Or
[scroll setContentOffset:CGPointMake(currentPointX - 320 , PointY) animated:YES];
This works flawlessly, the only down side is that if the user drags his finger on the UITableVIew that will be considered as a swipe. He won't be able to see half of screen A and half of screen B on the same screen.
You could subclass your scroll view and your table views, and add this gesture recognizer delegate method to each of them...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
I can't be sure this is exactly what you are after, but it may come close.

Changing view repositions them at (0,0)

I created a simple window with the purpose of being something like a "Wizard" (I know Apple guidelines basically forbid you to, I tried to convince the customer, but whatever.
It is just a simple view with two Custom Views inside, one in the bottom part which contains a "previous" and "next" button, and a bigger view at the top which takes most of the space.
I called the bottom view "NavigationView" and the top one "ContainerView".
I created an array to hold a series of views the user is supposed to navigate through with the "next" and "previous" buttons.
So, here's my code.
- (IBAction) next:(id)sender{
currentViewIndex++;
[self animatePushView:YES];
}
- (IBAction)previous:(id)sender{
currentViewIndex--;
[self animatePushView:NO];
}
- (void) animatePushView:(BOOL)forward{
NSView *nextView = [viewCollection objectAtIndex:currentViewIndex];
for (NSView *subView in [containerView subviews]) {
[subView removeFromSuperview];
}
[containerView addSubview:nextView];
[nextView setFrame:containerView.bounds];
[containerView setNeedsDisplay:YES];
}
It's pretty straightforward I think. I have an array which contains the next view to be displayed.
What happens is that I find the next view centered in the lower left part of the ContainerView. Why does this happen?
Also, as you may have guessed, I'm a newbie at managing views, even though I've been working on objective-c for quite some time, so if there's some best practice I'm missing I'm open to suggestions.
Thanks!
EDIT:
I forgot to add:
Some of these views have different sizes, and I would like to be able to change the window size according to the view size.
[nextView setFrame:containerView.bounds];
You are assigning container view bounds to the next view frame (doc).
What you probably want is assigning the current view frame to the next view frame, and possibly adjust width and height.
Keep a reference to the current displayed view, something like this (_currentView is an ivar of type NSView *) :
- (IBAction) next:(id)sender{
currentViewIndex++;
[self animatePushView:YES];
}
- (IBAction)previous:(id)sender{
currentViewIndex--;
[self animatePushView:NO];
}
- (void) animatePushView:(BOOL)forward{
NSView *nextView = [viewCollection objectAtIndex:currentViewIndex];
[nextView setFrame:_currentView.frame];
[_currentView removeFromSuperview]; // _currentView is retained in the collection
[containerView addSubview:nextView];
_currentView = nextView;
[containerView setNeedsDisplay:YES];
}
Ok, I figured it out.. Finally..
The problem was that the first view I had to show was already contained in the container view in the .xib file.
I don't really know why, but it probably caused some problem with the retain count of the container view, because it was released on the first click.
Releasing the container view would reposition the view on (0,0) probably because its frame was null, and the view would flash because it wasn't retained correctly.
Removing the view from the .xib file and adding it via code works properly anyway.

Mimic modal window to allow for tapping outside of modal window

I am using a Split View Controller and showing a modal window when a button is tapped in the master pane. I need to be able to dismiss the window when the user taps outside of the bounds of the window.
I am currently using presentViewController, which I have read does not allow for taps outside of the window.
I think I need to present a view controller myself and setup a gesture recognizer to handle the closing from there... the trouble is, I don't know where/how to present the view controller or where to attach the gesture recognizer to in an SVC.
I setup my view controller like this:
SearchViewController *searchViewController = [[SearchViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:searchViewController];
[navController setNavigationBarHidden:NO];
From there I don't know how to make the view controller appear without using presentViewController. I have tried the following (each separately):
[[self navigationController] addChildViewController:navController];
[self.view.window.rootViewController addChildViewController:navController];
[self.presentingViewController addChildViewController:navController];
[self.presentingViewController.presentingViewController addChildViewController:navController];
How do I present my navcontroller, and which view would I add a gesture recognizer to?
A UIPopoverViewController will work.
Alternatively, you can add a child view controller to your RootViewController. . (Check out UIViewController containment for lifecycle handling).
Basically just this in your root view controller:
- (void) presentSemiModalViewController
{
//Tint-out the background or blur it with some effect
_semiModalViewController = viewController;
//Choose the frame you'd like to use here, and an animation you'd like to use to present it
[self.view addSubView:_semiModalViewController.view];
[_semiModelViewController willMoveToParentViewController self];
}
If the RootViewController is not yours (eg a UINavigationController) you can use a category, but to retain the _semiModalViewController you'll need to use an associative reference (ie add a "property" to the category to store the modal VC while its in use). For info on that see here: Associative References Info
PS: You might want to choose a better name my "semi-modal", but you get the idea ;)

AutoLayout won't resize view when rotating in a custom container view?

I have a very basic container view that contains a sidebar and swaps out the view controllers in the content area (think UISplitView but with a small icon sidebar / vertical UITabBar).
The container view controller uses autoLayout and resizes correctly when rotated.
Content viewController 1 uses autolayout and was made with IB, so it has a xib file.
Content viewController 2 inherits from UITableViewController and does not use a xib.
If I assign viewController 1 as the root view controller and rotate, the resize works and here are the callbacks that I get in viewController 1:
willRotateToInterfaceOrientation
updateViewConstraints
viewWillLayoutSubviews
didRotateFromInterfaceOrientation
However, if I assign my container view controller as the root view controller, load viewController 1 and rotate, the resize does not work. And I only get the following callbacks inside viewController 1:
willRotateToInterfaceOrientation
didRotateFromInterfaceOrientation
Inside my view controller container, here's how I swap the view controllers:
[self addChildViewController:toViewController];
[toViewController didMoveToParentViewController:self];
// Remove the old view controller
[fromViewController willMoveToParentViewController:nil];
[fromViewController.view removeFromSuperview];
[fromViewController removeFromParentViewController];
// Add the new view
[self.contentContainerView addSubview:toViewController.view];
Now, I do get the callbacks that a rotation is about to happen, but it seems as if neither updateViewConstraints nor viewWillLayoutSubviews is called. This explains why the resize is not happening, but why are those methods not called once I put the view controller in a container view?
I also tried to explicitly return YES in my container on both
shouldAutomaticallyForwardAppearanceMethods
and
shouldAutomaticallyForwardAppearanceMethods
although this should be the default already.
Also, the view controller not made with IB (view controller 2) resizes correctly when rotating inside the container. However, I don't explicitly use NSLayoutConstraints on this one, so I suspect it's defaulting to Springs and Struts for the resizing when rotating.
Do I need to forward some other events on my view controller container to get the auto layout view controller to resize correctly when rotating?
OK, I think I was missing this method here in my view controller container:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.contentViewController.view.frame = self.contentContainerView.bounds;
}
While this resizes now correctly when rotating, it still doesn't trigger
updateViewConstraints
in my child view controller. Interesting
Seems like iOS 8 does call updateViewConstraints for you. But iOS 7 didn't. To get this called in iOS 7, call setNeedsUpdateConstraints, like this:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
BOOL isiOS7 = floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1;
if (isiOS7) {
// Trigger a call to updateViewConstraints
[self.view setNeedsUpdateConstraints];
}
}
In updateLayoutConstraints, a good way to check which orientation is the one to layout for is to check the status bar's orientation. This works for 7 and 8.
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
BOOL layoutAsLandscape = UIInterfaceOrientationIsLandscape(orientation);

UIView transitions present modal view at the bottom

I need to display a UIView/UIViewController when the user taps a button and the modal view should appear just like how the key board appears from the bottom bar when you edit text in a UITextField. Using the following code, I get to show it as a pop up.
[self presentModalViewController:child animated:YES];
How to make it appear like the keyboard?
I understad modal views cover the entire screen. But I really want a view that covers only half the screen. So, please tell me why this doesn't work
MyController *controller = [[MyController alloc] initWithNibName:#"MyView" bundle:nil];
CGRect frame = CGRectMake(0,44,768,264);
[controller view].frame = frame;
contoller.delegate = self;
[[self view] addSubView:[controller view]];
[controller release];
I am trying to add a sub view to my current view and make it appear where the keyboard appears.
Check if your child.modalTransitionStyle == UIModalTransitionStyleCoverVertical.
(And a model view controller always cover the whole screen. If you just need to cover half of the screen like the keyboard, you need to put the view controller's view as a subview of the main view, then animate it in manually with animation blocks.)
I know its an old question, but an answer to this is to use a UIActionSheet.
It won't present a View Controller, but you can present custom views that only cover a portion of the screen.
Check out this question for more information
Add UIPickerView & a Button in Action sheet - How?