使用 SwiftUI 设置 TabBar Item 徽章计数
Set TabBar Item badge count with SwiftUI
是否可以使用 SwiftUI 显示 TabItem 徽章?
使用 UIKit 很容易实现,就像这里描述的那样 ->
How to set badge value in Tab bar?
我没有找到使用 SwiftUI 执行此操作的方法。
唯一可能的方法是使用场景 rootViewController 访问 UITabBarController 并直接修改其标签栏项目。
func setBadgeCount(_ count: Int) {
UIApplication.shared.applicationIconBadgeNumber = count
guard let delegate = app.connectedScenes.first?.delegate as? SceneDelegate else {
return
}
if let tabBarController = delegate.window?.rootViewController?.children.first {
tabBarController.viewControllers?.first?.tabBarItem.badgeValue = "\(count)"
}
}
关于如何使用本机 SwiftUI 方法执行此操作的任何想法?
目前,SwiftUI 没有徽章功能,所以我们必须自定义。
参考HERE我用徽章创建我的tabar
struct ContentView: View {
private var badgePosition: CGFloat = 2
private var tabsCount: CGFloat = 2
@State var selectedView = 0
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
TabView {
Text("First View")
.tabItem {
Image(systemName: "list.dash")
Text("First")
}.tag(0)
Text("Second View")
.tabItem {
Image(systemName: "star")
Text("Second")
}.tag(1)
}
ZStack {
Circle()
.foregroundColor(.red)
Text("3")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
.frame(width: 15, height: 15)
.offset(x: ( ( 2 * self.badgePosition) - 0.95 ) * ( geometry.size.width / ( 2 * self.tabsCount ) ) + 2, y: -30)
.opacity(1.0)
}
}
}
}
func calculateBadgeXPos(width: CGFloat) -> CGFloat {
let t = (2*CGFloat(self.selectedTab))+1
return CGFloat(t * width/(2*CGFloat(TABS_COUNT)))
}
然后在这里使用它:
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
// make sure to update TABS_COUNT
TabView(selection: self.$selectedTab) {
...
}
NotificationBadge(...)
.offset(x: self.calculateBadgeXPos(width: geometry.size.width), y: -28)
}
}
在 Preview
上看起来像这样
struct ContentView: View {
var body: some View {
TabView {
Text("Home")
.tabItem {
Text("Home")
}
Text("Home")
.tabItem {
Text("Home")
}
Text("Home")
.tabItem {
Text("Home")
}
}
.introspectTabBarController { (tabbarController) in
if let items = tabbarController.tabBar.items {
let tabItem = items[2]
tabItem.badgeValue = "1"
}
}
}
}
现在在 SwiftUI 3 中,他们添加了一个 .badge()
修饰符
TabView {
Text("Your home screen here")
.tabItem {
Label("Home", systemImage: "house")
}
.badge(5)
}
上面提到的 .introspectTabBarController 修饰符对我有用。
在 tabItem 上提供一个漂亮的原生徽章。两个方向看起来都很棒。
.introspectTabBarController { (UITabBarController) in
self.tabBarControl = UITabBarController
if let items = UITabBarController.tabBar.items {
let tabItem = items[2] // in my case it was 3rd item
tabItem.badgeValue = "5" // hardcoded
}
}
不过,当更改标签时,徽章消失所以我将 UITabBarController 保存在@State 中,当标签更改时我再次设置 tabItem.badgeValue。
P.S: iOS 15+ 支持 .badge 修饰符。如果您的目标高于或 iOS 15.
,请使用它
iOS 15 添加了对 .badge
修饰符的支持,但由于我需要支持 iOS 14,因此我创建了 UITabBarController
包装器:
struct TabBarController<TabContent: View, Tab: Hashable>: UIViewControllerRepresentable {
let tabs: [Tab]
@Binding
var selection: Tab
let tabBarItem: (Tab) -> UITabBarItem
let badgeValue: (Tab) -> String?
@ViewBuilder
let contentView: (Tab) -> TabContent
func makeUIViewController(context: Context) -> UITabBarController {
let controller = UITabBarController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {
context.coordinator.viewControllers
.keys
.filterNot(tabs.contains(_:))
.forEach { removedKey in
context.coordinator.viewControllers.removeValue(forKey: removedKey)
}
uiViewController.viewControllers = tabs.map { tab in
let rootView = contentView(tab)
let viewController = context.coordinator.viewControllers[tab] ?? {
let viewController = UIHostingController(rootView: rootView)
viewController.tabBarItem = tabBarItem(tab)
context.coordinator.viewControllers[tab] = viewController
return viewController
}()
viewController.rootView = rootView
viewController.tabBarItem.badgeValue = badgeValue(tab)
return viewController
}
uiViewController.selectedIndex = tabs.firstIndex(of: selection) ?? 0
}
func makeCoordinator() -> Coordinator {
Coordinator(selection: $selection)
}
final class Coordinator: NSObject, UITabBarControllerDelegate {
@Binding
private var selection: Tab
var viewControllers = [Tab: UIHostingController<TabContent>]()
init(selection: Binding<Tab>) {
_selection = selection
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let newTab = viewControllers.first(where: { [=10=].value == viewController })?.key else {
print("tabBarController:didSelect: unexpected")
return
}
selection = newTab
}
}
}
用法:
TabBarController(
tabs: [1,2,3],
selection: $selection,
tabBarItem: { tab in
UITabBarItem(title: "tab \(tab)", image: UIImage(systemName: "1.square.fill"), tag: 0)
},
badgeValue: { tab in
"\(tab)"
},
contentView: { tab in
Text("\(tab) screen")
}
).ignoresSafeArea()
是否可以使用 SwiftUI 显示 TabItem 徽章?
使用 UIKit 很容易实现,就像这里描述的那样 ->
How to set badge value in Tab bar?
我没有找到使用 SwiftUI 执行此操作的方法。 唯一可能的方法是使用场景 rootViewController 访问 UITabBarController 并直接修改其标签栏项目。
func setBadgeCount(_ count: Int) {
UIApplication.shared.applicationIconBadgeNumber = count
guard let delegate = app.connectedScenes.first?.delegate as? SceneDelegate else {
return
}
if let tabBarController = delegate.window?.rootViewController?.children.first {
tabBarController.viewControllers?.first?.tabBarItem.badgeValue = "\(count)"
}
}
关于如何使用本机 SwiftUI 方法执行此操作的任何想法?
目前,SwiftUI 没有徽章功能,所以我们必须自定义。
参考HERE我用徽章创建我的tabar
struct ContentView: View {
private var badgePosition: CGFloat = 2
private var tabsCount: CGFloat = 2
@State var selectedView = 0
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
TabView {
Text("First View")
.tabItem {
Image(systemName: "list.dash")
Text("First")
}.tag(0)
Text("Second View")
.tabItem {
Image(systemName: "star")
Text("Second")
}.tag(1)
}
ZStack {
Circle()
.foregroundColor(.red)
Text("3")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
.frame(width: 15, height: 15)
.offset(x: ( ( 2 * self.badgePosition) - 0.95 ) * ( geometry.size.width / ( 2 * self.tabsCount ) ) + 2, y: -30)
.opacity(1.0)
}
}
}
}
func calculateBadgeXPos(width: CGFloat) -> CGFloat {
let t = (2*CGFloat(self.selectedTab))+1
return CGFloat(t * width/(2*CGFloat(TABS_COUNT)))
}
然后在这里使用它:
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
// make sure to update TABS_COUNT
TabView(selection: self.$selectedTab) {
...
}
NotificationBadge(...)
.offset(x: self.calculateBadgeXPos(width: geometry.size.width), y: -28)
}
}
在 Preview
struct ContentView: View {
var body: some View {
TabView {
Text("Home")
.tabItem {
Text("Home")
}
Text("Home")
.tabItem {
Text("Home")
}
Text("Home")
.tabItem {
Text("Home")
}
}
.introspectTabBarController { (tabbarController) in
if let items = tabbarController.tabBar.items {
let tabItem = items[2]
tabItem.badgeValue = "1"
}
}
}
}
现在在 SwiftUI 3 中,他们添加了一个 .badge()
修饰符
TabView {
Text("Your home screen here")
.tabItem {
Label("Home", systemImage: "house")
}
.badge(5)
}
上面提到的 .introspectTabBarController 修饰符对我有用。 在 tabItem 上提供一个漂亮的原生徽章。两个方向看起来都很棒。
.introspectTabBarController { (UITabBarController) in
self.tabBarControl = UITabBarController
if let items = UITabBarController.tabBar.items {
let tabItem = items[2] // in my case it was 3rd item
tabItem.badgeValue = "5" // hardcoded
}
}
不过,当更改标签时,徽章消失所以我将 UITabBarController 保存在@State 中,当标签更改时我再次设置 tabItem.badgeValue。
P.S: iOS 15+ 支持 .badge 修饰符。如果您的目标高于或 iOS 15.
,请使用它iOS 15 添加了对 .badge
修饰符的支持,但由于我需要支持 iOS 14,因此我创建了 UITabBarController
包装器:
struct TabBarController<TabContent: View, Tab: Hashable>: UIViewControllerRepresentable {
let tabs: [Tab]
@Binding
var selection: Tab
let tabBarItem: (Tab) -> UITabBarItem
let badgeValue: (Tab) -> String?
@ViewBuilder
let contentView: (Tab) -> TabContent
func makeUIViewController(context: Context) -> UITabBarController {
let controller = UITabBarController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {
context.coordinator.viewControllers
.keys
.filterNot(tabs.contains(_:))
.forEach { removedKey in
context.coordinator.viewControllers.removeValue(forKey: removedKey)
}
uiViewController.viewControllers = tabs.map { tab in
let rootView = contentView(tab)
let viewController = context.coordinator.viewControllers[tab] ?? {
let viewController = UIHostingController(rootView: rootView)
viewController.tabBarItem = tabBarItem(tab)
context.coordinator.viewControllers[tab] = viewController
return viewController
}()
viewController.rootView = rootView
viewController.tabBarItem.badgeValue = badgeValue(tab)
return viewController
}
uiViewController.selectedIndex = tabs.firstIndex(of: selection) ?? 0
}
func makeCoordinator() -> Coordinator {
Coordinator(selection: $selection)
}
final class Coordinator: NSObject, UITabBarControllerDelegate {
@Binding
private var selection: Tab
var viewControllers = [Tab: UIHostingController<TabContent>]()
init(selection: Binding<Tab>) {
_selection = selection
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let newTab = viewControllers.first(where: { [=10=].value == viewController })?.key else {
print("tabBarController:didSelect: unexpected")
return
}
selection = newTab
}
}
}
用法:
TabBarController(
tabs: [1,2,3],
selection: $selection,
tabBarItem: { tab in
UITabBarItem(title: "tab \(tab)", image: UIImage(systemName: "1.square.fill"), tag: 0)
},
badgeValue: { tab in
"\(tab)"
},
contentView: { tab in
Text("\(tab) screen")
}
).ignoresSafeArea()