My problem - when I add arranged subviews to a stackview programmatically, they just pile up in the upper right-hand corner as shown below. Why aren't they stacking up? I've tried many, many things including using views that have an intrinsic size.
The stackview is called mainStack and comes from a xib. mainStack is a vertical stack with Alignment set to Fill and Distribution set to Fill Equally (see settings at the bottom of this question). It contains one UIView with a blue background. For the sake of this question I have added two views to mainStack using addArrangedSubviews. Here is what I'm getting:
And this is what I would expect:
This is the code for the xib:
class TaskSheet: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet weak var mainStack: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
let nib = UINib(nibName: "TaskSheet", bundle: nil)
nib.instantiate(withOwner: self, options: nil)
contentView.frame = bounds
addSubview(contentView)
}
}
And this is how I'm trying to add views to mainStack:
class PDFSheet: UIView {
var taskSheet: TaskSheet!
var sheetArray = [UIView]()
func makeSheet() -> [UIView] {
taskSheet = TaskSheet(frame: CGRect(x: 0, y: 0, width: 612, height: 792))
let newView1 = UIView(frame: CGRect(x: 0, y: 0, width: 240, height: 128))
newView1.heightAnchor.constraint(equalToConstant: 128).isActive = true
newView1.widthAnchor.constraint(equalToConstant: 240).isActive = true
newView1.backgroundColor = .green
let newView2 = UIView(frame: CGRect(x: 0, y: 0, width: 120, height: 64))
newView2.heightAnchor.constraint(equalToConstant: 64).isActive = true
newView2.widthAnchor.constraint(equalToConstant: 120).isActive = true
newView2.backgroundColor = .yellow
taskSheet.mainStack.addArrangedSubview(newView1)
taskSheet.mainStack.addArrangedSubview(newView2)
sheetArray.append(taskSheet)
return sheetArray
}
}
And, here's the xib showing the stackview settings, just in case...
I think I understand what you're going for.
Here's an example...
xib layout (top label, vertical stack view, bottom label):
the stack view's properties:
Code for TaskSheet
, PDFSheet
and sample view controller:
class TaskSheet: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet var mainStack: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
let nib = UINib(nibName: "TaskSheet", bundle: nil)
nib.instantiate(withOwner: self, options: nil)
addSubview(contentView)
NSLayoutConstraint.activate([
// constrain contentView on all 4 sides with 8-pts "padding"
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
])
}
}
class PDFSheet: UIView {
var taskSheet: TaskSheet!
var sheetArray = [UIView]()
override init(frame: CGRect) {
super.init(frame: frame)
_ = makeSheet()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_ = makeSheet()
}
func makeSheet() -> [UIView] {
taskSheet = TaskSheet()
let newView1 = UIView(frame: CGRect(x: 0, y: 0, width: 240, height: 128))
newView1.heightAnchor.constraint(equalToConstant: 128).isActive = true
newView1.widthAnchor.constraint(equalToConstant: 240).isActive = true
newView1.backgroundColor = .green
let newView2 = UIView(frame: CGRect(x: 0, y: 0, width: 120, height: 64))
newView2.heightAnchor.constraint(equalToConstant: 64).isActive = true
newView2.widthAnchor.constraint(equalToConstant: 120).isActive = true
newView2.backgroundColor = .yellow
taskSheet.mainStack.addArrangedSubview(newView1)
taskSheet.mainStack.addArrangedSubview(newView2)
sheetArray.append(taskSheet)
addSubview(taskSheet)
taskSheet.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain taskSheet on all 4 sides
taskSheet.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
taskSheet.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
taskSheet.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
taskSheet.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
])
return sheetArray
}
}
class TaskViewController: UIViewController {
var theSheetView: PDFSheet!
override func viewDidLoad() {
super.viewDidLoad()
theSheetView = PDFSheet()
theSheetView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(theSheetView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain the sheet view on all top, leading, trailing with 32-pts "padding"
theSheetView.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0),
theSheetView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
theSheetView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -32.0),
// NO height or bottom constraint
])
}
}
and here's the source of the xib file (to make it easy to check) Edit: whoops, pasted the wrong xml source -- fixed now:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TaskSheet" customModule="scratchy" customModuleProvider="target">
<connections>
<outlet property="contentView" destination="TFh-sZ-4cx" id="zaP-M3-nAu"/>
<outlet property="mainStack" destination="oGz-Bu-nCT" id="oCb-IB-Q4i"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="315"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TFh-sZ-4cx">
<rect key="frame" x="8" y="8" width="359" height="299"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Top Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jZ3-yl-TaR">
<rect key="frame" x="0.0" y="0.0" width="359" height="21"/>
<color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="oGz-Bu-nCT">
<rect key="frame" x="0.0" y="21" width="359" height="257"/>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bottom Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8bg-zV-k8Q">
<rect key="frame" x="0.0" y="278" width="359" height="21"/>
<color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.36312681436538696" green="0.3205370306968689" blue="0.87124341726303101" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="oGz-Bu-nCT" firstAttribute="top" secondItem="jZ3-yl-TaR" secondAttribute="bottom" id="3FB-p9-cGU"/>
<constraint firstItem="8bg-zV-k8Q" firstAttribute="leading" secondItem="TFh-sZ-4cx" secondAttribute="leading" id="7bO-hv-chQ"/>
<constraint firstItem="oGz-Bu-nCT" firstAttribute="leading" secondItem="TFh-sZ-4cx" secondAttribute="leading" id="G5h-mz-ag5"/>
<constraint firstItem="jZ3-yl-TaR" firstAttribute="top" secondItem="TFh-sZ-4cx" secondAttribute="top" id="T1H-hj-4jJ"/>
<constraint firstAttribute="bottom" secondItem="8bg-zV-k8Q" secondAttribute="bottom" id="TYr-rY-NAc"/>
<constraint firstAttribute="trailing" secondItem="oGz-Bu-nCT" secondAttribute="trailing" id="VA9-gN-L1a"/>
<constraint firstAttribute="trailing" secondItem="8bg-zV-k8Q" secondAttribute="trailing" id="Vv5-P9-EGo"/>
<constraint firstItem="jZ3-yl-TaR" firstAttribute="leading" secondItem="TFh-sZ-4cx" secondAttribute="leading" id="XZc-QB-dm1"/>
<constraint firstItem="8bg-zV-k8Q" firstAttribute="top" secondItem="oGz-Bu-nCT" secondAttribute="bottom" id="ayn-E8-jo9"/>
<constraint firstAttribute="trailing" secondItem="jZ3-yl-TaR" secondAttribute="trailing" id="v4D-bJ-ltC"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="TFh-sZ-4cx" secondAttribute="trailing" constant="8" id="cgO-BT-ruo"/>
<constraint firstItem="TFh-sZ-4cx" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="rAA-Vf-F0B"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="TFh-sZ-4cx" secondAttribute="bottom" constant="8" id="rhR-sH-KEq"/>
<constraint firstItem="TFh-sZ-4cx" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="sag-F8-NuC"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="148" y="49.925037481259373"/>
</view>
</objects>
</document>
Result:
EDIt Slightly modified code to produce the "expected result" image the OP added:
Aligment: Fill
and Distribution: Fill Equally
TaskSheet.xib
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TaskSheet" customModule="scratchy" customModuleProvider="target">
<connections>
<outlet property="contentView" destination="TFh-sZ-4cx" id="zaP-M3-nAu"/>
<outlet property="mainStack" destination="oGz-Bu-nCT" id="oCb-IB-Q4i"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="315"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TFh-sZ-4cx">
<rect key="frame" x="8" y="8" width="359" height="299"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="oGz-Bu-nCT">
<rect key="frame" x="0.0" y="0.0" width="359" height="299"/>
</stackView>
</subviews>
<color key="backgroundColor" red="0.36312681436538696" green="0.3205370306968689" blue="0.87124341726303101" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="oGz-Bu-nCT" firstAttribute="leading" secondItem="TFh-sZ-4cx" secondAttribute="leading" id="G5h-mz-ag5"/>
<constraint firstAttribute="bottom" secondItem="oGz-Bu-nCT" secondAttribute="bottom" id="SIv-DX-ZpP"/>
<constraint firstAttribute="trailing" secondItem="oGz-Bu-nCT" secondAttribute="trailing" id="VA9-gN-L1a"/>
<constraint firstItem="oGz-Bu-nCT" firstAttribute="top" secondItem="TFh-sZ-4cx" secondAttribute="top" id="hPW-P3-dsk"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="TFh-sZ-4cx" secondAttribute="trailing" constant="8" id="cgO-BT-ruo"/>
<constraint firstItem="TFh-sZ-4cx" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="rAA-Vf-F0B"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="TFh-sZ-4cx" secondAttribute="bottom" constant="8" id="rhR-sH-KEq"/>
<constraint firstItem="TFh-sZ-4cx" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="sag-F8-NuC"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="148" y="49.925037481259373"/>
</view>
</objects>
</document>
Classes
class TaskSheet: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet var mainStack: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
let nib = UINib(nibName: "TaskSheet", bundle: nil)
nib.instantiate(withOwner: self, options: nil)
addSubview(contentView)
NSLayoutConstraint.activate([
// constrain contentView on all 4 sides with 0-pts "padding"
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
}
}
class PDFSheet: UIView {
var taskSheet: TaskSheet!
var sheetArray = [UIView]()
override init(frame: CGRect) {
super.init(frame: frame)
_ = makeSheet()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_ = makeSheet()
}
func makeSheet() -> [UIView] {
taskSheet = TaskSheet()
let newView1 = UIView()
newView1.backgroundColor = .green
let newView2 = UIView()
newView2.backgroundColor = .yellow
let spacerView = UIView()
spacerView.backgroundColor = .clear
// to get the "expected result" as shown in the OP's image,
// a 3-part stack view with equal heights,
// an easy way is to add a clear "spacer view" as the
// first - "top" - arranged subview
taskSheet.mainStack.addArrangedSubview(spacerView)
taskSheet.mainStack.addArrangedSubview(newView1)
taskSheet.mainStack.addArrangedSubview(newView2)
sheetArray.append(taskSheet)
addSubview(taskSheet)
taskSheet.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain taskSheet on all 4 sides
taskSheet.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
taskSheet.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
taskSheet.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
taskSheet.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
return sheetArray
}
}
class TaskViewController: UIViewController {
var theSheetView: PDFSheet!
override func viewDidLoad() {
super.viewDidLoad()
theSheetView = PDFSheet()
theSheetView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(theSheetView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain the sheet view on top and leading at 40-pts (just so it's not flush with top/left of the view)
// with specified width: 612 and height 792
theSheetView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
theSheetView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
theSheetView.widthAnchor.constraint(equalToConstant: 612),
theSheetView.heightAnchor.constraint(equalToConstant: 792),
])
}
}
Result as run on iPad Air 3rd gen (to match the OP's width: 612, height: 792
specification):
Another Edit:
This may be closer to the OP's intent.
PDFSheet
class is now treated as a "view provider" rather than as a view itself.TaskSheet
views (currently containing only one view) will be added as a subview to the viewController's view.Same .xib file as above.
class TaskSheet: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet var mainStack: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
let nib = UINib(nibName: "TaskSheet", bundle: nil)
nib.instantiate(withOwner: self, options: nil)
addSubview(contentView)
NSLayoutConstraint.activate([
// constrain contentView on all 4 sides with 0-pts "padding"
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
}
}
class PDFSheet: UIView {
var taskSheet: TaskSheet!
var sheetArray = [UIView]()
func makeSheet() -> [UIView] {
taskSheet = TaskSheet(frame: CGRect(x: 0, y: 0, width: 612, height: 792))
let newView1 = UIView()
newView1.backgroundColor = .green
let newView2 = UIView()
newView2.backgroundColor = .yellow
let spacerView = UIView()
spacerView.backgroundColor = .clear
// to get the "expected result" as shown in the OP's image,
// a 3-part stack view with equal heights,
// an easy way is to add a clear "spacer view" as the
// first - "top" - arranged subview
taskSheet.mainStack.addArrangedSubview(spacerView)
taskSheet.mainStack.addArrangedSubview(newView1)
taskSheet.mainStack.addArrangedSubview(newView2)
sheetArray.append(taskSheet)
return sheetArray
}
}
class TaskViewController: UIViewController {
var theSheetView: PDFSheet!
override func viewDidLoad() {
super.viewDidLoad()
theSheetView = PDFSheet()
let views: [UIView] = theSheetView.makeSheet()
guard let v = views.first else {
fatalError("PDFSheet failed to create TaskSheet")
}
// note: At this point, the view has not been added to the
// view hierarchy. If you're going to do something with it,
// such as output it to a png or pdf, for example, you need
// to tell auto-layout to do its work
v.setNeedsLayout()
v.layoutIfNeeded()
let s = v.exportAsPdfFromView()
view.addSubview(v)
}
}
and (visually) the same result. This time, the resulting TaskSheet
view is just being added to the viewController's view, without any offset: