Using TipKit in iOS16 and iOS17 codebase

371 views Asked by At

I'm trying to utilize Apple's new TipKit where I still support iOS16. Is there a way to initialize the Tip without making the entire struct as @available(iOS 17, *). Example being

struct MainHomeEntryView: View {
   var myTip = MyTip()
      ....
}

error: 'SubmitTip' is only available in iOS 17 or newer

1

There are 1 answers

5
Frank Rupprecht On

We had the same problem and came up with this small helper:

import SwiftUI
import TipKit


/// Small helper protocol for a tip that is available below iOS 17
/// so that we can pass tips around and have members storing tips,
/// as stored properties can't be marked with `@available`.
@available(iOS, obsoleted: 17, message: "Can be removed once we only support iOS 17+")
public protocol TipShim {

    @available(iOS 17, *)
    var tip: AnyTip { get }

}

@available(iOS 17, *)
public extension Tip where Self: TipShim {

    var tip: AnyTip { AnyTip(self) }

}


public extension View {

    /// Helper for making the `popupTip` modifier available for iOS <17.
    @available(iOS, obsoleted: 17, message: "Can be removed once we only support iOS 17+")
    @ViewBuilder func popupTipShim(_ tipShim: TipShim?, arrowEdge: Edge = .top) -> some View {
        if #available(iOS 17, *), let tip = tipShim?.tip {
            self.popoverTip(tip, arrowEdge: arrowEdge)
        } else {
            self
        }
    }

}

With it, you only need a minimal amount of if #available checks. You only have to let your tip conform to TipShim:

struct MyTip: Tip, TipShim {
    // no additional implementation required, 
    // as this was done in the extension above
}

and then you can use it like this:

struct MainHomeEntryView: View {
    var myTip: TipShim?
   
    init() {
        if #available(iOS 17, *) {
            self.myTip = MyTip()
        }
    }

    var body: some View {
        MyView()
            .popupTipShim(self.myTip)
    }
   
}