reckart.blog Open in urlscan Pro
45.33.0.175  Public Scan

Submitted URL: http://reckart.blog/
Effective URL: https://reckart.blog/
Submission: On December 06 via api from US — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

TYLERRECKART

BlogArchiveAbout

BlogArchiveAbout




WRITING A TAB VIEW CONTROLLER IN SWIFT UI

Posted on June 1, 2023

In my opinion, Swift UI is a fantastic jumping-off point for less experienced
developers wanting to get into iOS development. It reminds me of my earliest
days of programming; just turn the clock back 15 years and swap Swift with HTML.
One of the great things is that a lot of the groundwork has been laid by Apple.
It's relatively easy to use Swift's built-in components to put together an app
that looks at home on Apple's platforms. However, Swift's built-ins aren't
exactly the most flexible components when it comes to adapting for custom UIs.
Writing a custom navigation controller gives you the ability to completely
control the user experience as they flow through your apps. Let's see just how
easy that is.

So, the first thing we'll need to do is define the view hierarchy. When working
with custom navigation, I have found that it is often easier and more natural to
have the tab view context live above the app's main navigation state, then
nesting navigation views within each of the individual tab paths. Let's define
these parent-level views through an enum declaration.

enum TabView {
    case home
    case search
    case discover
    case profile
    case settings
}


Then we need to consider the context of how the tab bar UI will render in
relation to our views. The simplest way to do this is by using a ZStack {}
wrapper to render the tab bar higher up in the application's z-axis and thus
above each of our child views. The child views will need to account for the
height of the wrapper, but that is a simple calculation. All we need to do to
handle the view switching is utilize a simple switch statement that will render
the target view based on the application's current state.

struct ContentView: View {
    // Define the currently active view. This should default to the home screen.
    @State private var activeView: TabView = .home

    var body: some View {
        ZStack {
            switch (activeView) {
                case .home:
                    Color.red
                case .search:
                    Color.blue
                case .discover:
                    Color.green
                case .profile:
                    Color.yellow
                case .settings:
                    Color.purple
            }

            // TabBar(activeView: $activeView)
        }
    }
}


We can then define the markup to render the view itself. For now, each tab
option will be represented by an SF Symbol until the button component is
defined.

struct TabBar: View {
    @Binding var activeView: TabView

    var body: some View {
        VStack(spacing: 0) {
            Spacer()
            
            Rectangle()
                .fill(Color(.systemGray5))
                .frame(height: 0.5)
            
            HStack(spacing: 0) {
                Image(systemName: "1.circle.fill")
                    .frame(maxWidth: .infinity)
                Image(systemName: "2.circle.fill")
                    .frame(maxWidth: .infinity)
                Image(systemName: "3.circle.fill")
                    .frame(maxWidth: .infinity)
                Image(systemName: "4.circle.fill")
                    .frame(maxWidth: .infinity)
                Image(systemName: "5.circle.fill")
                    .frame(maxWidth: .infinity)
            }
            .padding(.top, 15)
            .padding(.bottom, 30)
            .background(Color(.systemBackground))
        }
        .edgesIgnoringSafeArea(.bottom)
    }
}


At this stage you should have a screen that looks similar to this:



The tab bar group item is similarly straightforward in its markup:

struct TabBarGroupItem: View {
    @Binding var activeView: TabView

    var targetView: TabView
    var image: String
    
    var body: some View {
        Button(action: {
            withAnimation(.spring()) {
                // Update the view.
                self.activeView = targetView
                // Provide haptic feedback.
                UIImpactFeedbackGenerator(style: .medium).impactOccurred()
            }
        }) {
            VStack(spacing: 6) {
                Image(systemName: image)
                    .font(.system(size: 22, weight: .medium))
                    .foregroundColor(activeView == targetView ? .blue : .gray)
                
                Circle()
                    .fill(activeView == targetView ? .blue : .clear)
                    .frame(width: 4, height: 4)
            }
            .frame(maxWidth: .infinity)
        }
    }
}


With that defined, the TabBar component can be updated with the view-switching
markup.

HStack(spacing: 0) {
    TabBarGroupItem(activeView: $activeView, targetView: .home, image: "house")
    TabBarGroupItem(activeView: $activeView, targetView: .search, image: "magnifyingglass")
    TabBarGroupItem(activeView: $activeView, targetView: .discover, image: "safari")
    TabBarGroupItem(activeView: $activeView, targetView: .profile, image: "person.crop.circle")
    TabBarGroupItem(activeView: $activeView, targetView: .settings, image: "gearshape")
}


Now there are just a few more pieces we'll need to implement. Because we're
using the z-axis to render the tab bar above the application's content, a shared
Screen view can be used to provide a wrapper around the content for each tab
that will let you adjust behavior and placement with a few simple modifiers. As
seen in the snippet below, this is where you can use Swift's built-in transition
modifiers to add some flavor to the way your views switch.

struct Screen<Content>: View where Content: View {
    let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        content()
            .edgesIgnoringSafeArea(.top)
            .transition(.push(from: .leading))
    }
}


If you're feeling ambitious, you could easily implement an intelligent "push"
animation by defining the navigation scheme and using the index of the current
screen with the target screen to determine which direction to animate the push
from.

With that, the Screen view wrapper can be integrated into our application's root
view.

struct ContentView: View {
    @State private var activeView: TabView = .home

    var body: some View {
        ZStack {
            switch (activeView) {
                case .home:
                    Screen { Color.red }
                case .search:
                    Screen { Color.blue }
                case .discover:
                    Screen { Color.green }
                case .profile:
                    Screen { Color.red }
                case .settings:
                    Screen { Color.purple }
            }

            TabBar(activeView: $activeView)
        }
    }
}


That's it! You now have a tab view controller at the root of your app that you
have complete control over. You can add custom animations between views,
interactions on tab changes, and more.




FOR THE LOVE OF THE GAME

Posted on March 14, 2023

I played Magic: The Gathering for the first time in 2006 when I was eleven years
old. I remember spending time at a neighbor's house over the summer and seeing
that friend's older brother playing this card game that I was wholly unfamiliar
with. I was instantly interested.

By this time, I had spent much of my childhood playing collectible card games.
First playing Pokemon as an extension of the Gameboy games that I still hold
dear, then transitioned into years of playing and collecting Yu-Gi-Oh cards as I
went through elementary school. It didn't take long after I discovered Magic for
the game to become the primary focus for my collecting and playing. The
15-minute walk down the road to my local game shop to spend a hard-earned $3 on
a booster pack of Guildpact, Planar Chaos, and Future Sight became a weekly
ritual. As I went through high school and college, my interest in the game faded
over time. Programming became my driving interest, and the rest, as they say, is
history.

Continue Reading

© 2011-2024 Tyler Reckart. All Rights Reserved.

Subscribe to the RSS feed or the alternate JSON feed.