UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Just had a Tech Lab for SwiftUI at WWDC23 and they've utterly confused me - claim SwiftUI should never use CONDITIONALS or TRAITS!?

Forums > SwiftUI

I met with a great senior engineer, to look at a couple of really weird bugs I've got when trying to add SwiftUI to my UIKit app.

He looked at my code, then the view heirachy and said: "Shouldn't use conditionals in SwiftUI and using SwiftUI in a UIKit app and using traits to determine device rotation and size classes are mixing two orthogonal programming systems (swiftui and uikit) and you shouldn't do that."

But my problem, is how the hell do you NOT use conditionals and traits for size class and screen orientation when you're trying to build a multiplatform app that can work from the iPhone SE 1st Gen to a 13" iPad to a whatever size screen mac app?!

It even contradicted this lesson from Paul:

https://www.hackingwithswift.com/quick-start/swiftui/how-to-automatically-switch-between-hstack-and-vstack-based-on-size-class

As an example, here's the thing he had the biggest complaint about - but without conditionals and traits etc, how the hell am I supposed to deal with this complexity?!

struct QuickCalculationsView: View
{
  @Environment(\.horizontalSizeClass) var hSizeClass
  @Environment(\.verticalSizeClass)   var vSizeClass

  @ObservedObject           var   settings    : iDSMenuSettingsModel
  @StateObject    internal  var   model       : VerticalTabBarModel  = VerticalTabBarModel()
                  internal  var   keypad      : NumberPadView        = NumberPadView()
                  internal  var   linePadView : iPhoneNumberLineView = iPhoneNumberLineView()

  var body: some View
  {
    ZStack
    {
      Color.getSwiftUICustomApplicationBackgroundColour()
        .ignoresSafeArea(.all)

            GeometryReader
      { geometry in
        if geometry.size.height > geometry.size.width              // PORTRAIT
        {
          if hSizeClass == .compact
          {
            portraitArrangement(Width: geometry.size.width, Height: geometry.size.height)
          }
          else
          {
            if settings.isLeftHanded == YES
            {
              landscapeArrangementLeftHanded(Width: geometry.size.width, Height: geometry.size.height)
            }
            else
            {
              landscapeArrangementRightHanded(Width: geometry.size.width, Height: geometry.size.height)
            }
          }
        }
        else                                                       // LANDSCAPE
        {
          if hSizeClass == .compact
          {
            portraitArrangement(Width: geometry.size.width, Height: geometry.size.height)
          }
          else
          {
            if settings.isLeftHanded == YES
            {
              landscapeArrangementLeftHanded(Width: geometry.size.width, Height: geometry.size.height)
            }
            else
            {
              landscapeArrangementRightHanded(Width: geometry.size.width, Height: geometry.size.height)
            }
          }
        }
      }
    }
  }

  @ViewBuilder func portraitArrangement(Width width: CGFloat, Height height: CGFloat) -> some View
  {
    VStack(alignment: .center, spacing: 0)
    {
      if      UIScreen.main.traitCollection.userInterfaceIdiom == .phone
      {
        VerticalTabBarView(model: model, settings: settings)
          .frame(width: width, height: 0.55 * height)
          .fixedSize()

        self.keypad
          .frame(width: width, height: 0.45 * height)
          .fixedSize()
          .offset(x: 0, y: -5)
      }
      else if UIScreen.main.traitCollection.userInterfaceIdiom == .pad
      {
        VerticalTabBarView(model: model, settings: settings)
          .frame(width: width, height: 0.75 * height)
          .fixedSize()

        self.keypad
          .frame(width: width, height: 0.4 * height)
          .fixedSize()
      }
    }
  }

  @ViewBuilder func landscapeArrangementLeftHanded(Width width: CGFloat, Height height: CGFloat) -> some View
  {
    VStack(alignment: .center, spacing: 0)
    {
      if      UIScreen.main.traitCollection.userInterfaceIdiom == .phone
      {
        VerticalTabBarView(model: model, settings: settings)
          .frame(width: width, height: 0.8 * height)
          .fixedSize()

        self.linePadView
      }
      else if UIScreen.main.traitCollection.userInterfaceIdiom == .pad
      {
        VerticalTabBarView(model: model, settings: settings)
          .frame(width: width, height: 0.6 * height)
          .fixedSize()

        self.keypad
          .frame(width: width, height: 0.375 * height)
          .fixedSize()
          .offset(x: 0, y: 0)
      }
    }
  }

  @ViewBuilder func landscapeArrangementRightHanded(Width width: CGFloat, Height height: CGFloat) -> some View
  {
    VStack(alignment: .center, spacing: 0)
    {
      if      UIScreen.main.traitCollection.userInterfaceIdiom == .phone
      {
        VerticalTabBarView(model: model, settings: settings)
          .frame(width: width, height: 0.8 * height)
          .fixedSize()

        self.linePadView
      }
      else if UIScreen.main.traitCollection.userInterfaceIdiom == .pad
      {
        VerticalTabBarView(model: model, settings: settings)
          .frame(width: width, height: 0.6 * height)
          .fixedSize()

        self.keypad
          .frame(width: width, height: 0.375 * height)
          .fixedSize()
          .offset(x: 0, y: 0)
      }
    }
  }
}

Any advice would be really appreciated...

2      

Argh, lost my buffer, which had a longer answer with some links. Sorry about that.

Basically:

  1. If you're using iOS 16 or later, you should use ViewThatFits instead of calculating with GeometryProxy. (This was said to be an anti-pattern last year in a breakout session, but it's the only one we had before then.) I'd suggest watching last year's SwiftUI WWDC sessions for more info.
  2. I'd suggest putting the UIKit-specific stuff in a View Model var to reduce calculation complexity.
  3. Also as a note, visionOS will run UIKit on top of SwiftUI (rather than underneath/alongside), so that could cause some interesting bugs with the way you have it constructed. (Edit: source on this was a screencap from one of the WWDC breakout sessions.)

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.