Back to Basics: UIKit Constraint and Auto Layout
Hi everyone, today we will revisit and deepen our understanding of UIKit constraints and auto layout. This article will be divided into three parts: first, we’ll explore intrinsic content size; next, we’ll discuss constraints and auto layout; and finally, we’ll examine how constraints and Auto Layout function within UIScrollView and UICollectionView.
In this article, we will use the creation of a ticker view as our practical use case. A ticker view is a common UI component that displays a continuous stream of information, such as news headlines or stock prices. By building this component, we will explore how to effectively apply UIKit constraints and auto layout principles. This hands-on example will help us understand intrinsic content size, configure constraints, and ensure our layout adapts correctly within UIScrollView or UICollectionView. Through this process, you’ll gain a deeper understanding of how to implement and troubleshoot auto layout in your own projects.
Understanding Intrinsic Content Size
The ticker view anatomy is outlined above. Components of UIKit utilized in its construction include UILabel, UITextView, UIImageView, UIStackView, UIScrollView, and UIView.
In the context of creating a ticker with dynamic height and constrained width, it’s important to consider the intrinsic content size of the components. Here’s a breakdown:
Intrinsic content size refers to the inherent dimensions or natural size of a UI element based on its content or internal properties. This intrinsic size allows these elements to adapt their dimensions based on the amount of content they contain.
Views with Intrinsic Content Size
- UILabel, Provides intrinsic size determined by their text, making it well-suited for dynamic height adjustments.
- UITextView Also has an intrinsic size determined by the text, allowing for dynamic height based on content. Make sure to disable the scrolling, this ensures that the components adapt to their content without unnecessary scrolling.
Views without Intrinsic Content Size
- UIImageView, Typically doesn’t have intrinsic size. Its dimensions are often determined by content or constraints.
- UIStackView, Doesn’t have intrinsic size; its size adapts dynamically based on arranged subviews.
- UIScrollView, Lacks intrinsic size; its size is determined by content and constraints.
- UIView, Similar to UIImageView, a UIView typically doesn’t have intrinsic size. Its dimensions are influenced by content and constraints.
Constraint and Auto Layout
Before delving into details, it’s essential to understand the concepts of content hugging, compression, and the fundamentals of constraints.
Content Hugging
Content hugging priority is a measure of how much a UI element resists growing larger than its intrinsic content size.
- Higher Priority: A higher content hugging priority means the element prefers to stay at or near its intrinsic content size, resisting expansion.
- Use Case: Useful when you want a view to maintain a compact size unless there’s a compelling reason for it to expand.
Content Compression Resistance
Content compression resistance priority is a measure of how much a UI element resists shrinking smaller than its intrinsic content size.
- Higher Priority: A higher content compression resistance priority means the element prefers to avoid reducing its size below its intrinsic size.
- Use Case: Useful when you want to prevent a view from becoming too small, especially when there’s limited space.
Basics of Constraints
Constraints are rules that define the layout and behavior of UI elements within a view.
- Types of Constraints: Constraints can specify attributes like width, height, spacing, and relationships between different elements.
- Auto Layout: In UIKit, Auto Layout is the system used to define and manage constraints.
- Adaptive Layout: Constraints enable adaptive layouts that adjust to different screen sizes and orientations.
In summary, content hugging and content compression resistance priorities are key factors in guiding Auto Layout on how to handle the size of UI elements based on their intrinsic content size. Constraints, on the other hand, are the rules and relationships that define the layout of these elements within a view.
Based on the breakdown provided earlier, prioritizing components with intrinsic size is essential to determine the content size of the ticker. To achieve this, let’s delve into some effective constraint and auto layout tricks.
- Setup the UILabel
- Set the number of lines to 0 for multiline text, allowing it to dynamically adjust its height based on content.
- Ensure that the content hugging and compression resistance priorities are set to
required
titleLabel.numberOfLines = 0
// below code just to make sure the priority is right, by default it already set to required
titleLabel.setContentHuggingPriority(.required, for: .vertical)
titleLabel.setContentCompressionResistancePriority(.required, for: .vertical)
2. Setup the UITextView
- Set
isScrollEnabled
tofalse
, to prevent the UITextView to allows the UITextView to adjust its size according to its content, effectively utilizing its intrinsic size.
contentTextView.isScrollEnabled = false
3. Setup the UIImageView
- As UIImageView lacks intrinsic content size based on its content, it is necessary to explicitly set width and height constraints to define its dimensions.
NSLayoutConstraint.activate([
iconImageView.widthAnchor.constraint(equalToConstant: 24),
iconImageView.heightAnchor.constraint(equalToConstant: 24)
])
4. Setup the UIStackView
- If we revisit the anatomy of the ticker, we observe that it comprises two stack views encapsulating the ticker components. The inner vertical stack envelops the
titleLabel
andcontentTextView
, and this, in turn, is enclosed within a horizontal stack alongside theiconImageView
. - The
UIStackView
inherently adapts its dimensions based on the intrinsic content size of its arranged subviews, such as thetitleLabel
andcontentTextView
. This dynamic adjustment is influenced by both the intrinsic sizes of the components and the specified settings for alignment and distribution within the stack view.
let labelStackView = UIStackView()
labelStackView.addArrangedSubview(titleLabel)
labelStackView.addArrangedSubview(contentTextView)
labelStackView.axis = .vertical
labelStackView.spacing = 4
- Similarly, the horizontal stack view functions by dynamically creating dimensions based on its content. With the UIImageView constraints set and distribution configured to fill by default, the horizontal stack view prioritizes the first child, compress it, and expands the second child — labelStackView — in the process.
let mainStackView = UIStackView(arrangedSubviews: [
iconImageView,
labelStackView
])
mainStackView.axis = .horizontal
mainStackView.alignment = .center
mainStackView.spacing = 8
5. Set mainStackView constraint
- Next, establish constraints for the
mainStackView
by aligning its top, leading, and trailing edges to its UIView container. - While these constraints ensure the stack view spans the full width, it’s important to include the bottom constraint. This additional constraint, even with intrinsic height from the content, precisely defines the vertical positioning of the
mainStackView
within its UIView container.
While the intrinsic height of the content inside the mainStackView is considered for its vertical dimension, setting the bottom constraint is crucial to define the content’s position relative to the bottom edge of the enclosing UIView. Without the bottom constraint, the vertical positioning of the mainStackView would be ambiguous, and Auto Layout wouldn’t have enough information to determine the exact vertical placement.
- By setting the bottom constraint, you explicitly instruct Auto Layout to position the mainStackView in a specific way, ensuring it has a well-defined position within its containing UIView. This becomes especially important when dealing with dynamic layouts or in scenarios where the intrinsic content size might not fully determine the overall vertical position.
mainStackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(mainStackView)
let inset = 8.0
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: inset),
mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -inset),
mainStackView.topAnchor.constraint(equalTo: topAnchor, constant: inset),
mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -inset)
])
With the completion of these five steps, we have successfully created a ticker content layout with dynamic height, allowing for adaptability based on the intrinsic sizes of its components.
UIScrollView vs UICollectionView
In the case of managing multiple contents for a paginated carousel, two viable options are UIScrollView and UICollectionView. This section will evaluate and determine which one is more suitable for achieving our goals.
In terms of adapting to intrinsic size, UICollectionView
generally outperforms UIScrollView
. However, when our objective is to ensure that either UICollectionView
or UIScrollView
accommodates the tallest content height for its overall height, there are distinct considerations.
In the case of UICollectionView
, the optimal approach involves calculating content size dynamically due to its lazy loading behavior. This is essential as we can't load all content simultaneously, making it challenging to identify the maximum height.
Conversely, UIScrollView
preloads all contents within its content view, facilitating the determination of contentSize based on the cumulative height of its contents. This capability enables us to identify the content with the greatest height and apply it as the UIScrollView's overall height.
Despite UIScrollView
lacking cell reusability and custom layout features, it efficiently meets our requirement with minimal code. Considering the expected usage scenario where a ticker may not contain an extensive amount of content, the absence of these advanced features becomes an acceptable trade-off for simplicity and ease of implementation.
Here’s a comparison table summarizing the considerations between UICollectionView
and UIScrollView
:
Considering the outlined considerations, UIScrollView proves to be the suitable choice meeting all the requirements. Therefore, we opt for UIScrollView. Now, let’s delve into the implementation details.
Setting Up UIScrollView
- Setup Properties
public final class TickerView: UIView {
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
return scrollView
}()
private let contents: [TickerContent]
...
}
2. Create Horizontal UIStackView
let contentStackView = UIStackView(arrangedSubviews: contentViews)
contentStackView.axis = .horizontal
contentStackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentStackView)
3. Configure each contentView to match the width of the parent view, enabling seamless pagination.
for contentView in contentViews {
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
}
4. Set UIScrollView the constraints
NSLayoutConstraint.activate([
contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
scrollView.heightAnchor.constraint(equalTo: contentStackView.heightAnchor)
])
contentStackView
is pinned to the leading, trailing, top, and bottom edges of thescrollView
.- The height of the
scrollView
is constrained to be equal to the height of thecontentStackView
. This ensures that the height of thescrollView
is determined by the height of the content within the stack view.
More explanation of scrollView.heightAnchor.constraint(equalTo: contentStackView.heightAnchor)
In this line of code, a constraint is set for the height of the scrollView
to be equal to the height of the contentStackView
. This constraint has a significant impact on how the scrollView
behaves and adapts to the content within the contentStackView
.
Here’s the breakdown:
- Height Constraint: This constraint establishes a relationship between the height of the
scrollView
and the height of thecontentStackView
. It essentially states that the height of thescrollView
should dynamically adjust to match the height of its content, which is thecontentStackView
. - Dynamic Adaptation: By setting the
scrollView
height equal to thecontentStackView
height, thescrollView
dynamically adjusts its own height based on the combined height of its content views within the horizontal stack. This ensures that thescrollView
can properly display and scroll through all the content within the stack view. - Automatic Sizing: As the content within the
contentStackView
changes or if different content views have varying heights, thescrollView
automatically adapts its height to accommodate the maximum height among the content views. This behavior is crucial for handling multiple contents of different sizes within the scrollable view. - Responsive Layout: The constraint ensures a responsive layout, allowing the
scrollView
to efficiently manage and display various content scenarios without having a fixed or predetermined height. It's particularly useful when dealing with dynamic content where the height is not known in advance.
In summary, by establishing this height constraint, the scrollView
becomes a flexible container that adjusts its height based on the varying heights of its content views within the contentStackView
.
With the aforementioned setup, we have successfully implemented a structure accommodating multiple contents within a UIScrollView, utilizing a horizontal stack view. Each content view dynamically adjusts its width to match that of the parent view, providing an effective and adaptable solution for handling diverse content scenarios.