I've realised that I don't understand AutoLayout.
I want to measure view's required height given the constant width.
This is my TestViewTwo.xib
TestViewTwo.swift
import UIKit
class TestViewTwo: UIView {
@IBOutlet weak var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
let nib = Bundle.main.loadNibNamed("TestViewTwo", owner: self, options: nil)
let view = nib!.first as! UIView
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
}
}
Test Controller
import Foundation
import UIKit
class TestControllerTwo : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testView = TestViewTwo()
let estimatedSize = testView.systemLayoutSizeFitting(CGSize(width: 200, height: 500))
print("Estimated size: \(estimatedSize), imageView.frame: \(testView.imageView.frame)")
}
}
The output is
Estimated size: (100.0, 500.0), imageView.frame: (0.0, 0.0, 414.0, 621.0)
I don't understand why estimated width is 100 ? Where is this coming from ? Why estimated height is 500 and not 300 (200x1.5) ? I also don't understand why imageView frame is set and why to such values
Please help me understand what I'm doing wrong.
I want to get estimatedSize = 200x300
I guess there is something fundamentally wrong I'm doing here. It is not about ratio that I use.
When I set constant width and height of image view
I get
Estimated size: (200.0, 500.0), imageView.frame: (0.0, 0.0, 200.0, 300.0)
When I set constant height only
I get
Estimated size: (100.0, 500.0), imageView.frame: (0.0, 0.0, 414.0, 300.0)
What is wrong in my layout / code so that I don't get estimatedSize = 200x300 ?
Let's deal with constant dimensions before moving to ratio problem.
You definitely need to constrain the bottom of the image view to the bottom of its superview...
Give the bottom constraint Priority: High (750)
Then, when you want to know the estimated Height based on a given rectangle:
let estimatedSize = testView.systemLayoutSizeFitting(CGSize(width: 200, height: 500),
withHorizontalFittingPriority: .defaultHigh,
verticalFittingPriority: .defaultLow)
print("Estimated size: \(estimatedSize), imageView.frame: \(testView.imageView.frame)")
// output: Estimated size: (200.0, 300.0), imageView.frame: (0.0, 0.0, 197.0, 295.5)
If you want to know the estimated Width based a given rectangle:
let estimatedSize = testView.systemLayoutSizeFitting(CGSize(width: 200, height: 500),
withHorizontalFittingPriority: .defaultLow,
verticalFittingPriority: .defaultHigh)
print("Estimated size: \(estimatedSize), imageView.frame: \(testView.imageView.frame)")
// output: Estimated size: (333.5, 500.0), imageView.frame: (0.0, 0.0, 197.0, 295.5)
Note that the imageView.frame
will NOT be set yet, so it will evaluate to whatever size you have it in IB.
Also note that we give the image view a Bottom constraint with a less-than-required Priority. This avoids the IB warnings when we don't have the view frame sized to exactly 1:1.5
ratio, and avoids auto-layout warning/errors messages at run-time.
Here is the source to the XIB:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_0" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TestViewTwo" customModule="DrawingTutorial" customModuleProvider="target">
<connections>
<outlet property="imageView" destination="QgA-Qr-3jM" id="MGu-3W-9i4"/>
</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="197" height="391"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QgA-Qr-3jM">
<rect key="frame" x="0.0" y="0.0" width="197" height="295.5"/>
<color key="backgroundColor" red="0.99998801950000005" green="0.62141335009999998" blue="0.00022043679199999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" secondItem="QgA-Qr-3jM" secondAttribute="height" multiplier="1:1.5" id="CpW-r1-rJA"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="0.45009386540000001" green="0.98132258650000004" blue="0.4743030667" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="QgA-Qr-3jM" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="3As-tz-AZL"/>
<constraint firstAttribute="trailing" secondItem="QgA-Qr-3jM" secondAttribute="trailing" id="4Q2-dC-O75"/>
<constraint firstItem="QgA-Qr-3jM" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="xJ2-05-m7l"/>
<constraint firstAttribute="bottom" secondItem="QgA-Qr-3jM" secondAttribute="bottom" priority="750" id="xy9-yL-2gg"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="109.6875" y="126.23239436619718"/>
</view>
</objects>
</document>
And example classes to demonstrate:
class TestControllerTwo : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testView = TestViewTwo()
// withHorizontalFittingPriority: .defaultHigh
// verticalFittingPriority: .defaultLow
// gives priority to the WIDTH
// returns a size based on fitting the Target WIDTH
let estimatedSizeW = testView.systemLayoutSizeFitting(
CGSize(width: 200, height: 500),
withHorizontalFittingPriority: .defaultHigh,
verticalFittingPriority: .defaultLow)
print("Width Priority Estimated size: \(estimatedSizeW)",
"imageView.frame: \(testView.imageView.frame)")
// withHorizontalFittingPriority: .defaultLow
// verticalFittingPriority: .defaultHigh
// gives priority to the HEIGHT
// returns a size based on fitting the Target HEIGHT
let estimatedSizeH = testView.systemLayoutSizeFitting(
CGSize(width: 200, height: 500),
withHorizontalFittingPriority: .defaultLow,
verticalFittingPriority: .defaultHigh)
print("Height Priority Estimated size: \(estimatedSizeH)",
"imageView.frame: \(testView.imageView.frame)")
}
}
class TestViewTwo: UIView {
@IBOutlet weak var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
let nib = Bundle.main.loadNibNamed("TestViewTwo", owner: self, options: nil)
let view = nib!.first as! UIView
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
}
}