Using Cocoa autolayout to hide and show a view - macos

I'm trying to hide and show view #1 in the following picture based on whether the button is clicked using Autolayout. Anyone know how to do this?
I tried setting two NSLayoutConstraints for view #2, one where it is tied to the top of the superview of view #1 and view #2 and one where it is tied to the bottom of view #1, and then alter the priority of the NSLayoutConstraints to hide view #1, but that didn't seem to do anything.
Any advice would be appreciated. I'm mainly trying to do this in IB, but programatic solutions are welcome as well.
Pic for reference:

NSStackView is appropriate here. It automates creating constraints that tie its subviews to each other in stack.
Hiding a view does not change layout. It's still there, just isn't drawing.
If you were doing it without NSStackView, what you would do is change the constraints. Keep an instance variable, _stackConstraints. In one configuration, the stack constraints would be
V:|-[0]-[view1(v1Height)]-0-[view2]-0-[view3(v3Height)]-0-|
and in the other configuration
V:|-[0]-[view2]-0-[view3(v3Height)]-0-|
When you hit the button, do
[[self view] removeConstraints:_stackConstraints];
_stackConstraints = <make other set of constraints>
[[self view] addConstraints:_stackConstraints];

If you're willing to require 10.11+, you can just select "Detaches Hidden Views" on the NSStackView in Interface Builder (or set detachesHiddenViews = YES on it programmatically).
Then setting View #1 to hidden = YES will automatically re-layout the stack view, making View #2 take up more space (assuming the stack view height is fixed – if not, the stack view would just get less tall instead).
If you need to support 10.10 or earlier, then you can hide the view this way:
[stackView setVisibilityPriority:NSStackViewVisibilityPriorityNotVisible forView:view1];
And show it again via:
[stackView setVisibilityPriority:NSStackViewVisibilityPriorityMustHold forView:view1];

Answer here Hide autolayout UIView : How to get existing NSLayoutConstraint to update this one
With this category https://github.com/damienromito/UIView-UpdateAutoLayoutConstraints
//Hide View
[myView1 hideByHeight:YES];

Related

Cocoa: How to properly hide other views below NSImageView?

I have a view with several subviews (NSButton, NSTextField, NSPopUpButton) and a NSImageView with a spinner icon which should be displayed on top of the other views while data is retrieved from the web.
To display the NSImageView on top I have set
imageViewSpinner.wantsLayer = true
imageViewSpinner.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
The problem is, that the focus borders and PopUpButtons are still accessible/shining through the NSImageView (see attached video).
To solve this, I could iterate over all the other subviews and set them to "isHidden" or "disabled" but I wonder, if there is a cleaner solution to this problem, for example defining the NSImageView as topmost layer without things getting through?
You could put all of the other views inside of one container view and hide that.
You can also use a tab-less tab view to programmatically switch between view sub-hierarchies. (That basically achieves the same thing. It's better when there are more than 2 views to manage.)

How to show different views in same window in cocoa?

Is it possible to do navigation within the same window in a mac application ?(Like it is possible in ios apps).I want to show each view in the same window instead of opening different windows on a button click.
e.g When a user clicks a button then the next page should be loaded in the same window.(The next page will have nothing in common with the current page.)
You may use Tab View for easy switching between views on a same window.
UPDATE:
You may also customize your tab view , make it tabless (In the attributes inspector set style to tabless) and use your buttons to switch between views.
You may take help from the following link : http://devcry.heiho.net/2012/01/nstabview-tutorial.html
OR
You may add or remove subviews from your window on button clicks, using
[[yourWindow contentView] addSubview: yourSubview]; // Add subview to window
[yourSubview removeFromSuperview]; //Remove subview
UPDATE:
Steps to swap between views using a tabless tab view.
Drag a NSTabView to your xib.
Set the no. of tabs in attribute inspector to no. of views you want.
Design each view of the tab as per your requirement.
Now in the attribute inspector of tabview, set style to tabless.
Now drag the buttons you want to use for swapping between views. Suppose Button0 and Button1 are for 1st and 2nd view of your tab view.
Create a IBOutlet for your NSTabView in your .h file. Bind it to the referencing outlet of you tabview.
IBOutLet NSTabView* tabview;
Set a IBAction for both your buttons in your .h class file.
In the button action method for button1, use
- (IBAction)button1clicked:(id)sender
{
[tab selectTabViewItemAtIndex:0];
}
Similarly in button2 action method use:
[tab selectTabViewItemAtIndex:1];
In this way you can have any no. of views and you may select any view on button click using
[tab selectTabViewItemAtIndex:(index of the view you want to load)];
In general you want to google for view swapping.
There are tons of examples out there. Some from Apple and lots elsewhere.
Much of it is very similar to iOS.
You need to read the docs a bit too.
Understand NSView and how to load views from nibs, how to create view objects in code, how to add a subview and how to remove a view.
There are many approaches to having different views for different reasons. The right approach is a combination of style, experience and what your app actually needs to do.
Cocoa includes NSBox, NSTabView, and lots of others. Those two can be configured to not display any visual indication that they are containers.
You will also need to understand at least a little about NSWindow to understand its content view (the root container of other views generally)

Can I disable autolayout for a specific subview at runtime?

I have a view that needs to have its frame manipulated programmatically - it's a kind of document view that wraps to its content which is then scrolled and zoomed around a superview by manipulating the frame origin. Autolayout fights with this at runtime.
Disabling autolayout completely seems a bit harsh because it could reasonably be used to handle layout for the other views. It seems like what I might want is some kind of "null constraint".
I had the same problem. But I have resolved it.
Yes, you can disable auto layout at runtime for a specific UIView, instead of disabling it for the whole xib or storyboard which is set by default in Xcode 4.3 and later.
Set translatesAutoresizingMaskIntoConstraints to YES, before you set the frame of your subview:
self.exampleView.translatesAutoresizingMaskIntoConstraints = YES;
self.exampleView.frame = CGRectMake(20, 20, 50, 50);
I had a similar issue where Autolayout was overriding some of my frame-setting at run time (I had a dynamic view that in some cases pushed a new view controller...pushing and then pressing Back would reset the initial view).
I got around this by putting my manipulation code in viewDidLayoutSubviews of my View Controller. This seems to get called after whatever constraint mojo gets called, but before viewDidAppear, so the user is none the wiser.
Perhaps just setting translatesAutoresizingMaskIntoConstraints to YES (and not adding additional constraints affecting that view) will let you set the frame without fighting the auto layout system.
In iOS 8 you can set an NSLayoutConstraint to be active or not. So if I'm using interface builder, I add all my constraints to an OutletCollection and then activate or deactivate using:
NSLayoutConstraint.deactivateConstraints(self.landscapeConstraintsPad)
NSLayoutConstraint.activateConstraints(self.portraitConstraintsPad)
The particular application I'm using it for here is having different constraints in portrait and landscape mode and I activate/deactivate based on the rotation of the device. It means I can create some complex layout changes all in interface builder for both orientations, and still use auto layout without the verbose auto layout code.
Or you can activate / deactivate using removeConstraints and addConstraints.
I don't know if this will help anyone else, but I wrote a category to make this convenient because I find myself doing this a lot.
UIView+DisableAutolayoutTemporarily.h
#import <UIKit/UIKit.h>
#interface UIView (DisableAutolayoutTemporarily)
// the view as a parameter is a convenience so we don't have to always
// guard against strong-reference cycles
- (void)resizeWithBlock:(void (^)(UIView *view))block;
#end
UIView+DisableAutolayoutTemporarily.m
#import "UIView+DisableAutoResizeTemporarily.h"
#implementation UIView (DisableAutoResizeTemporarily)
- (void)resizeWithBlock:(void (^)(UIView * view))block
{
UIView *superview = self.superview;
[self removeFromSuperview];
[self setTranslatesAutoresizingMaskIntoConstraints:YES];
__weak UIView *weakSelf = self;
block(weakSelf);
[superview addSubview:self];
}
#end
I use it like this:
[cell.argumentLabel resizeWithBlock:^(UIView *view) {
[view setFrame:frame];
}];
Hope it helps.
You can set the translatesAutoresizingMaskIntoConstraints type Boolean, Value Yes in the User Defined Runtime Attributes of the UIView you want in the xib/storyboard.
In my view I had a Label and a Text. The label had pan gesture. The label moves around fine during drag. But when I use the text box keyboard, the label resets its position to the original location defined in auto layout. The issue got resolved when I added the following in swift for the label. I added this in viewWillAppear but it can be added pretty much anywhere you have access to the target field.
self.captionUILabel.translatesAutoresizingMaskIntoConstraints = true
Open project in 4.5
Select storyboard
Open the file inspector
Under Interface Builder Document uncheck 'Use Autolayout'
You can split across multiple storyboards if you want to use autolayout for some views.
For me it worked to create the subview programmatically, in my case the auto layout was messing with a view that I needed to rotate around its center but once I created this view programmatically it worked.
I've encountered a similar scenario, where I joined a project that was initiated with auto-layout, but I needed to make dynamic adjustments to several views. Here is what has worked for me:
Do NOT have views or components laid out in interface builder.
Add your views purely programmatically starting with alloc/init and setting their frames appropriately.
Done.
This happened to me in a project without storyboards or xib files. All 100% code. I had an ad banner at the bottom and wanted the view bounds to stop at the ad banner. The view would resize itself automatically after loading. I tried every resolution on this page but none of them worked.
I ended up just creating a sub view with the shortened height and placed that in into the main view of the controller. Then all my content went inside the sub view. That solved the problem very easily without doing anything that felt like it was going against the grain.
I am thinking if you want a view that is not the normal size that fills the window then you should use a sub view for that.
Instead of disabling autolayout, I would just calculate the new constraint with the frame you are replacing. That appears to me to be the appropriate way. If you are adjusting components that rely on constraints, adjust them accordingly.
For example, if you have a vertical constraint of 0 between two views (myView and otherView), and you have a pan gesture or something that adjusts the height of myView then you can recalculate the constraint with the adjusted values.
self.verticalConstraint.constant = newMyViewYOriginValue - (self.otherView.frame.origin.y + self.otherView.frame.size.height);
[self.myView needsUpdateConstraints];
For those of you who are using auto layout, please check out my solution here. You should be making #IBOutlet's of the constraints you want to adjust and then change their constants.
if it's xib file:
select the .xib file
select the "File's Owner"
show the Utilities
click on: "File Inspector"
Under "Interface Builder Document" disable: "Use Autolayout"

Resizing view controller acording to tabbar controller

In my application, I have developed one wizard in which I am providing a way for the user to setup his details one-by-one. After finishing all steps, the user will be redirected to the screen where TabBar will come into the picture.
The problem here is that the user can access the same view controllers with the wizard (without TabBar controllers) and normal flow (which is with tabbar controller). In the wizard, I am using a view controller of size 320x480 and the same in normal flow. But whenever I load any view controller using TabBar the 44 pixel view from bottom side gets hidden behind TabBar.
I know the I can manually set the view size, by detecting whether TabBar is present or not, but here in this case number of view controllers are more and its already designed of size 320x480.
I had tried with all methods given in Apple's documentation but none of it seems to work for me.
Following are the methods I have tried, along with some xib settings.
[self setWantsFullScreenLayout:YES];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[self.view setAutoresizesSubviews:YES];
[self.navigationController.view setNeedsLayout];
Is there any way to set the height of a view controller according to whether that TabBar is present or not?
In my case, (Status bar/Nav bar/Hidden tabbar) this worked
//Add this in your ViewController
self.edgesForExtendedLayout = UIRectEdgeBottom;
self.extendedLayoutIncludesOpaqueBars = YES;
However Barry's answer is better if you use Storyboard but I could not for this VC.
Tried for iOs7 and iOs 8.
If you're using storyboards, select each tab VC and clear the box for View Controller > Extend Edges > Under Bottom Bars.
I wasn't able to find a good answer for this, so I ended up adding a BOOL, hasTabBar. I set it true or false based on where I create my view, and use that to calculate my layouts.
frame.size.height -= (hasTabBar*50); // works nicely.

How to resize UI Table View of a UI Table View Controller programmatically?

I subclassed UITableViewController and called it FeaturedGamesViewController. Ok, I also have a navigation controller to which I added this FeaturedGamesViewController as the root. All pretty standard stuff that you do in the App Delegate.
Also note that there is no NIB file created for FeaturedGamesViewController. Just subclassing UITableViewController and calling initWithStyle sets the style of the table and sets the dataSource and delegate and size automatically. Data Source and Delegate are obviously set to FeaturedGamesViewController.
- (id)init
{
// Call the superclass's designated initializer
[super initWithStyle:UITableViewStyleGrouped];
}
OK, You see that I have set the table size to "Grouped". In Landscape view on my iPad it has about 20 pixels of space to the top, left and right (Sorry can't post screen shot because I am new here and the system won't let me until I have accumulated a certain number of points)
I DO NOT want that! This is Landscape so I expect it to fill up the all the space between the navigation bar and the tab bar below. What is worse is that I have faked a grid with a Custom UITableViewCell but the space to the left and right make it so that if you click on that space, the entire row is selected thus betraying the sense that this is a grid.
Now I figure I should resize the table view in viewDidLoad or something but I don't know how. I cannot do initWithFrame because of potential memory leaks (and possibly resetting dataSource and delegate and autoresizeMask properties that were already set) so there must be a setter or something to reset the origin of the tableview to just beneath the Navigation bar and filling up the entire screen with size 1024X748. How do you do dynamically reset the size of the table view?
Then I got really frustrated and I decided to do it via a Nib file, that way I can set the the orientation to landscape and set simulated Navigation and Tab bars and fill the rest of the space with the table view. This worked! If you are curious how to create a table view with a Nib after you have subclassed UITableViewController WITHOUT a nib, here is how:
http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/CreateConfigureTableView/CreateConfigureTableView.html#//apple_ref/doc/uid/TP40007451-CH6-SW10
Go to the paragraph right before "Creating a Table View Programmatically".
OK, when I did that, my landscape view of the "grid" looks filled up the entire space between the navigation bar at the top and the tab bar at the bottom just like I wanted.
I was fiddling with this some more and I found out that in my "non nib" version (the problematic one), I had set the table style to "grouped". When I changed it to "plain", it worked!!! But here is the thing though: In the nib version, "grouped" or "plain" gives the correct layout with the table occupying the whole space. So what gives?
Sorry for the long question but I guess what I am asking is:
1) How do you programmatically reset the size of the table view without introducing potential memory leaks or affecting other properties already set for you (ex: dataSource, delegate, autoResizeMask - these are set for you just because you subclassed UITableViewController)?
2) How do you do that for any view in general?
3) Why does "plain" style fill the layout as desired whereas "grouped" style gives the weird layout. Note that it this is not a problem in the Nib version.
Thanks for your patience!
Answer for (2), and hence for (1):
A UIView's frame is in a local coordinate system of its superview. A common way to make a view fit its superview is
CGRect bounds = [[self superview] bounds];
[self setFrame:bounds];
You should do this inside layoutSubviews.

Resources