How to show Profile Icon next to Large Navigation Bar Title in SwiftUI?

Viewed 2499

I am developing an App that supports multiple Profiles. I really like the way Apple displays the Profile Icon next to the Large Navigation Bar Title in all their Apps. See the Screenshot below:

Screenshot Example

My Question is the following:

  • Is it possible to achieve this in SwiftUI? And if so, how?
  • If it's not possible in pure SwiftUI, how can I achieve it including UIKit Code?

Thanks for your help.

2 Answers

I solved this by using SwiftUI-Introspect, to "Introspect underlying UIKit components from SwiftUI".

Here is an example of a view:

struct ContentView: View {

    @State private var lastHostingView: UIView!
    
    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(1 ... 50, id: \.self) { index in
                    Text("Index: \(index)")
                }
                .frame(maxWidth: .infinity)
            }
            .navigationTitle("Large title")
            .introspectNavigationController { navController in
                let bar = navController.navigationBar
                let hosting = UIHostingController(rootView: BarContent())
                
                guard let hostingView = hosting.view else { return }
                // bar.addSubview(hostingView)                                          // <--- OPTION 1
                // bar.subviews.first(where: \.clipsToBounds)?.addSubview(hostingView)  // <--- OPTION 2
                hostingView.backgroundColor = .clear
                
                lastHostingView?.removeFromSuperview()
                lastHostingView = hostingView
                
                hostingView.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    hostingView.trailingAnchor.constraint(equalTo: bar.trailingAnchor),
                    hostingView.bottomAnchor.constraint(equalTo: bar.bottomAnchor, constant: -8)
                ])
            }
        }
    }
}

Bar content & profile picture views:

struct BarContent: View {
    var body: some View {
        Button {
            print("Profile tapped")
        } label: {
            ProfilePicture()
        }
    }
}

struct ProfilePicture: View {
    var body: some View {
        Circle()
            .fill(
                LinearGradient(
                    gradient: Gradient(colors: [.red, .blue]),
                    startPoint: .topLeading,
                    endPoint: .bottomTrailing
                )
            )
            .frame(width: 40, height: 40)
            .padding(.horizontal)
    }
}

The .frame(width: 40, height: 40) & hostingView.bottomAnchor constant will need to be adjusted to your needs.

And the results for each option (commented in the code):

Option 1 Option 2
View sticks when scrolled View disappearing underneath on scroll
Result - Option 1 Result - Option 2

Without NavigationView

I done this with pure SwiftUI. You have to replace the Image("Profile") line with your own image (maybe from Assets or from base64 data with UIImage).

HStack {
    Text("Apps")
        .font(.largeTitle)
        .fontWeight(.bold)
    
    Spacer()
    
    Image("Profile")
        .resizable()
        .scaledToFit()
        .frame(width: 40, height: 40)
        .clipShape(Circle())
}
.padding(.all, 30)

This products following result: result displaying big bold word "Apps" and then my example profile pircture

With NavigationView

Let's assume that you have NavigationView and inside that there's only ScrollView and .navigationTitle. You can add that profile image there by using overlay.

NavigationView {
    ScrollView {
        //your content here
    }
    .overlay(
        ProfileView()
            .padding(.trailing, 20)
            .offset(x: 0, y: -50)
    , alignment: .topTrailing)

    .navigationTitle(Text("Apps"))
}

Where ProfileView could be something like this:

struct ProfileView: View {
    var body: some View {
        Image("Profile")
            .resizable()
            .scaledToFit()
            .frame(width: 40, height: 40)
            .clipShape(Circle())
    }
}

The result will be like this...

result of the code above

...which is pretty close to the App Store:

gif displaying App Store behaviour

Related