可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a simple horizontal UIStackView
with several UIViews stacked inside. My goal is to create variable spacing between views. I am well aware that I can create constant space between the subviews using "spacing" property. However my goal is to create variable space. Please note, if at all possible, I would like to avoid using invisible views that act as spacers.
The best I came up with was to wrap my UIViews
in a separate UIStackView
, and use layoutMarginsRelativeArrangement = YES
to respect layout margins of my inner stack. I was hoping I could do something similar with any UIView
without resorting to this ugly work-around. Here is my sample code:
// Create stack view
UIStackView *stackView = [[UIStackView alloc] init];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
stackView.axis = UILayoutConstraintAxisHorizontal;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.layoutMarginsRelativeArrangement = YES;
// Create subview
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
// ... Add Auto Layout constraints for height / width
// ...
// I was hoping the layoutMargins would be respected, but they are not
view1.layoutMargins = UIEdgeInsetsMake(0, 25, 0, 0);
// ... Create more subviews
// UIView view2 = [[UIView alloc] init];
// ...
// Stack the subviews
[stackView addArrangedSubview:view1];
[stackView addArrangedSubview:view2];
The result is a stack with views right next to each other with spacing:
回答1:
Update For iOS 11, StackViews with Custom Spacing
Apple has added the ability to set custom spacing in iOS 11. You simply have to specify the spacing after each arranged subview. Unfortunately you can't specify spacing before.
stackView.setCustomSpacing(10.0, after: firstLabel)
stackView.setCustomSpacing(10.0, after: secondLabel)
Still way better than using your own views.
For iOS 10 and Below
You could simply add a transparent views into your stack view and add width constraints to them.
(Label - UIView - Label - UIView -Label)
and if you keep distribution
to fill, then you can setup variable width constraints on your UIViews.
But I would consider if this is the right situation to use stackviews if that's the case. Autolayout makes it very easy to setup variable widths between views.
回答2:
This will keep a coherent behavior between pre and post ios11 when spacing is different from 0 :
extension UIStackView {
func addCustomSpacing(_ customSpacing: CGFloat, after arrangedSubview: UIView) {
if #available(iOS 11.0, *) {
setCustomSpacing(customSpacing, after: arrangedSubview)
} else {
guard let arrangedSubviewIndex = arrangedSubviews.firstIndex(of: arrangedSubview) else {
return
}
let separatorView = UIView(frame: .zero)
separatorView.translatesAutoresizingMaskIntoConstraints = false
//calculate spacing to keep a coherent spacing with the ios11 version
let isBetweenExisitingViews = arrangedSubviewIndex != arrangedSubviews.count - 1
let existingSpacing = isBetweenExisitingViews ? 2 * spacing : spacing
let separatorSize = customSpacing - existingSpacing
guard separatorSize > 0 else {
return
}
switch axis {
case .horizontal:
separatorView.widthAnchor.constraint(equalToConstant: separatorSize).isActive = true
case .vertical:
separatorView.heightAnchor.constraint(equalToConstant: separatorSize).isActive = true
}
insertArrangedSubview(separatorView, at: arrangedSubviewIndex + 1)
}
}
}
回答3:
From Rob's response I created a UIStackView extension that might help:
extension UIStackView {
func addCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) {
if #available(iOS 11.0, *) {
self.setCustomSpacing(spacing, after: arrangedSubview)
} else {
let separatorView = UIView(frame: .zero)
separatorView.translatesAutoresizingMaskIntoConstraints = false
switch axis {
case .horizontal:
separatorView.widthAnchor.constraint(equalToConstant: spacing).isActive = true
case .vertical:
separatorView.heightAnchor.constraint(equalToConstant: spacing).isActive = true
}
if let index = self.arrangedSubviews.firstIndex(of: arrangedSubview) {
insertArrangedSubview(separatorView, at: index + 1)
}
}
}
}
You can use and modify it any way you want, for exemplo if you want the "separatorView" reference, you can just return the UIView:
func addCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) -> UIView?
回答4:
SWIFT 4
Following lilpit answer, here is an extension of the UIStackView to add a top and a bottom spacing to your arrangedSubview
extension UIStackView {
func addCustomSpacing(top: CGFloat, bottom: CGFloat) {
//If the stack view has just one arrangedView, we add a dummy one
if self.arrangedSubviews.count == 1 {
self.insertArrangedSubview(UIView(frame: .zero), at: 0)
}
//Getting the second last arrangedSubview and the current one
let lastTwoArrangedSubviews = Array(self.arrangedSubviews.suffix(2))
let arrSpacing: [CGFloat] = [top, bottom]
//Looping through the two last arrangedSubview to add spacing in each of them
for (index, anArrangedSubview) in lastTwoArrangedSubviews.enumerated() {
//After iOS 11, the stackview has a native method
if #available(iOS 11.0, *) {
self.setCustomSpacing(arrSpacing[index], after: anArrangedSubview)
//Before iOS 11 : Adding dummy separator UIViews
} else {
guard let arrangedSubviewIndex = arrangedSubviews.firstIndex(of: anArrangedSubview) else {
return
}
let separatorView = UIView(frame: .zero)
separatorView.translatesAutoresizingMaskIntoConstraints = false
//calculate spacing to keep a coherent spacing with the ios11 version
let isBetweenExisitingViews = arrangedSubviewIndex != arrangedSubviews.count - 1
let existingSpacing = isBetweenExisitingViews ? 2 * spacing : spacing
let separatorSize = arrSpacing[index] - existingSpacing
guard separatorSize > 0 else {
return
}
switch axis {
case .horizontal:
separatorView.widthAnchor.constraint(equalToConstant: separatorSize).isActive = true
case .vertical:
separatorView.heightAnchor.constraint(equalToConstant: separatorSize).isActive = true
}
insertArrangedSubview(separatorView, at: arrangedSubviewIndex + 1)
}
}
}
}
Then you would use it like this:
//Creating label to add to the UIStackview
let label = UILabel(frame: .zero)
//Adding label to the UIStackview
stackView.addArrangedSubview(label)
//Create margin on top and bottom of the UILabel
stackView.addCustomSpacing(top: 40, bottom: 100)
回答5:
I created a library LRCustomSpacingStackView to support UIStackView custom spacing on iOS 9 and above.
Simply replace UIStackView's builtin methods in your code with this library's extension methods, and specify spacing by setting any subview's lr_stackSpacing
property:
import LRCustomSpacingStackView
stackView.lr_addArrangedSubview(view1)
view1.lr_stackSpacing = UIEdgeInsets(top: 20, left: 30, bottom: 10, right: 0)
All UIStackView's methods have their replacements:
lr_arrangedSubviews
for arrangedSubviews
lr_addArrangedSubview(_:)
for addArrangedSubview(_:)
lr_removeArrangedSubview(_:)
for removeArrangedSubview(_:)
lr_insertArrangedSubview(_:at:)
for insertArrangedSubview(_:at:)
lr_stackSpacing
is more flexible and powerful than setCustomSpacing(_:after:)
. You are able to specify spacing of a view in 4 directions: top, right, bottom, left.