iosxib 设置图片_如何:在iOS中使用StackView View从XIB自我调整自定义视图

I've recently started diving into iOS development with Swift again and am now looking into a custom UIControl. For layout and flexibility this custom view is built using a StackView.

What I want to achieve ...

... is basically to simply have the same functionality with the StackView that I usually have when I drag it directly into the storyboard - where it resizes to fit no problem. This essentially the same as self-sizing tableview cells.

Showcase

I can reduce my CustomView to a simple example: It's a StackView with three labels in it, Alignment set to Center, Distribution Equal Centering. The StackView would be pinned to left, right and top layout guides (or trailing, leading and top). This I can easily re-create with a CustomView, loaded from XIB translate into a CustomView with the same parameters - I attached both as screenshot.

Loading the XIB File and setting it up.

import UIKit

@IBDesignable

class CustomView: UIView {

// loaded from NIB

private weak var view: UIView!

convenience init() {

self.init(frame: CGRect())

}

override init(frame: CGRect) {

super.init(frame: frame)

self.loadNib()

}

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

self.loadNib()

}

override func layoutSubviews() {

super.layoutSubviews()

// we need to adjust the frame of the subview to no longer match the size used

// in the XIB file BUT the actual frame we got assinged from the superview

self.view.frame = bounds

}

private func loadNib () {

self.view = Bundle (for: type(of: self)).loadNibNamed(

"CustomView", owner: self, options: nil)! [0] as! UIView

self.addSubview(self.view)

}

}

What you can see here is that I simply load the XIB file, override layout Subviews to adapt the frame to the actual view and that's it.

Questions

So my questions are related to how this is usually approached. The labels in my StackView have an intrinsic content size, i.e., usually the StackView simply adapts to the height of the labels without complaining - IF you design it in the storyboard. It doesn't though, if you put all of it in a XIB and leave the layout as it is - the IB will complain about an unknown height if the view is pinned three ways as in the screenshot (top, leading, trailing).

I read the Auto-Layout Guide and watched the WWDC videos "Mysteries of AutoLayout" but the topic is still quite new to me and I don't think I got the right approach yet. So this is where I need help

(1) Should I set constraints and disable translatesAutoresizingMaskIntoConstraints ?

What I first could and should do is to set translatesAutoresizingMaskIntoConstraints false such that I don't get auto-generated constraints and define my own constraints. So I could change loadNib into this:

private func loadNib () {

self.view = Bundle (for: type(of: self)).loadNibNamed(

"CustomView", owner: self, options: nil)! [0] as! UIView

self.view.translatesAutoresizingMaskIntoConstraints = false

self.addSubview(self.view)

self.addConstraint(NSLayoutConstraint (

item: self

, attribute: .bottom

, relatedBy: .equal

, toItem: self.view

, attribute: .bottom

, multiplier: 1.0

, constant: 0))

self.addConstraint(NSLayoutConstraint (

item: self

, attribute: .top

, relatedBy: .equal

, toItem: self.view

, attribute: .top

, multiplier: 1.0

, constant: 0))

self.addConstraint(NSLayoutConstraint (

item: self.view

, attribute: .leading

, relatedBy: .equal

, toItem: self

, attribute: .leading

, multiplier: 1.0

, constant: 0))

self.addConstraint(NSLayoutConstraint (

item: self.view

, attribute: .trailing

, relatedBy: .equal

, toItem: self

, attribute: .trailing

, multiplier: 1.0

, constant: 0))

}

Basically I did stretch the view to fit the full width of my UIView in the Storyboard and restrained it's top and bottom to top and bottom of the StackView.

I had tons of problems with XCode 8 crashing on me when using this one so I'm not quite sure what to make of it.

(2) Or should I override intrinsicContentSize ?

I could also simply override intrinsicContentSize by checking the height of all my arrangedSubviews and take the one that is the highest, set my view height to it:

override var intrinsicContentSize: CGSize {

var size = CGSize.zero

for (_ , aSubView) in (self.view as! UIStackView).arrangedSubviews.enumerated() {

let subViewHeight = aSubView.intrinsicContentSize.height

if subViewHeight > size.height {

size.height = aSubView.intrinsicContentSize.height

}

}

// add some padding

size.height += 0

size.width = UIViewNoIntrinsicMetric

return size

}

While this surely gives me more flexibility I'd also have to invalidate the intrinsic content size, check the Dynamic Type and lots of other stuff I'd rather avoid.

(3) Am I even loading the XIB in the "proposed" way ?

Or is there a better way, e.g. by using UINib.instantiate? I really couldn't find a best-practice way to do that.

(4) Why do I have to do this in the first place?

Really, why? I only moved the StackView into a CustomView. So why does the StackView stop adapting itself now, why do I have to set the top and bottom constraints?

What was helping the most so far is that key-phrase in https://stackoverflow.com/a/24441368 :

"In general, auto layout is performed in a top-down fashion. In other

words, a parent view layout is performed first, and then any child

view layouts are performed."

.. but I'm really not so sure about it.

(5) And why do I have to add the constraints in code?

Assuming I disable translatesAutoresizingMaskIntoConstraints then I can't set constraints like for tableview cells in IB? It'll always translate into something like label.top = top and thus the label would be stretched instead of the StackView inheriting the size/height. Also I can't really pin the StackView to it's container view in the IB for the XIB file.

Hope someone can help me out there. Cheers!

解决方案

Here are some answers to your questions, although in a different order:

(5) And why do I have to add the constraints in code?

Assuming I disable translatesAutoresizingMaskIntoConstraints then I

can't set constraints like for tableview cells in IB? It'll always

translate into something like label.top = top and thus the label would

be stretched instead of the StackView inheriting the size/height. Also

I can't really pin the StackView to it's container view in the IB for

the XIB file.

You have to add the constraints in code, in this example, because you are loading an arbitrary view from a nib, and then adding it to a your coded CustomView. The view in the nib has no pre-existing knowledge of what context or hierarchy it will be placed into, so it can't possible defined ahead of time how to constrain itself to an unknown future superview. In the case of the StackView on the storyboard, or inside a prototype cell, that context is known as well as its parent view.

One nice alternative you can consider for this case, if you don't want to manually write constraints, is to make your CustomView subclass UIStackView instead of UIView. Since UIStackView generates appropriate constraints automatically, it will save you the manual coding. See next answer for some sample code.

(3) Am I even loading the XIB in the "proposed" way ?

Or is there a better way, e.g. by using UINib.instantiate? I really

couldn't find a best-practice way to do that.

I'm not aware of a "standard" or "right" way to handle loading a nib into a custom view. I've seen a variety of approaches that all work. However, I will note that you are doing more work than needed in your sample code. Here is a more minimal way of accomplishing the same outcome, but also using a UIStackView subclass instead of a UIView subclass (as mentioned above):

class CustomView: UIStackView {

required init(coder: NSCoder) {

super.init(coder: coder)

distribution = .fill

alignment = .fill

if let nibView = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as? UIView {

addArrangedSubview(nibView)

}

}

}

Note that because CustomView is now a subclass of UIStackView, it will create the constraints needed for any subviews automatically. In this case, because distribution and alignment are both set to .fill, it will pin the edges o the subview to its own edges, as you want.

(4) Why do I have to do this in the first place?

Really, why? I only moved the StackView into a CustomView. So why does

the StackView stop adapting itself now, why do I have to set the top

and bottom constraints?

Actually, the key issue here is your StackView's alignment property of .center. That is saying that the labels in StackView will be centered vertically, but it does not constrain them to the top or the bottom of the StackView. It's just like adding a centerY constraint to a subview inside a view. You can set the outer view to any height you want, and the subview will be centered, but it does not "push out" the top and bottom of the outer view, because center constraints only constrain centers, not top and bottom edges.

In your storyboard version of the StackView, the view hierarchy above that StackView is known, and there are also options to generate constraints automatically from resizing masks. Either way, the final StackView heigh is known to UIKit and the labels will be centered vertically within that height, but will not actually effect it. You need to either set the alignment of your StackView to be .fill, or add additional constraints from the tops and bottoms of the labels to the top and bottom of the StackView in order for auto layout to determine how tall the StackView should be.

(2) Or should I override intrinsicContentSize ?

This wouldn't help much, because the intrinsic content size by itself won't push out the edges of a parent view unless the edges of the subview are also constrained to the edges of the parent view. In any event, you will get all the correct intrinsic content size behavior you need in this case automatically, so long as you address the .center alignment issue mentioned above.

(1) Should I set constraints and disable translatesAutoresizingMaskIntoConstraints ?

This would normally be a valid approach. If you subclass UIStackView as I describe above, though, you don't need to do either.

In summary

If you:

Create your CustomView as a subclass of UIStackView as in the sample code above

Create a view on your storyboard set to your custom class CustomView. If you want the CustomView to self-size correctly, you need to give it constraints and not use auto-generated constraints from the resizing mask.

Change the StackView in your nib to either have the alignment set to .fill or to have additional constraints between the top and bottom of at least one of the labels (most likely the large center one) and the top and bottom of the stack view

Then auto layout should work properly and your custom view with the StackView should layout as expected.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值