What's new in Xcode 13 beta 3

SwiftUI
15 July 2021

Xcode 13 beta 3 has just been released, and it comes with new SwiftUI goodies: let's have a look at what's new!

Shape style erasing with AnyShapeStyle

/// A type-erased ShapeStyle value.
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@frozen public struct AnyShapeStyle : ShapeStyle {
  /// Create an instance from `style`.
  public init<S>(_ style: S) where S : ShapeStyle
}

While we have @ViewBuilder to build advanced views, the same cannot be said for shapes. Until today, offering different shape styles meant defining multiple times the same view with different shape styles.

New from this beta seed, we have AnyShapeStyle, which makes our life easier:

struct ContentView: View {
  @State private var isAngular = false

  var body: some View {
    ZStack {
      Rectangle()
        .fill(shapeStyle)
        .ignoresSafeArea()
      Button(isAngular ? "Angular" : "Linear") { isAngular.toggle() }
        .buttonStyle(.bordered)
        .controlProminence(.increased)
    }
  }

  var shapeStyle: some ShapeStyle {
    let gradient: Gradient = Gradient(colors: [.red, .pink, .purple])
    if isAngular {
      return AnyShapeStyle(.conicGradient(gradient, center: .center)) // 👈🏻
    } else {
      return AnyShapeStyle( // 👈🏻
        LinearGradient(gradient: gradient, startPoint: .leading, endPoint: .trailing)
      )
    }
  }
}

Maybe we can get a @ShapeStyleBuilder next? 🙏🏻 FB9331755

Styles re-organization with HierarchicalShapeStyle

We have a new HierarchicalShapeStyle, used for all the .primary, .secondary, etc styles. Previously each one of these styles had their own style, e.g. QuaternaryContentStyle for .quaternary.

All the old styles have been deprecated and will likely be removed in an upcoming beta seed.

From:

extension ShapeStyle where Self == PrimaryContentStyle {
  public static var primary: PrimaryContentStyle { get }
}

extension ShapeStyle where Self == SecondaryContentStyle {
  public static var secondary: SecondaryContentStyle { get }
}

...

To:

extension ShapeStyle where Self == HierarchicalShapeStyle {
  public static var primary: HierarchicalShapeStyle { get }
  public static var secondary: HierarchicalShapeStyle { get }
  public static var tertiary: HierarchicalShapeStyle { get }
  public static var quaternary: HierarchicalShapeStyle { get }
}

Angular Gradient convenience initializers

Angular gradients have gained new static convenience initializers (available from iOS 13 and later):

extension ShapeStyle where Self == AngularGradient {
  public static func conicGradient(
    _ gradient: Gradient, center: UnitPoint, angle: Angle = .zero
  ) -> AngularGradient

  public static func conicGradient(
    colors: [Color], center: UnitPoint, angle: Angle = .zero
  ) -> AngularGradient

  public static func conicGradient(
    stops: [Gradient.Stop], center: UnitPoint, angle: Angle = .zero
  ) -> AngularGradient
}

For an example on this, go back to the AnyShapeStyle example, I've sneaked one of those in there 😜

keyboardShortcut environment value

struct ContentView: View {
  var body: some View {
    VStack {
      Button("Tap me or press ⌘F") {
        print("tapped or pressed")
      }
      .keyboardShortcut("F", modifiers: [.command])

      Button("Tap me or press ⌘S") {
        print("tapped or pressed")
      }
      .keyboardShortcut("S", modifiers: [.command])
    }
    .buttonStyle(FiveStarsButtonStyle())
  }
}

private struct FiveStarsButtonStyle: ButtonStyle {
  @Environment(\.keyboardShortcut) private var shortcut: KeyboardShortcut? // 👈🏻

  func makeBody(configuration: Configuration) -> some View {
    configuration.label
      .font(.body.weight(shortcut == .init("F") ? .heavy : .regular))
  }
}

Xcode 13 Beta 3 introduces new \.keyboardShortcut environment value, letting us read the assigned keyboard shortcut to any view. We can use this value as we please, for example to customize the appearance of a button.

More compact TimelineView definitions

Similar to styles, the SwiftUI team is pushing us to use the more compact syntax for the new TimelineView.

From Xcode 13b3, this generates a deprecation warning:

TimelineView(EveryMinuteTimelineSchedule()) { context in
  Text(context.date.description)
}

Where the recommended way is:

TimelineView(.everyMinute) { context in
  Text(context.date.description)
}

View builders backport

In SwiftUI patterns evolution: view builders we've covered a new emerging SwiftUI pattern from this year, at the end of the article we've seen how it was relatively easy to create extensions back-porting said pattern to previous versions OSes.

Thanks to this new beta seed, most of these extensions are no longer needed, as SwiftUI backports these new initializers view modifiers all the way to iOS 13 (and equivalent on other platforms).

For example, we went from:

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) // 👈🏻
extension Section where Parent : View, Content : View, Footer : View {
  public init(
    @ViewBuilder content: () -> Content, 
    @ViewBuilder header: () -> Parent, 
    @ViewBuilder footer: () -> Footer
  )
}

To:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) // 👈🏻
extension Section where Parent : View, Content : View, Footer : View {
  public init(
    @ViewBuilder content: () -> Content, 
    @ViewBuilder header: () -> Parent, 
    @ViewBuilder footer: () -> Footer
  )
}

Documentation

We have a lot more documentation for:

Conclusions

Did you find anything else interesting in Xcode 13 beta 3? Please let me know!

As always, please report any issue to Apple: we're still months away from the official release, things can and will be fixed, as long as we report them.

This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's feed RSS or follow @FiveStarsBlog on Twitter to never miss new content!

⭑⭑⭑⭑⭑

Further Reading

Explore SwiftUI

Browse all