top of page

iOS Navigation Series: Data Flow & Data Sharing using SwiftUI

Writer: Eric PalmaEric Palma

Updated: 2 days ago

In our previous post, we introduced you to the foundational concepts of Stack-Based and Tabbed Navigation, which are central to iOS app navigation. As we dive further in, our attention turns to two critical elements: data flow and data sharing. This post will explore the inner workings of how data seamlessly traverses through your app's navigation framework and how it's shared among different components. In a nutshell, by the end of this post we will know how to pass data between screens as well as how to share data between them using SwiftUI.


If you haven’t already, I recommend you check out our previous post introducing the basics of iOS navigation using SwiftUI.


Data Flow vs Data Sharing


Data Flow and Data Sharing are related concepts in app development, but they serve different purposes. Before we explore each in more detail it would be helpful to get a clearer understanding of each.

Data flow vs Data sharing

Data flow refers to the movement of data within an application, often in a unidirectional manner. It focuses on how data is passed between different parts or components of an app, typically in a structured and organized way.


Examples:

  • Passing data from a parent view to a child view.

  • Managing the flow of user input through an app's logic.


Data sharing involves making data accessible to multiple parts of an application, often for collaboration or synchronization. Data sharing is particularly useful when multiple views or components need access to a common data source or when you want to maintain consistency across different parts of the app.


Examples:

  • Sharing a user's authentication status or profile data across various views.

  • Allowing different tabs of a tabbed interface to access a common dataset.


In summary, data flow primarily deals with how data is passed and updated within an app, focusing on the flow of data from one point to another. Data sharing, on the other hand, deals with making data accessible and consistent across various parts of the app, allowing multiple components to work with the same dataset. While they are related, they serve different aspects of data management and communication within an application.


Data flow in Stack-Based Navigation


Continuing from the previous section, data flow thrives on structure and organization. One way to bring structure into our data flow is by making it unidirectional, where information moves in a single, clear direction. This approach enhances the scalability and maintainability of our applications. With unidirectional data flow, it becomes more straightforward to reason about data interactions and ensure the long-term health of our app.


In our previous introductory post, we learned how the hierarchical nature of Stack-Based navigation inherently provides a sense of structure. Consequently, when working with SwiftUI's NavigationStack, data flow often takes center stage. To shed further light on this concept, let's explore two common data flow scenarios encountered when using a navigation stack:

  1. Passing data from a parent view to a child view

  2. Passing data from an ancestor view to a descendant view deep in the navigation stack


passing data from parent to child vs passing data from ancestor to descendant view


Passing data from parent view to child view


The first case is generally the most common scenario encountered when using stack-based navigation. We have a parent screen that presents a child screen using a NavigationStack, however, the child screen needs some data from the parent screen in order to display the correct information. We can typically pass data directly from the first screen to the second through the initializer as shown in the code below:

In our example code above we pass the Article data needed by the ArticleDetailView directly through the initializer. This approach helps maintain a clear contract between the parent and child views, making your code more robust and easier to understand. However, in more complex navigation structures, you may encounter scenarios where data needs to be passed from an ancestor view down to a descendant view.


Passing data from ancestor view to descendant view


This is the second case which you may encounter when you have several screens added to your navigation stack. This scenario often arises in apps with complex hierarchies or when information must be passed across multiple screens deep within your application. There are two practical options in SwiftUI:

  1. Pass data down through the initializers of each screen (as we did above)

  2. Use SwiftUI’s @Environment decorator


Although the initializers of views offer a straightforward means of passing data, this approach can quickly become unwieldy, particularly as your navigation stack grows. Moreover, it becomes less practical when dealing with multiple data types or when not all descendant views rely on the same data. To address these challenges and simplify the data flow process, SwiftUI provides us with the powerful @Environment decorator. With @Environment, we can ensure that the right data is accessible to the right views without the need for explicit initializer-based passing.


Imagine we're building a note-taking app that allows users to organize their notes into various categories such as “Work”, “Personal”, and “Hobbies”. Each category has its own unique color theme to provide a visual distinction. All views within a category should make use of the same color corresponding to their category. Additionally, when users navigate into the details of individual notes, we want to display the current category name.



Here's where @Environment proves invaluable. By utilizing @Environment, we can effortlessly propagate the selected category's name and color theme throughout the view hierarchy, ensuring a cohesive and user-friendly experience across all notes within the same category.


First, we have to define the @Environment values we need. We start by subclassing EnvironmentKey to define keys by which to access the values in our environment. Then we extend the EnvironmentValues struct with a new property for each new value. Following is the code used to define the categoryColor environment value we will use later to propagate our category colors (categoryName follows exactly the same process):

      Want to read more?

      Subscribe to curiousalgorithm.com to keep reading this exclusive post.

      Site
      Join the growing community of iOS engineers who are taking their skills to the next level.

      © 2025 Curious Algorithm. All rights reserved.

      bottom of page