Auto-resizing window - objective-c

I'd like to embed a Tabbar or a TabViewController using a container into a ViewController of a Window for a macOS App. The two ViewControllers of the Tabbar / TabVC should have different heights, a tall one and a small/flat one.
The point is, that I'd like to automatically resize the whole window height when the Tabbar / TabVC changes its ViewController. This way the window is always as small as possible and doesn't show any unused and empty space.
This is my current storyboard, which (of course) doesn't resize the windows:
Does anybody have a hint, how to automatically resize the window according to the size of the selected ViewController in the Tabbar or TabVC?

Try setting the preferredContentSize in viewWillAppear like:
- (void) viewWillAppear
{
[super viewWillAppear];
self.preferredContentSize = CGSizeMake(self.view.fittingSize.width, 194);
}
You can also use self.preferredContentSize = self.view.fittingSize; if that works for you.
But I'm not sure if this will animate the change for you.

Related

iOS7 - Setting selectedIndex of UITabBarController breaks touch events along right-hand edge of screen?

I've hit a weird problem with UITabBarController on iOS7 and can't seem to find a workaround, so any help would be welcome!
Scenario:
Navigation-based app using landscape orientation on iPad.
App consists of a main view, and a second view which is a UITabBarController.
TabBarController has two tabs.
First view has two buttons - each button performs a segue to the tab bar controller and sets a different tab as selected. (i.e. button1 selects the first tab, and button2 selects the second tab).
Setting the tab is done in prepareForSegue by calling setSelectedIndex on the tab bar controller.
Outcome:
On iOS 7 I am finding that the view shown in the tab bar controller fails to register any touch events along the right-hand edge of the view! So in the storyboard shown above, the UISwitch on the right side of the screen cannot be tapped.
I've even attached a tap gesture recognizer to the views and used it to log the area of the screen that can be touched - it seems to register touch events up to about x=770 points across. The remaining 1/4 of the screen is 'untouchable'!
After the segue, if you manually switch to the other tab and switch back again, the touch events are 'fixed' and the full view responds to touches again.
This doesn't seem to be a problem on iOS 5 / 6.
Any help much appreciated as to:
What is causing this to happen in the first place (iOS7 bug / change?)
How else can I work around this? I've tried calling setSelectedViewController as well as using setSelectedIndex and this seems to be the same.
Thanks in advance.
I ended up raising this with Developer Tech Support, and it looks like a bug. This is the response I got back from Apple:
The container view that the tab bar controller sets up to contain your view controller is not being resized to account for the interface being in landscape orientation. It's dimensions at the time your view controller is displayed are 768 (width) x 1024 (height).
The view hierarchy looks like this when the selected tab's view is displayed:
UIWindow
/* Navigation Controller */
UILayoutContainerView
UINavigationTransitionView
UIViewControllerWrapperView
/* Tab bar controller */
UILayoutContainerView
UITransitionView
UIViewControllerWrapperView <-- Incorrectly sized.
/* MyViewController */
MyViewController.view
The incorrect size of UIViewControllerWrapperView does not cause a display problem because subviews are still displayed even if they are outside their superview's bounds. However, event routing is much more strict. Events on the right quarter of the screen are never routed to your view controller's view because the hit test fails at the wrongly-sized UIViewControllerWrapperView where the event falls outside UIViewControllerWrapperView's bounds.
As a workaround, I subclassed UITabBarController, and added the following in viewWillAppear:
#implementation FixedIOS7TabBarController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Fix the frame of the UIViewControllerWrapperView
self.selectedViewController.view.superview.frame = self.view.bounds;
}
#end
Hope that helps someone else....
As explained in this answer,
The container view that the tab bar controller sets up to contain your
view controller is not being resized to account for the interface
being in landscape orientation. Its dimensions at the time your view
controller is displayed are 768 (width) x 1024 (height).
I was encountering this problem when the TabBarController was originally displayed in portrait mode. When the device was rotated into landscape mode, the view was unresponsive on the right hand side.
The solution proposed in that answer did not work for me, because viewWillAppear: is invoked only once. However, viewDidLayoutSubvews is invoked whenever the view changes, including rotations, so my solution was to subclass UITabBarController and perform the workaround in viewDidLayoutSubvews:
#implementation FixedIOS7TabBarController
- (void)viewDidLayoutSubviews
{
// fix for iOS7 bug in UITabBarController
self.selectedViewController.view.superview.frame = self.view.bounds;
}
#end
End up finding a workaround here:
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Right answer don't worked for me, cause user can change orientation; And it still not touchable in some area when change orientation.
So I create my own solution, I don't sure that is normal solution.
#implementation FixedIOS7TabBarController
- (UIView*)findInSubview:(UIView*)view className:(NSString*)className
{
for(UIView* v in view.subviews){
if([NSStringFromClass(v.class) isEqualToString:className])
return v;
UIView* finded = [self findInSubview:v className:className];
if(finded)
return finded;
}
return nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIView* wraperView = [self findInSubview:self.view className:#"UIViewControllerWrapperView"];
wraperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
#end
Works perfectly for me!
In the list of view controllers on the left hand side navigate to the views/view controllers affected, drag the view to underneath the first responder so that it is disassociated to the view controller's view.
Then go to the layout tab on the right hand side, select all 4 anchors and both sets of resizing arrows (horizontal + vertical).
Then drag the view back to where it was originally (just below the view controller).

NSWindow not resizing to fit NSView - SOMETIMES?

I'm having trouble resizing an NSWindow to fit an NSView. It must be a logic error as it works but not for one action.
I have one NSWindow which is empty, and 3 NSViews with components and are different sizes.
With the following code I resize the NSWindow to fit the NSView and display it:
[_window setContentSize:_mainView.frame.size];
[_window setContentView:_mainView];
This code works fine.
However in one NSView I have a Back button, and while this displays the correct NSView in the NSWindow, it does not re-size it back. As an example the initial window is a certain size, I click to switch to another view and it resizes correctly, I press the back button, the NSView is displayed but the window stays the same size?
Can anyone explain to me why when I switch back to the original NSView, it doesn't resize the NSWindow?
Thanks in advance everyone. This is the complete code I have:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[_window setContentSize:_mainView.frame.size];
[_window setContentView:_mainView];
}
- (IBAction)switchSubtractionView:(id)sender {
[_window setContentSize:_subtractionView.frame.size];
[_window setContentView:_subtractionView];
}
- (IBAction)switchAdditionView:(id)sender {
[_window setContentSize:_additionView.frame.size];
[_window setContentView:_additionView];
}
// -----------------------------------------------------------------------
// THE FOLLOWING METHOD DISPLAYS THE NEW VIEW CORRECTLY BUT DOESN'T RESIZE
// -----------------------------------------------------------------------
- (IBAction)switchMainMenu:(id)sender {
[_window setContentSize:_mainView.frame.size];
[_window setContentView:_mainView];
}
Thanks in advance everyone.
EDIT: it seems to me that when getting VIEW.frame.size, if this is repeatedly used, it loses its values? This seems very strange behaviour to me?
A window's content view must always be sized to fill the window's content area. Therefore, when you set the window's content size, you effectively change the size of the current content view. This happens just before you switch the content view, so you are changing the size of the old view.
Try setting the content view to a new, disposable NSView before changing the content size, and then setting the new content view.

Xcode Adding Subview on entire screen above everything

I want to use addSubview method to add new view on my screen. However, new view must also include navigation bar etc. which means it should include entire screen. What should i do?
Whenever i try to addSubview it always shows new view under navigation bar.
I don't want to use pushViewController or presentModelViewController because new added view will be low opacity and i can see background but i do not want to interact with background objects like bar buttons table etc.
Sorry for english, i hope, i clearly told what problem is.
Just set the frame property of the view you add before, and set it with screen bounds.
[myView setFrame:[[UIScreen mainScreen] bounds];
[self.navigationController.view addSubview:myView];
If you want to disable interaction with the navigation bar :
[self.navigationController.navigationBar setUserInteractionEnabled:NO];
You could take a look at adding another UIWindow above your root window.
when I tried doing the navigationController.view addSubview, it wouldn't cover my tab bar so I did the following with my app delegate's window:
[self.window addSubview:myView];
if you need access to your appDelegate from another class, you'd just import your application delegate and call it from that.

Two UIViews in one .xib file?

i made a second uiview in mei .xib file. the first view is landscape and i get it by following code
ItemController *newItem;
newItem = [[ItemController alloc] init];
newItem.view.....
how can i "activate" the second view, so i can use it with
newItem.view2...
is that possible? the second view is portait mode, so it should be hidden and when turning the ipad the first view should be hidden and the second gets visible.
thanks
If I understand your question correctly, you can use the method willRotateToInterfaceOrientation:duration: in UIViewController.
You can declare two UIView variables and set connections to them using IB:
IBOutlet UIView *view1;
IBOutlet UIView *view2;
Then, in willRotateToInterfaceOrientation:duration: you can swap the views. Assuming that your ItemController is a subclass of UIViewController, you could have something like this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
[self setView:view1];
}
else {
[self setView:view2];
}
}
When the user rotates the iPad this method will get called automatically.
NOTE: make sure you set shouldAutorotateToInterfaceOrientation: to return YES, and you may have to check the orientation to set the view property to the correct view initially.
EDIT: My answer is assuming that you have two views with distinct layouts/elements and not two views that are essentially the same thing sized for different orientations. If the latter is the case, there is probably a better way to do it using only one view.
The approach you are taking will lead you to a trouble situation when you have some IBActions to associate with click of buttons or change the label text (which is on xib) etc.
The best approach is to change the frame height width and positions when the orientation changes. Although this is difficult in beginning but it will save you from lots of troubles.
For assistance check Rotate UIViewController to counteract changes in UIInterfaceOrientation
Hope this helps.
Thanks,
Madhup

Adding a UINavigationController as a subview of UIView

I'm trying to display a UILabel on top of a UINavigationController. The problem is that when I add the UILabel as a subview of UIWindow it will not automatically rotate since it is not a subview of UIViewController (UIViewController automatically handles updating subviews during rotations).
This is the hierarchy I was using:
UIWindow
UILabel
UINavigationController
So I was thinking I could use the following hierarchy:
UIWindow
UIViewController
UIView
UILabel
UINavigationController
This way the label could be displayed on top of the UINavigationController's bar while also automatically being rotated since it is a subview of UIViewController.
The problem is that when I try adding a UINavigationController as a subview of a view:
[myViewController.view addSubview:myNavigationController.view];
it will appear 20 pixels downwards. Which I'm guessing is because it thinks it needs to make room for the status bar. But, since the UINavigationController is being placed inside a UIView which does not overlay on top of the status bar, it is incorrectly adding an additional 20 pixels. In other words, the top of the UINavigationBar is at the screen's 40 pixel mark instead of at 20 pixels.
Is there any easy way to just shift the UINavigationController and all of its elements (e.g. navigation bar, tool bar, root view controller) up 20 pixels? Or to let it know that it shouldn't compensate for a status bar?
If not, I guess I would need to use my first hierarchy mentioned above and figure out how to rotate the label so it is consistent with the navigation bar's rotation. Where can I find more information on how to do this?
Note: by "displaying a label on top of the navigation bar", I mean it should overlay on top of the navigation bar... it can't simply be wrapped in a bar button item and placed as one of the items of the navigation bar.
Using this code seems to work:
nav.view.frame = CGRectMake(nav.view.frame.origin.x, nav.view.frame.origin.y - 20,
nav.view.frame.size.width, nav.view.frame.size.height);
I did this before adding the navigation controller as a subview. Using the [UIApplication sharedApplication].statusBarFrame instead of the hard coded 20 would probably be a good idea too.
I'm not sure if it's the best way to do it though.
If you want a frame representing the available content area, then you should just use: [[UIScreen mainScreen] applicationFrame]. Of course, this restricts your top-level view controller so that it can only be top level. So still kind of dodgy, but less so.
Why don't you use App Frame instead of adding some values to origins? I mean using:
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
as a reference frame, and do something like this:
nav.view.frame = CGRectMake(appFrame.origin.x, appFrame.origin.y, ...
This one worked for me.
I had this same problem actually but managed to fix it.
I noticed that my view controller's view had the correct frame, but the view controller's navigation bar did not (it had a frame origin of (0,20) ).
Insert this into the view's controller that is the superview of the navigation controller:
- (void) viewDidAppear:(BOOL)animated {
if (navigationController.navigationBar.frame.origin.y != 0) {
[[navigationController view] removeFromSuperview];
[[self view] addSubview:navigationController.view];
}
}
Swift 5:
add the following line in the viewDidLoad() of the root view controller of the UINavigationController.
self.edgesForExtendedLayout = [.top, .bottom]