I'm new to swiftUI. We have both UIKit and swiftUI code in our project and we have some custom logic to add few UIBarButtonItem. I need to pass viewController.navigationItem.rightBarButtonItems array from UIKit code to a separate swiftUI module and then create toolBar with these rightBarButtonItems.
struct ToolBarItems: ToolbarContent {
let barButtons: [UIBarButtonItem]
init(barButtons: [UIBarButtonItem]) {
self.barButtons = barButtons
}
var body: some ToolbarContent {
ToolbarItem(placement: .principal) {
Text("principal Title")
}
}
@ToolbarContentBuilder
var toolbarButtons: some ToolbarContent {
ForEach(barButtons) { item in
ToolbarItem(placement: .navigationBarTrailing) {
Button(item.title!) {
}
}
}
}
}
This gives errors "No exact matches in reference to static method 'buildExpression'". Not sure how to do this conversion. Please advise.
The error is because ForEach
is not ToolbarContent
. You can create a ToolbarItemGroup
instead, which takes a @ViewBuilder
, and you can put the ForEach
in the @ViewBuilder
:
struct UIKitBarButtons: ToolbarContent {
...
var body: some ToolbarContent {
ToolbarItemGroup(placement: .topBarTrailing) {
ForEach(barButtons) { item in
Button(item.title!) { ... }
}
}
}
}
Also, you would need extension UIBarButtonItem: Identifiable {}
for the ForEach
to work.
However, item.title
is main actor-isolated, but ToolbarContent.body
is not. While you can isolate ToolbarContent.body
to @MainActor
, you would end up implementing a non-isolated protocol requirement with an actor-isolated property, which is potentially not safe.
I would create my own struct to represent a "navigation bar item". For example, if you are only interested in the title, image, and action of the UIBarButtonItem
, I would create a struct like this:
struct NavBarButton: Identifiable {
let id: ObjectIdentifier
let title: LocalizedStringKey?
let image: UIImage?
let action: () -> Void
@MainActor
init(_ barButtonItem: UIBarButtonItem) {
id = ObjectIdentifier(barButtonItem)
title = barButtonItem.title.map(LocalizedStringKey.init(_:))
image = barButtonItem.image
self.action = {
guard let selector = barButtonItem.action else {
return
}
UIApplication.shared.sendAction(selector, to: barButtonItem.target, from: nil, for: nil)
}
}
}
You can use this in a custom ToolbarContent
like this:
struct UIKitBarButtons: ToolbarContent {
let barButtons: [NavBarButton]
var body: some ToolbarContent {
ToolbarItemGroup(placement: .topBarTrailing) {
ForEach(barButtons) { item in
Button {
item.action()
} label: {
if let title = item.title {
Text(title)
}
if let image = item.image {
Image(uiImage: image)
}
}
}
}
}
}
In the body
of a View
, you can then do:
.toolbar {
UIKitBarButtons(barButtons: uiBarButtonItems.map(NavBarButton.init))
}
NavBarButton.init
is main actor-isolated, but so is View.body
, so this is okay.