TipKit: Как интегрировать Tip со SwiftUI или UIKit?

Теперь, когда TipKit выпущен Apple и должен работать с Xcode 15 beta 5, я не знаю, как интегрироватьTipс видом?

У меня есть следующий код:

      import SwiftUI

struct TipKitTestView: View {
    var body: some View {
        VStack {
            Text("Some filler text")
            UselessTip()
        }
    }
}

struct UselessTip: Tip {
    var title: Text {
        Text("Useless title")
    }
    
    var message: Text {
        Text("Some useless message that is a bit longer than the title.")
    }
}

Компилятору не нравится, что яUselessTip()внутриTipKitTestView, выдавая ошибку:Static method 'buildExpression' requires that 'UselessTip' conform to 'View'. Как я могу заставить код скомпилироваться? Я не знаю, как сделать «Совет для просмотра», если в этом есть какой-то смысл.

Кстати, какой код заставит Совет работать в UIKit? Я пытаюсь добавить советы в свой проект с помощью комбинации кода SwiftUI и UIKit, поэтому не знаю, как интегрировать советы в проект с преимущественно кодом UIKit. Кто-нибудь знает как это сделать?

2 ответа

Хотя TipKit преимущественно написан на SwiftUI, Apple предоставила реализации UIKit и AppKit.

Чтобы реализовать подсказку в UIKit, вы можете сделать что-то вроде этого:

      struct SearchTip: Tip {
    var title: Text {
        Text("Add a new game")
    }
    
    var message: Text? {
        Text("Search for new games to play via IGDB.")
    }
    
    var asset: Image? {
        Image(systemName: "magnifyingglass")
    }
}

class ExampleViewController: UIViewController {
    var searchButton: UIButton
    var searchTip = SearchTip()

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        Task { @MainActor in
            for await shouldDisplay in searchTip.shouldDisplayUpdates {
                if shouldDisplay {
                    let controller = TipUIPopoverViewController(searchTip, sourceItem: searchButton)
                    present(controller)
                } else if presentedViewController is TipUIPopoverViewController {
                    dismiss(animated: true)
                }
            }
        }
    }
}

Дополнительную документацию по реализации UIKit можно получить от Apple черезTipUIView,TipUIPopoverViewController, иTipUICollectionViewCell. Я также написал статью, в которой рассказывается, как интегрировать TipKit со SwiftUI или UIKit.

Есть несколько вещей, которые вам нужно сделать:

  1. В других настройках Swift в настройках сборки добавьте-external-plugin-path $(SYSTEM_DEVELOPER_DIR)/Platforms/iPhoneOS.platform/Developer/usr/lib/swift/host/plugins#$(SYSTEM_DEVELOPER_DIR)/Platforms/iPhoneOS.platform/Developer/usr/bin/swift-plugin-server

  2. ИмпортироватьTipKit, то в вашемApp'sbodyдобавитьtaskдля настройки Советы:

      var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    try? await Tips.configure()
                }
        }
    }
  1. СоздатьTip:
      public struct PickNumbersTip: Tip {
    
    @Parameter
    static var hasGeneratedNumbers: Bool = false
    
    public var id: String {
        return "tip.identifier.pick-numbers"
    }
    
    public var title: Text {
        return Text("tip.title.pick-numbers", comment: "Pick Numbers Tip Title")
    }
    
    public var message: Text? {
        return Text("tip.message.pick.numbers", comment: "Pick Numbers Tip Message")
    }
    
    public var asset: Image? {
        return Image(systemName: "hand.tap")
    }
    
    public var actions: [Action] {
        [
            Action(
                id: "action.title.dismiss",
                title: String(localized: "action.title.dismiss", comment: "Dismiss")
            ),
            Action(
                id: "action.title.try-now",
                title: String(localized: "action.title.try-now", comment: "Try Now")
            )
        ]
    }
    
    public var rules: [Rule] {
        #Rule(Self.$hasGeneratedNumbers) { $0 == false } // User has never generated numbers, which makes this tip eligible for display.
    }
    
    public var options: [TipOption] {
        [Tips.MaxDisplayCount(1)]
    }
    
}
  1. Добавьте его вView:
      struct ContentView: View {
    
    @State private var viewModel = ContentViewModel()
    
    private var pickNumbersTip = PickNumbersTip()
    private var generatedNumbersTip = GeneratedNumbersTip()
    
    var body: some View {
        VStack {
            
            HStack {
                ForEach(0..<viewModel.latestNumbers.count, id: \.self) { i in
                    BallView(number: viewModel.latestNumbers[i])
                }
            }
            .popoverTip(generatedNumbersTip, arrowEdge: .top) { action in
                if action.id == "action.title.dismiss" {
                    generatedNumbersTip.invalidate(reason: .userClosedTip)
                }
                if action.id == "action.title.find-out-more" {
                    generatedNumbersTip.invalidate(reason: .userPerformedAction)
                    UIApplication.shared.open(URL(string: "https://developer.apple.com/documentation/gameplaykit/gkrandomdistribution")!)
                }
            }
            
            Spacer()
            Button(action: {
                viewModel.latestNumbers = LottoGenerator.new()
                PickNumbersTip.hasGeneratedNumbers = true
                GeneratedNumbersTip.hasGeneratedNumbers = true
                GeneratedNumbersTip.countOfGeneratedNumbers.donate()
            }, label: {
                Text("button.title.pick-numbers", comment: "Pick Numbers")
            })
            .buttonStyle(.borderedProminent)
            .popoverTip(pickNumbersTip, arrowEdge: .bottom) { action in
                if action.id == "action.title.dismiss" {
                    pickNumbersTip.invalidate(reason: .userClosedTip)
                }
                if action.id == "action.title.try-now" {
                    pickNumbersTip.invalidate(reason: .userPerformedAction)
                    PickNumbersTip.hasGeneratedNumbers = true
                    viewModel.latestNumbers = LottoGenerator.new()
                    GeneratedNumbersTip.hasGeneratedNumbers = true
                    GeneratedNumbersTip.countOfGeneratedNumbers.donate()
                }
            }
        }
        .padding()
        .task {
            for await status in pickNumbersTip.shouldDisplayUpdates {
                print("Pick Numbers Tip Display Eligibility: \(status)")
            }
        }
        .task {
            for await status in generatedNumbersTip.shouldDisplayUpdates {
                print("Generated Numbers Tip Display Eligibility: \(status)")
            }
        }
    }
    
    private struct BallView: View {
        
        var number: Int
        
        var body: some View {
            ZStack {
                Circle()
                    .foregroundStyle(.red)
                
                Text(verbatim: "\(number)")
                    .bold()
                    .fontWidth(.condensed)
                    .foregroundStyle(.white)
            }
        }
    }
    
}

Рабочий пример приложения доступен здесь: https://github.com/stuartbreckenridge/TipKitSample .

Другие вопросы по тегам