NavigationView Issues in iOS 18 Beta 3

I have a SwiftUI NavigationView that is presented inside of a UIHostingController. In iOS 17 and earlier everything looks and works okay.

I opened up my project in iOS 18 Beta 3 via the iOS Simulator running on macOS Sequoia, the NavigationView just displays a blank view with a Sidebar Button in the top left outside of the safe area. If I comment out the NavigationView and just display my 'root view' everything looks as it should but navigation is broken as it uses NavigationLink which of course doesn't work without NavigationView.

I still target iOS 14 as Minimum Version so swapping completely to NavigationStack isn't possible given that NavigationView is deprecated from iOS 16 onwards.

I tried to implement a solution with NavigationStack, it works but my UI displays outside of the safe area. I have a button towards the bottom of the screen which now overlaps the system line at the bottom of the screen. The Back Button in the Navigation Stack now sits behind the time in the top left. I do not have 'ignoreSafeArea' applied anywhere in this View Hierarchy. It looks like that is being applied by 'NavigationStack' on it's own accord.

Is this happening to anyone else? Is this a known bug? I could not see this in the release notes if it is known.

Thanks!

Hi @bradley_7 ,

Could you try to reproduce this with a new, small sample project and attach code snippets as necessary so I can replicate your issue? I created a new iOS storyboard based project and used UIHostingController(rootView: TestNav) in the SceneDelegate to launch:

struct TestNavigation: View {
    var body: some View {
        NavigationStack {
            List(0..<10) { item in
                NavigationLink(item.description) {
                    Text(item.description)
                }
                
            }
            .navigationTitle("TestNav")
        }
    }
}

This worked in iOS 18. I also replaced NavigationStack with NavigationView, which also worked.

Since I'm not able to replicate it with this small amount of code, I am wondering if there's something else in the view or something in your destination views that is causing this problem.

Best,

Sydney

Thanks Sydney!

I created a sample project and the issue persists, please see the below code snippet:

import UIKit
import SwiftUI

class ViewController: UIViewController {
    
    @IBOutlet weak var containerView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let onboardingView = UIHostingController(rootView: FirstLoadOnboardingView())
        SwiftUIBrain().assign(hostingController: onboardingView, to: containerView)
    }


}

struct SwiftUIBrain {
    
    func assign(hostingController: UIHostingController<some View>, to uiKitView: UIView) {
        uiKitView.addSubview(hostingController.view)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.topAnchor.constraint(equalTo: uiKitView.topAnchor).isActive = true
        hostingController.view.bottomAnchor.constraint(equalTo: uiKitView.bottomAnchor).isActive = true
        hostingController.view.leftAnchor.constraint(equalTo: uiKitView.leftAnchor).isActive = true
        hostingController.view.rightAnchor.constraint(equalTo: uiKitView.rightAnchor).isActive = true
    }
    
}

struct FirstLoadOnboardingView: View {
    
    var body: some View {
        NavigationView {
            Rectangle().foregroundColor(.red)
        }
    }
    
}

The 'containerView' is setup as per the below images with constraints to the edge of the display:

NavigationView in the sample project still just displays the Sidebar Button in the top left and does not show the Red Rectangle. I'm not sure why it is doing this.

If you comment out NavigationView and just have the Red Rectangle View it shows as it should, that is a SwiftUI View that respects the safe areas.

It seems that if I use NavigationStack and modify the constraints of the 'containerView' it will work as expected, however those constraints work in iOS 17 with NavigationView and display correctly as is, so it'd mean I'd need to programatically change the constraint values if it's running on iOS 18.

My understanding of this is that I am giving the SwiftUI View the full screen to work with within the UIViewController, as I am using NavigationStack it should apply it's own Safe Area Insets considering the NavBar based on the iPhone. But it seems that NavigationStack does not create it's own safe area insets in iOS 18. I just tested NavigationStack in iOS 17 and the Safe Area Insets are created and respected.

So I think there are two issues here:

  1. NavigationView does not display content and shows a Sidebar Button
  2. NavigationStack does not create it's own Safe Area Insets

Thank you!

Hey @bradley_7 ,

Thanks for the code! I put that exact code into a new Storyboard based project and put a container view into my storyboard and set the constraints as you have. I had no issues running this on iOS 18, can you tell me what version of Xcode you're using?

I'm attaching an image of my storyboard setup as well as one of me switching it to safe area instead of superview for the constraints. I'm curious if that will help you.

Thanks for getting back so quickly.

I am using Xcode 16.0 beta 3 on macOS 15.0 beta 3 (24A5289h) running in a VM with a 14.5 Host.

At a glance, one difference I can see if that I am using a UIView as my 'containerView' where it looks you are using a 'UIContainerView'.

I understand that I can switch to using Safe Area and tweak all of the subsequent views, but in iOS 17 it is setup as Superview and all safe area insets are handled on the Swift UI Side.

It seems I can not upload my project here as a Zip File, this is a link to download my project so we are 100% on the same page:

https://drive.google.com/file/d/1cCaY4trWLJqDSxtxTwUGqwQIfQ305_j2/view?usp=drivesdk

I just did a test with a UIContainerView instead of a UIView as the 'containerView' in which the SwiftUI View is drawn.

Everything works as expected in UIContainerView. It seems that there are changes to UIView in iOS 18 that impact how it handles safe area insets.

Ah, it seems that UIContainerView is the fix here :) I was able to reproduce the issue with a UIView

Yes, that is what I thought until I tried to use the UIContainerView in my actual project, rather than the new, bare bones test project. Using UIContainerView in my actual project still has the same issues as described above.

Also, I noticed that UIContainerView isn't actually a subclass of UIView, it is just a UIView inserted as a child. So it's unclear exactly why using UIContainerView via Interface Builder fixes the problem in the Test Project and why UIView is functioning different than it did in iOS 17.

Hey @bradley_7 ,

This does seem like unexpected behavior, but given that NavigationView is deprecated, there's not much we can do other than have you move to NavigationStack. I think the best bet here, since you said you need to support iOS 14 still, is to use an availability check:

struct FirstLoadOnboardingView: View {
    var body: some View {
        if #available(iOS 16.0, *) {
            NavigationStack {
                InnerNavigationView()
            }
        }
        else {
            NavigationView {
                InnerNavigationView()
            }
        }
    }
}

struct InnerNavigationView: View {
    var body: some View {
        Rectangle().foregroundColor(.red)
    }
}

This way it'll run correctly no matter what device you're using.

Hello,

I took a little bit of a break from looking at this and then decided to come back to it today.

I tried to proceed with a solution using NavigationStack but encountered two issues. I'll detail them below.

My NavigationStack follows this hierarchy. Welcome -> Enter Name -> Enter Job

I use NavigationLink as follows to go from one view to another:

NavigationLink(destination: OnboardingNameView(vc: vc)) {
                OnboardingButtonView(buttonString: "next".localized())
}

Issue 01:

If I press Next on WelcomeView, it takes me to EnterNameView as expected. If I hit back to return to WelcomeView, the Next Button no longer works until I force quit and re-open the app.

Issue 02:

On EnterNameView the Next Button is greyed out and can not be pressed. Normally it will appear blue and can be used as normal.

I tested both of these issues in iOS 17 vs iOS 18 using the exact same code. No issues in iOS 17 and everything works as expected.

It seems to fix this I may need to completely re-build every facet of my navigation. This is a lot of work so I opted to look for an alternate solution.


Alternate Solution

Previously I detailed that NavigationView would present a blank screen with a sidebar button. This is still the case. I could never pressed the sidebar button as it would appear outside of the safe area and thus taps would not work. When I added padding I could press the button and it printed this in the console:

unknown display mode: Automatic

With this in mind I found that you can still use NavigationView in iOS 18 with:

.navigationViewStyle(.stack)

It seems that the .automatic navigationViewStyle has been removed in iOS 18 which is why it doesn't work by default.

However I still had an issue. Same code, in iOS 17 it automatically adds safe area insets to NavigationView or NavigationStack, in iOS 18 this is not the case.

I found that if I add 0.5 Vertical Padding as per the below the NavigationView and/or NavigationStack would show as intended:

.padding(.vertical, 0.5)

The only other issue that I am facing is that the slide animation when you navigate forward and back in a NavigationView or NavigationStack does not work in iOS 18 which my existing code. It does work with a clean project using NavigationStack and Rectangles with different colors. I'm not sure what it is in my existing code that causes the animation to break, however the animation works fine in iOS 17. I have seen that a few other developers have had the 'no animation' issue in iOS 18.

As this overarching issue essentially breaks my onboarding process which means new users can not access the app if running iOS 18, I opted for a manual version check workaround. For reference, Apple will not let you use a normal version check for an unreleased iOS Version as per the below:

if #available(iOS 16.0, *)

Please see below for a complete code snippet with all workarounds. I may not have animation on iOS 18 but at least users can use my app as intended on iOS 17 and still access it if they opt to try the iOS 18 Beta.

import SwiftUI

struct FirstLoadOnboardingView: View {
    @StateObject var vc: FirstLoadOnboardingViewController
    @State private var verticalPadding: Double = 0
    
    var body: some View {
        NavigationView {
            OnboardingWelcomeView(vc: vc)
        }
        .navigationViewStyle(.stack)
        .padding(.vertical, verticalPadding)
        .onAppear {
            let systemVersion = UIDevice.current.systemVersion
            if systemVersion.contains("18.") {
                // Update Vertical Padding for iOS 18
                verticalPadding = 0.5
            }
        }
    }
}
NavigationView Issues in iOS 18 Beta 3
 
 
Q