Доклад: SwiftUI In A Nutshell / Тёма Пстыго (Авито)
Introduction to Swift UI
Overview of the Season
- The host, Misha, introduces the 15th season of "Подлодка AOSCW," focusing on Swift UI.
- Emphasizes that significant time has passed since their last discussion on Swift UI, highlighting its active development and new targets.
- The focus is on practical applications in production rather than theoretical testing.
Speaker Introduction
- Misha introduces co-host Alexander Andryukhin and welcomes offline listeners.
- Tёма Pstyga is introduced as a speaker from Avito, who specializes in Swift UI and community engagement.
Understanding Swift UI Concepts
Session Goals
- Tёма outlines the session's aim to explore fundamental concepts of Swift UI deeply.
- The goal is to shift thinking from UIKit to Swift UI by understanding how it simplifies development.
Declarative vs. Imperative Programming
- Discussion begins on the difference between declarative (Swift UI) and imperative (UIKit) programming approaches.
- In UIKit, developers think about events affecting state changes; in contrast, Swift UI focuses on states themselves.
Graph Theory Analogy
State Representation
- Tёма uses graph theory to illustrate how UIKit emphasizes transitions (edges), while Swift UI emphasizes states (nodes).
- Transitioning from edges to nodes reduces cognitive load from n² complexity to n complexity.
Practical Examples of Declarative Syntax
Layout Example
- A simple example demonstrates creating a red rectangle with padding using declarative syntax in Swift UI versus manual layout calculations in UIKit.
Data Binding Mechanism
- Discusses data flow between model and user interface through text fields, emphasizing two-way binding in both frameworks.
- In UIKit, explicit mechanisms are required for data transfer; however, Swift UI handles this more seamlessly.
Understanding Swift UI and Data Binding
The Concept of Binding in Swift UI
- In Swift, a declarative feature called binding creates a two-way communication channel between the UI model and the business logic model.
- An example is provided with a text field for username, which is tightly linked to the model's text property. Changes in the model reflect immediately in the UI and vice versa.
- This prevents desynchronization issues between the model and UI, ensuring real-time updates.
Exploring Identity in Swift UI
- The discussion shifts to the concept of Identity, crucial for understanding how Swift UI handles complex view rendering beyond simple examples.
- A simple view displaying a logo is introduced, demonstrating how its position can change based on an offset value controlled by user interaction.
Animation Mechanics in Swift UI
- When toggling options in the UI, changes trigger a re-evaluation of state and layout, causing visual transitions like moving logos across the screen.
- Applying an animation modifier to this frame allows for smooth transitions when changing values like offset. The mechanics behind this behavior are explored further.
Understanding Layout Interpolation
- Questions arise about how Swift UI animates transitions between different layouts despite having initially laid out views only once.
- The concept of identity becomes essential here; each view has a unique memory address (Object Identifier), while structures do not have fixed identities.
Types and Hierarchies in View Structures
- Swift UI uses type structures as identifiers during compilation to maintain hierarchy integrity within views displayed on-screen.
- Non-transparent types (opaque types), indicated by 'some', hide specific types from developers but allow access to underlying generic structures.
Practical Examples of Type Structures
- A practical example illustrates that adding elements like vertical stacks or frames modifies their generic types dynamically based on content structure.
- As more modifiers are applied (like padding), it becomes evident that each layer adds complexity to the overall view hierarchy.
Conclusion: Compiling View Hierarchies
- Ultimately, Swift UI compiles all potential view hierarchies at compile time, ensuring efficient rendering during runtime. This foundational understanding aids developers in creating responsive UIs effectively.
Understanding Frame and Alignment in SwiftUI
The Nature of Frames and Their Properties
- A frame is immutable, with its properties like alignment hardcoded during compilation. This means that the frame's position in the hierarchy is fixed.
- Changing the alignment does not structurally alter the frame; it remains the same despite any adjustments made to its layout or initialization.
- SwiftUI may require assistance when displaying lists of elements whose sizes and identities are unknown at compile time.
Handling Dynamic Lists in SwiftUI
- An example illustrates a list where the first item is a header, which does not need an explicit identity since it's known at compile time.
- Other items within a
ForEachloop must have their identities provided because they are not defined until runtime.
Using Identifiers Effectively
- When using list constructors or
ForEach, passingselfas an identifier can lead to issues with animation due to how hash values are computed.
- In a fruit-selling app scenario, toggling new arrivals should animate their movement rather than causing a full reload of the collection.
Problems with Using Self as ID
- Using
selffor IDs results in entire items being treated as new when any property changes, leading to unwanted animations such as disappearing and reappearing elements.
- This can also trigger modifiers like
onDisappear, resulting in unintended side effects during transitions.
Best Practices for Identifiers
- A better approach would be using stable identifiers from backend data models instead of relying on mutable properties like titles.
- Stable identifiers ensure uniqueness across screens and prevent runtime errors caused by duplicate entries.
Advanced Animation Techniques
- For complex animations (e.g., transitioning between sun and moon), explicit identifiers help manage transitions effectively while maintaining visual continuity.
Understanding View Transitions in Layouts
Transition Application to Views
- The transition effect is not currently applied to views that are already in the hierarchy, as they do not leave the screen. Transitions only occur when a view enters or exits the hierarchy.
- By explicitly defining a modifier ID for different states of a view, we can create a scenario where the view appears with one ID and then disappears, triggering a transition.
Layout Concepts
- The discussion shifts towards layout mechanics, which Apple refers to as "cooperative layout," though its cooperative nature may not be immediately clear.
- An example is introduced involving an app for submarines, highlighting design considerations such as padding around elements on the splash screen.
Custom Padding Implementation
- SwiftUI has built-in padding; however, custom padding implementation requires understanding how layouts function through protocols like
Layout.
- Two essential methods must be implemented:
sizeThatFits, which determines layout size based on proposed constraints and subviews, andplaceSubviews, which positions subviews within the coordinate space.
Size Adjustment Logic
- The initial implementation focuses on handling only one subview due to complexity. A guard statement ensures this limitation.
- The logic involves adjusting the proposed size by reducing it according to defined insets (padding), allowing for proper fitting of subviews within their designated area.
Understanding Proposed Sizes
- The overall layout size is calculated by increasing the adjusted size back with desired insets. This approach ensures that padding does not negatively impact small views.
- Proposed view sizes are similar to CG sizes but can be optional. This allows flexibility in layout management based on available dimensions.
Finalizing Layout Placement
- In implementing
placeSubviews, adjustments are made similarly by taking into account final rendering proposals while ensuring that all calculations respect defined boundaries and constraints.
Understanding Layout Cooperation in UI Design
The Concept of Layout Cooperation
- The layout cooperation principle indicates that while we can set the position of a subview using the
place atmethod, we cannot directly define its size. Instead, we provide a proposal for sizing.
- This cooperative approach means that the superview suggests dimensions, but ultimately, the subview decides its own size based on the provided proposal.
Debugging Layout Issues
- An issue arises when an element (logo) unexpectedly shifts from its intended center position after adding padding. This appears to be a bug.
- To debug this, borders are added to visualize frames; it becomes clear that the logo's bounds extend beyond expected limits due to misunderstanding how bounds are defined in relation to parent coordinates.
Understanding Bounds and Origin
- It is crucial to recognize that bounds do not always start at zero; they can have different origins depending on their parent view's coordinate space.
- Adjusting the origin by applying offsets helps correct positioning issues within layouts.
Utilizing Geometry Reader Effectively
What is Geometry Reader?
- Geometry Reader is a unique view that allows content definition as a function of its own size, useful for manual layout calculations.
- It assists in scenarios where precise mathematical adjustments are needed for layout distribution across available space.
Best Practices for Using Geometry Reader
- Use Geometry Reader as an overlay or background since it occupies only as much space as its parent view allows.
- It's particularly effective when elements have strict height or width constraints, ensuring they fit within specified dimensions without disrupting overall layout integrity.
When Not to Use Geometry Reader
- Avoid using Geometry Reader when an element’s size depends on content inside it. This creates a circular dependency problem between text size and geometry reader dimensions.
Challenges with Dynamic Text Sizing
Managing Text Size with Geometry Reader
- If you need text to occupy half of available space dynamically, using Geometry Reader may seem appropriate; however, text resizing introduces complexity due to non-linear behavior.
Circular Dependency Problem
- The challenge lies in wanting text size determined by both fixed geometry reader dimensions and dynamic content height—this results in conflicting requirements that cannot be resolved effectively with current tools.
Configuring Views in Swift: Constructor Patterns
Overview of Configuration Methods
- The discussion begins with an overview of three primary methods for configuring views in Swift, contrasting them with UI Kit.
- These methods include constructor arguments for fixed parameters, mutable properties for changeable attributes, and the delegate pattern for complex behaviors.
Constructors as a Key Mechanism
- In Swift, constructors play a more significant role compared to UIKit. They are essential for defining behavior types.
- An example is provided involving a component called "Avito," which allows selection states (selected/deselected) based on different modes defined by designers.
Behavior Typing through Constructors
- By choosing specific constructors, developers can enforce view behavior without needing additional fields like "Selection mode."
- This design choice ensures that the view's state aligns strictly with the selected constructor type.
Non-overlapping Behavior Separation
- Another example illustrates how constructors can separate non-overlapping behaviors using a circular progress bar with four distinct display modes.
- Each mode is represented by separate constructors, clearly documenting use cases and preventing unintended behavior overlaps.
Customization Flexibility in Components
- Constructors also serve as tools for flexible customization of components. This flexibility contrasts sharply with previous design systems where modifications required extensive changes or duplications.
- A toggle component example highlights this flexibility; it offers both a simple string-based constructor and an abstract builder allowing any custom label.
Environment-Based Configuration Modifications
- The second group of modifications involves configuration through environments—a dictionary-like structure that propagates changes throughout subtrees in the view hierarchy.
- This method enables bulk updates across multiple views efficiently, enhancing overall usability and maintainability within the UI framework.
Utilizing Environment in UI Design
Adapting Views to Color Schemes
- The discussion begins with the need for views to adapt based on certain conditions, such as color schemes (light/dark mode).
- An example is provided where a view is modified to align with the app's color scheme by redefining its environment, affecting all child views.
Propagating Styles through Environment
- When applying a button style modifier to a specific button within a stack of buttons, it can propagate this new style throughout all buttons due to their shared parent view.
- This method allows for consistent styling without cluttering individual component constructors with loading state arguments.
Reusable Behavior via Environment
- Components can check properties from the environment for states like loading, promoting reusable behavior across multiple components.
- Using modifiers intuitively helps developers understand how components behave without needing extensive documentation.
Introduction to Builder Pattern
- The builder pattern is introduced as another configuration method that appears similar but has distinct advantages in detail.
- It involves creating methods that return copies of structures, allowing chaining of modifiers and scoping them specifically to types.
Advantages of Builder Pattern
- The builder pattern prevents adding excessive arguments in constructors, which can lead to complexity when many customization points exist.
- It applies modifications only to specific views rather than an entire tree structure, enhancing clarity and maintainability.
Practical Example: Tab Group Component
- A practical example illustrates a Tab Group component that displays tabs with customizable counters linked directly to each tab group.
- The necessity of using the builder pattern arises because counters are context-specific and do not apply universally across different tab groups.
Limitations of Environment Usage
- Counters cannot be effectively managed through environment settings due to their specificity; they must relate directly to particular instances of tab groups.
- Generic constraints limit how items are defined within the tab group context, necessitating the use of builders for proper implementation.
Real-world Application: Image Component
- An example from standard libraries shows how image components utilize a builder pattern for resizing functionality, emphasizing its relevance only within that specific context.
Performance Comparison of Swift UI
Challenges in Comparing Swift and Other Frameworks
- The speaker notes that comparing Swift to other frameworks is challenging due to a lack of appropriate tools for such comparisons.
- Differences in timing and animations between similar components (e.g., APR vs. view lifecycle methods) complicate direct comparisons.
- The focus will be on identifying performance baselines within Swift UI using available tools rather than making direct comparisons.
Tools for Performance Improvement
Self Print Changes Method
- Introduces the "self print changes" method, which is poorly documented but mentioned as a key debugging tool in developer sessions.
- This method prints changes and reasons for body re-evaluations, helping identify unexpected property changes during development.
Xcode Instruments Profiler
- Discusses the use of Xcode Instruments, which provides templates specifically for Swift UI to track body calls and dynamic property changes.
- It tracks various dynamic properties like state binding and environment variables, crucial for understanding how views react to data changes.
Case Study: User Feedback View Implementation
Initial Setup
- A user feedback view is described, featuring an animated text field title and an emotion selection area at the bottom.
- The case study originates from a production scenario where users provide feedback about their app experience.
Analyzing Performance with Self Print Changes
- Using self print changes reveals that every minor change triggers a complete re-evaluation of the body, indicating potential inefficiencies.
Refactoring for Improved Performance
Reducing Body Calls
- After refactoring by separating components into distinct views (emotion slider and text input), significant performance improvements are noted.
Results from Profiling
- Post-refactor analysis shows a reduction in total body calls from 299 to 184, marking a 40% decrease in calls due to better component organization.
Conclusion on Performance Gains
Implications of Refactoring
- While fewer body calls do not guarantee reduced redraw operations, it suggests improved efficiency by minimizing unnecessary calculations within the view hierarchy.
Understanding Swift UI and Testing Strategies
Data Binding in Swift UI
- The text field in the view only transmits data through binding; it does not utilize the data directly within the view itself.
- Swift UI optimizes updates by recognizing that only nested views may change, allowing for efficient recalculations without affecting parent structures.
Encapsulation and Reusability
- Logic encapsulation is emphasized as a method to enhance reusability within the codebase.
- Standard Apple UI tests can be used, but they often present challenges due to differences in hierarchy compared to UIKit or Swift UI.
Challenges with UI Testing Frameworks
- The Mixbox framework was developed to address issues with existing testing frameworks, providing a more stable and clear approach to testing.
- Mixbox constructs an accurate hierarchy of views on the app side, facilitating better interaction during tests.
Snapshot Testing Insights
- Snapshot tests are actively utilized in design systems to ensure components do not change unexpectedly.
- Integrating snapshot testing into existing frameworks can require significant adjustments due to isolation constraints imposed by main actor requirements.
Limitations of View Rendering
- Views using
UIHostingViewmay not render correctly during snapshot tests, leading to unexpected visual outputs like yellow rectangles.
- This issue extends beyond custom views; even standard Apple views like
UIScrollViewface similar rendering challenges when tested.
Conclusion and Resources
- A QR code and link will provide access to project examples discussed during the session for further exploration of concepts presented.
Performance Impact of View Nesting in SwiftUI
Does Deep View Nesting Affect Performance?
- The question is raised about whether deep view nesting impacts performance and if there are optimization methods available.
- It is noted that, based on experience, deep view nesting should not significantly affect performance. Apple claims to compress the hierarchy effectively.
- When using UI debugging tools, fewer rectangles may be displayed than expected due to the way SwiftUI optimizes rendering by collapsing views.
- SwiftUI can render elements directly as bitmaps rather than maintaining separate rectangles in the hierarchy, which simplifies rendering processes.
- The speaker reassures that developers need not worry excessively about depth in view hierarchies.
Transitioning from UIKit to SwiftUI
- A participant expresses concern over the paradigm shift from UIKit to SwiftUI, particularly for those who have only worked with UIKit and are unfamiliar with web-like layout concepts.
- The differences in layout approaches (e.g., padding and spacing) can be confusing for developers transitioning from a pixel-based mindset typical of UIKit.
- There is uncertainty regarding when to use specific layout components like spacers and frames correctly within SwiftUI's framework.
Understanding Layout Systems
- The discussion acknowledges that while some foundational aspects of layout systems were covered, deeper insights into specific topics were limited during this session.
- The speaker emphasizes the importance of understanding identity within layouts as a crucial concept for effective development practices.
Resources for Further Learning
- Recommendations are made for additional sessions at Dadaab focusing on practical examples and guidelines for mastering layouts in SwiftUI.
- Links to resources will be shared to help attendees better understand how layouts function within SwiftUI.
Conclusion of Session
- The session concludes with an invitation to join an upcoming talk on deploying SwiftUI applications at scale.