UIView loaded from the Nib, manually calling autolayout - ios

I need to load a UIView from Nib and render it to a PDF (it's an Invoice). So I load the custom UIView invoiceView from a Nib and set it up (set various labels in it).
After setting the labels, I have to force the view to layout itself so that various UI elements resize/reposition themselves correctly.
Calling:
invoiceView.updateConstraints()
doesn't do anything.
Calling:
invoiceView.layoutSubviews()
messes everything up.
Here's what I have already tried:
Setting frame of the invoiceView (doesn't have any effect).
Setting the frame and adding the invoiceView to the current view hierarchy:
self.view.addSubview(invoiceView)
sets up everything correctly. However I do not want to do it. I want to force the view to layout itself correctly (using the layout constraints). What am I doing wrong? What methods should I call on invoiceView so that it is laid out correctly without adding it to the current view hierarchy?
PS: the code has been typed from memory. please ignore typos.

try
invoiceView.setNeedsLayout()
invoiceView.layoutIfNeeded()

Related

How to access custom view properties after initialize

I'm targeting IOS 8+.
I have a form that is used in more than one place. So I decided to create a custom view where I define the various "form" text fields.
I have built my XIB, and the UIView subclass contains the outlets for each textField.
The view is composed of a background image and a scroll with the form fields over it.
Now, my first obstacle was: I need to have this custom view in a container that may or may not have a navigation bar. This made me create a constraint outlet so I could update its value to push down the scroller view. This way I'd have the whole image in the frame, the top being behind the navbar and the scroller bellow the nav bar).
Here's a manual drawing to help understanding the problem.
It's very possible that I'm making a lot of mess and confusion on my way to solve this. :)
The problem is:
After awakeFromNib runs I have no access to the constraint property. I then noticed the same thing happens for the TextFields outlets.
So, how can I access the custom view's properties when I instantiate them programatically?
Something like:
Controller:
let customView = SignupView(frame: f)
view.addSubview(customView)
customView.pushScrollerDownBy(50.0)
Custom view:
func pushScrollerDownBy(yOffset: CGFloat) {
//topScrollerConstraint is the outlet for the textField.
topScrollerConstraint.constant = yOffset //right now topScrollerConstraint is nil.
}
You should check if you have connected your topScrollerConstraint to the file's owner since it will not get instantiated and therefore, error. Here is a recent SO question regarding difference between these two:
What is File’s owner in XIB in this case?

updateViewConstraints called when creating constraints in viewDidLoad

Currently I'm experiencing a strange behavior. I removed the auto resizing mask from a view for viewForHeaderInSection. When I'm doing this the app crashes because I tried to remove a constraint which is nil.
I'm creating the concerned constraints in viewDidLoad in my parent controller. In debug mode I found out that when the system tries to create a constraint where my child view controller is involved (which has the removed auto resizing mask from the section header view) it directly jumps to updateViewConstraints. Of course the constraints are nil because the weren't created yet.
If I add again the auto resizing mask the app works, but I can't do what I'm trying to do (to layout my views correctly).
If I create my constraints in updateViewConstraints the app also works.
I don't understand why this is happening. On a similar view controller it is working without problems. Sometimes I think auto layout is more a pain than a gain. On a server error an alert was displayed. Here the view could be correctly loaded. Seems that this is a kind of timing problem.
I want to know why this is happening and how should I proceed in future that such an error doesn't happen anymore. Am I doing something wrong?
Edit:
Don't know if it helps but if I call setNeedsLayout and layoutIfNeeded on the view of my child view controller in viewDidLoad of my parent then also the app crashes.
Edit 2:
Seems that it occurs when I add multiple views with constraints on different places to my view controller. For my table I add an empty message if there are currently no entries. If I don't add the label as subview to my table everything works fine.
So when I'm allowed to add my constraints? Currently I add them right after the view was added as subview. For the empty message it is in viewDidLoad and for the section header it is viewForHeaderInSection. Do I have to use something like setNeedsLayout?
Edit 3:
Adding a subview to the table view isn't a good idea at all (especially when using auto layout). For now I'm using the background view, but that's not the solution I'm looking for.
You do not need to change the value of translatesAutoresizingMaskIntoConstraints unless you actually have set the autoresizingMask to something other than 0. If you are using constraints, the solution is to completely ignore autoresizingMask and translatesAutoresizingMaskIntoConstraints.
Now I got the same problem again. My workaround didn't worked:
// on iOS 7 this would bring "Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super." and the app crashes
// on iOS 8 this is needed, otherwise "Unable to simultaneously satisfy constraints."
// bug?
if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) {
sectionHeader.TranslatesAutoresizingMaskIntoConstraints = false;
}
What I've found out so far is that it has to do with the headerview and the empty message (view). When both are used I get this problem. Either the app crashes or I get
Unable to simultaneously satisfy constraints
depending if I have the autoresizing mask turned off or not. Interestingly, only iOS 8 made this problem with my workaround. A solution would be to set a boolean variable in viewDidLoad if the constraints have been set up and check for this in updateViewConstraints. But I wanted to know the real cause for this and not using a workaround for a workaround ...
Every time I retrieve new data for my table I checked if the number of records is zero or not. If they were zero I showed my empty message. The problem seems to be caused by the reloadData, which I called before this check. Than UITableViewHeaderFooterContentView got a height of zero from the NSAutoresizingMaskLayoutConstraint. Restructuring my code to this variant
if (myList.Count == 0) {
this.TableView.BackgroundView = emptyMessage;
this.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
} else {
this.TableView.BackgroundView = null;
this.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine;
if (emptyMessage != null) {
emptyMessage.RemoveFromSuperview ();
}
}
// call this afterwards!
TableView.ReloadData();
seems to solve my issue.

iOS - Where to initialize views

If I want to initialize views programmatically, where in the viewcontroller lifecycle should this happen?
The initial intuition is loadView. However, here, we don't yet have the frame of the view itself (necessary for calculating the sizes/positions of the views). Ditto for viewDidLoad.
Next intuition is viewWillAppear- here we DO (finally) have a guarantee of the frame of the view. However, this has potential to be called many times throughout the vc lifecycle. Ditto for viewDidAppear, etc...
Finally, I found viewWillLayoutSubviews. This works for the initialization of most static layouts- however, whenever any view moves this gets called again (same problem as viewWillAppear).
I've seen recommendations to init the views in loadView and set their frames in viewWillLayoutSubviews (since setting frames should be idempotent, who cares if it gets called a couple times). But then why does apple so strongly encourage initWithFrame: as the standard initialization method of UIViews (https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/CreatingViews/CreatingViews.html)?
Would it be crazy to subclass all my UIViewControllers to have an initWithViewFrame: method? That way I can pass in a frame, manually set it immediately in loadView and be done with it? Or is it better to have a viewHasBeenFormatted flag in viewWillAppear that, if not set, calls the formatting of views and then sets it?
Or is this just apple's way of saying "use interface builder or you're screwed"?
Any help is appreciated!
edit- accidentally wrote loadView where I meant viewWillAppear (in final paragraph)
update- I guess I've come to terms with the fact that there is no place where
The frame is confidently known
The code will only be run once (on setup)
Looks like you're expected to initWithFrame: all your views in viewDidLoad (but then I guess the contents of that view shouldn't treat that frame as even remotely final? because how could it be when it was derived on an assumption? ugh...). Then re-set their frames in layoutSubviews. And make sure to manually handle the differences between initial layout and layout as a result of a moved view there... Man I feel like I've GOT to be missing something... (lol denial...)
I guess that, OR submit and use IB.
update2- viewWillLayoutSubviews WILL get called when one of its subviews is resized. So it is still disqualified as it fails property 2 of the required characteristics that I'm looking for. :(
If you're doing layout with IB, it's fine to do additional view initialization in viewDidLoad (for example, if you need to do stuff that IB doesn't handle well, or if you have UIView subclasses with properties not supported by IB). Alternatively, if you're not using IB, the documentation says you should use loadView to manually initialize your view hierarchy.
You're right, though, that you can't rely on the frame being accurate at that point. So you can accomplish layout via each view's autoResizingMask property, layout constraints (if you're iOS 6 and later), and/or overriding layoutSubviews.
My usual approach is to do layout to some degree in IB, then do anything else I need to (nontrivial layout, custom classes, etc) in viewDidLoad. Then, if I have layout to figure out that autoResizingMask doesn't cover (I'm supporting down to iOS 5), I override viewWillAppear (or layoutSubviews if I'm subclassing UIView) and do some pixel math. I've got a category on UIView to help with this that has things like:
-(void)centerSubviewHorizontally:(UIView *)view pixelsFromTop:(float)pixels;
-(void)centerSubviewHorizontally:(UIView *)view pixelsBelow:(float)pixels siblingView:(UIView *)sibling;
View controllers should not have initWithFrame: methods. What I do in all of my code (I never use IB) is to let the default loadView do its own thing. I create and setup all subviews in viewDidLoad. At this point the view controller's frame has at least a sane value. All subviews can be created with their own sane frames based on the initial size of the view controller's view. With proper autoresizingMask values this may be all you need.
If you need more specific subview layout, put the appropriate layout code in the viewWillLayoutSubviews method. This will deal with any view controller view frame changes including rotation, in-call status bars, etc.
If you don't use interface builder you should override loadView and initialize the views there. If you use autolayout you can also add your constraints there. If you don't use autolayout you can override the layoutSubviews method of your views to adjust the frames.

Where to place the code to change the properties of a UIView

If I'm creating a UIView programmatically and I wish to change the UIView properties (background, for example, or actually, messing with CALayers), must I place the code outside of UIView such as in the View controller? Can I put the code somewhere inside UIView?
I was checking out the CoreAnimationKioskStyleMenu example, its code is inside UIView but it's loaded from Nib and can be placed at awakeFromNib, so it doesn't seem to apply to my case.
That depends. Obviously, a good way to handle this is to use a xib file, as it is designed to hold data like this, but that isn't always the best answer for every situation.
If the view is meant to be reused frequently (like a button, or some widget) throughout the application, its best to store all that customization in a subclass of the UIView.
If its a single larger view that will always be managed by a UIViewController, you can keep some of the information in the UIViewController. However, if you end up subclassing a UIView anyway it's probably best practice to keep the data in the UIView.
As a general note, I believe its worth your time to push as much of this data into a xib using interface builder. Magic values (like colors or sizes) peppered through your code will always be a problem if you want to modify it. I have found modifying a xib to be much easier.
Actually there are some methods where you could place initialization/ customization code.
(void)willMoveToSuperview:(UIView *)newSuperview;
(void)didMoveToSuperview;
will get called as soon as u add the view as a subview to another view, at which point you already have the frame and all the properties, and you can do further customizing as you wish.
(void)layoutSubviews -- generally used for changing subviews' frames and layout organization.
Will get called each time the view needs to be redrawn by the system, or when you specifically call [self setNeedsLayout] on your UIView.
Hope this helps.

Programmatically accessing subviews of UIView added in Interface Builder

I have a nib file where I have a view that contains a background image, a button and another image that covers the full screen (a shadow) that needs to be moved to the front.
On the view, I'm creating child views, and after creating those and adding them using [self addView] I need to move to the front the shadow image.
I'm currently using the tag attribute to find that view, but I'm thinking there's probably a better way, by means of identifying the subviews I add in Interface Builder by some name.
I tries adding a IBOutlet to connect the subview with its parent, but it didn't work (and made no sense, since the subview is already connected to its parent in some way).
The IBOutlets way should work, and is probably the best way to do it. Make sure you made the proper connection in Interface Builder after you declared them in the .h file.
The iPhone does a lazy loading of view controllers. The nib might not have been loaded in initWithCoder or any init method for that matter as Kendall specified.
viewDidLoad is the preferred place to access anything from the nib if you want to access them before the view is displayed.
Hope that helps.
At what point are you trying to access the subviews? If you try within init of a ViewController, the IBOutlets will be nil. The first method you can get at them is probably viewDidLoad.
The reason it does make sense to do things this way is that IBOutlets are just direct pointers to some component, even if they are already subviews of something else. Just saves a lot of hunting.
Using the Tag is a perfectly valid way to locate specific views, so long as you're using the viewWithTag: method. If you're already using tags, there's no need to change to IBOutlets unless you just don't like calling viewWithTag:.

Resources