lynnswap/uihostingmenu
UIHostingMenu is a Swift package that builds UIKit UIMenu instances from SwiftUI menu content.
Usage
import Observation
import SwiftUI
import UIKit
import UIHostingMenu
@Observable
final class EditorMenuState {
var canReload = true
var selectedFormat = "JSON"
func reload() {
canReload = false
}
}
struct EditorMenuItems: View {
var state: EditorMenuState
var body: some View {
Button("Reload") {
state.reload()
}
.disabled(!state.canReload)
Divider()
Menu("Format: \(state.selectedFormat)") {
Button("JSON") { state.selectedFormat = "JSON" }
Button("HTML") { state.selectedFormat = "HTML" }
}
}
}
let state = EditorMenuState()
let hostingMenu = UIHostingMenu(rootView: EditorMenuItems(state: state))
button.menu = try hostingMenu.menu()
button.showsMenuAsPrimaryAction = true
state.canReload = falseDeclare menu content as SwiftUI that directly reads an @Observable source of truth. Do not rebuild or reassign the menu when the same source object changes; the visible menu follows SwiftUI/Observation reads.
Static menus work the same way:
button.menu = try UIHostingMenu(menuItems: {
Button("Refresh") {}
Divider()
Menu("More") {
Button("Share") {}
Button("Delete", role: .destructive) {}
}
}).menu()Migration
v0.2.0
These notes apply when upgrading from v0.1.x or earlier to v0.2.0.
requestUpdate(after:)andsetNeedsUpdate()have been removed. Menu updates are driven by SwiftUI reading@Observablesource-of-truth objects.- Move mutable menu inputs into an
@Observableobject and read that object from the SwiftUI menu view. - Do not rebuild or reassign the menu when properties on the same source object change.
- Use
updateRootView(_:)only when replacing the SwiftUI root view or switching to a different source object. - If an update should be delayed, schedule the model mutation itself and let SwiftUI/Observation deliver the menu update.
License
MIT. See LICENSE.
Package Metadata
Repository: lynnswap/uihostingmenu
Default branch: main
README: README.md