Xcode 15 / IOS 17 Breaks compact Landscape tab bar

**Run this code on IOS 16 or below, or build with XCode 14, and when you rotate your phone landscape you get a Tab Bar with with labels below the images rather than the default of labels to the left **

extension UITabBar {
   
    // the Master view controller shows the UITabBarItem icon next to the text
    
    override open var traitCollection: UITraitCollection {
        if UIDevice.current.userInterfaceIdiom == .pad {
            return UITraitCollection(horizontalSizeClass: .compact)
        }
      //  return super.traitCollection
        
        if UIDevice.current.userInterfaceIdiom == .phone {
                return UITraitCollection(horizontalSizeClass: .compact)
            }
           
        
        return super.traitCollection
    }
    
}

_**_**Build with Xcode 15 and run on IOS 17 Iphone and you get the following error message **_**_



Thread 1: "A trait environment returned a trait collection with unspecified values for traits that are not allowed to be unspecified. This is a serious application bug and will cause undefined behavior. This issue may be caused by your class overriding the traitCollection property getter, which is not supported. Make sure to use the appropriate API if you are trying to override traits. Trait Environment: <UITabBarSwappableImageView: 0x103847660; frame = (0 0; 0 0); opaque = NO; userInteractionEnabled = NO; image = <UIImage:0x281372c70 CGImage anonymous; (34 38)@3>; layer = <CALayer: 0x282135ea0>>; Trait Collection: <UITraitCollection: 0x10386d230; HorizontalSizeClass = Compact, PreferredContentSizeCategory = L>"

_____Serious bug? more than 100,000 users over 4 years and never had an issue with this code until now. This is preventing us from building with Xcode 15 and affecting our production.

It was suggested we try this, it prevents the crash but does not work.**

____**_
` 
       ` if UIDevice.current.userInterfaceIdiom == .phone {
            if #available(iOS 17.0, *) {
                return UITraitCollection(traitsFrom: [super.traitCollection, UITraitCollection(horizontalSizeClass: .compact)])
            } else {
                return UITraitCollection(horizontalSizeClass: .compact)
            }
           
        }

`

Post not yet marked as solved Up vote post of michelle211 Down vote post of michelle211
2.1k views
  • I solved the problem:

    override open var traitCollection: UITraitCollection { if UIDevice.current.userInterfaceIdiom == .pad { if #available(iOS 17.0, *) { self.traitOverrides.horizontalSizeClass = .compact } else { return UITraitCollection(horizontalSizeClass: .compact) } } return super.traitCollection }
Add a Comment

Replies

Overriding the traitCollection property getter in a UIView or UIViewController subclass has never been supported, and since iOS 13 you would see the following warning logged to the console if you tried to do this:

"Class ... overrides the -traitCollection getter, which is not supported. If you're trying to override traits, you must use the appropriate API."

As the message suggests, you need to use a supported API if you want to override (i.e. modify) trait values. This is critical so that the system knows when you are applying an override, and when the value of the override you're applying has changed.

Fortunately, in iOS 17 and later, this is extremely easy to do using the new traitOverrides property. For example, to set an override for the horizontal size class trait on a view, just do the following:

view.traitOverrides.horizontalSizeClass = .compact

If your app still supports iOS 16 and earlier, you need to use the one of the override trait collection APIs on UIViewController or UIPresentationController.

There is not a lot of documentation on this beyond the WDDC 23 video.

If I have a Tabcontroller managing 4 view controllers, where would this override go, I would think it should go on the TabBarItem, not. Each individual view, I tried placing it in ViewIsAppearing and it had no effect.

Forcing a Runtime error is really a great way to treat your developers. Also, we filed a DTS ticket on this. It's been more than 24 hours. Does Apple ignore service level agreements now, too?

This is causing production problems for us as we cannot build with Xcode 15 to IOS 17,

I solved the problem:

    override open var traitCollection: UITraitCollection {
        if UIDevice.current.userInterfaceIdiom == .pad {
            if #available(iOS 17.0, *) {
                self.traitOverrides.horizontalSizeClass = .compact
            } else {
                return UITraitCollection(horizontalSizeClass: .compact)
            }
        }
        return super.traitCollection
    }

I've tried this but it seems not to work.

    override open var traitCollection: UITraitCollection {
        if UIDevice.current.userInterfaceIdiom == .pad {
            if #available(iOS 17.0, *) {
                self.traitOverrides.horizontalSizeClass = .compact
            } else {
                return UITraitCollection(horizontalSizeClass: .compact)
            }
        }
        return super.traitCollection
    }

However this works. Unfortunately this method is deprecated starting iOS 17.

if UIDevice.current.userInterfaceIdiom == .phone {
    if #available(iOS 17.0, *) {
        return UITraitCollection(traitsFrom: [super.traitCollection, UITraitCollection(horizontalSizeClass: .compact)])
    } else {
        return UITraitCollection(horizontalSizeClass: .compact)
    }           
}

Still looking for a better solution.