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.
Есть несколько вещей, которые вам нужно сделать:
В других настройках 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
Импортировать
TipKit
, то в вашемApp
'sbody
добавитьtask
для настройки Советы:
var body: some Scene {
WindowGroup {
ContentView()
.task {
try? await Tips.configure()
}
}
}
- Создать
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)]
}
}
- Добавьте его в
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 .