UINavigationBar in SwiftUI is the native navbar, that can have a “Back” < navigation link, page title, or action buttons.

UIMenu is a component that lets you open a small native dropdown:

Hotwire Native UIMenu

You can add a UIMenu using Hotwire Native Bridge.

First, add the Bridge component to your Hotwire Native iOS app:

// NavComponent.swift
import HotwireNative
import UIKit

final class NavComponent: BridgeComponent {
    override class var name: String { "nav" }

    override func onReceive(message: Message) {
        guard let viewController else { return }
        addButton(via: message, to: viewController)
    }

    private var viewController: UIViewController? {
        delegate.destination as? UIViewController
    }

    private func addButton(via message: Message, to viewController: UIViewController) {
        guard let data: MessageData = message.data() else { return }

        let items: [UIAction] = data.items.map { item in

            UIAction(title: item.title,
                     image: UIImage(systemName: item.image),
                     attributes: item.destructive ? .destructive : [],
                     state: item.state == "on" ? .on : .off
            ) { (_) in
                self.onItemSelected(item: item)
            }
        }

        // build the menu item
        let image = UIImage(systemName: data.image)
        let menu = UIMenu(title: data.title, children: items)
        let menuItem = UIBarButtonItem(image: image, menu: menu)

        if data.side == "right" {
            viewController.navigationItem.rightBarButtonItem = menuItem
        } else {
            viewController.navigationItem.leftBarButtonItem = menuItem
        }
    }

    private func onItemSelected(item: MenuItem) {
        self.reply(
            to: "connect",
            with: SelectionMessageData(selectedIndex: item.index)
        )
    }
}

private extension NavComponent {
    struct MessageData: Decodable {
        let items: [MenuItem]
        let image: String
        let side: String
        let title: String
    }
    struct MenuItem: Decodable {
        let title: String
        let image: String
        let destructive: Bool
        let state: String
        let index: Int
    }
    struct SelectionMessageData: Encodable {
        let selectedIndex: Int
    }
}

Add a Stimulus controller in your Web app:

// app/javascript/controllers/bridge/nav_controller.js
import { BridgeComponent, BridgeElement } from "@hotwired/hotwire-native-bridge"

export default class extends BridgeComponent {
  static component = "nav"
  static targets = ["item"]

  connect() {
    super.connect()

    const items = this.itemTargets.map((item, index) => {
      const itemElement = new BridgeElement(item)

      return {
        title: itemElement.title,
        image: itemElement.bridgeAttribute("image") ?? "none",
        destructive: item.dataset.turboMethod === "delete",
        state: itemElement.bridgeAttribute("state") ?? "off",
        index
      }
    })

    const element = this.bridgeElement
    const title = element.bridgeAttribute("title") ?? ""
    const side = element.bridgeAttribute("side") || "left"
    const image = element.bridgeAttribute("image") || "none"

    this.send("connect", { items, title, image, side }, (message) => {
      const selectedIndex = message.data.selectedIndex
      const selectedItem = new BridgeElement(this.itemTargets[selectedIndex]);

      selectedItem.click()
    })
  }
}

Finally, define links and icons that should appear in UIMenu

<%= tag.div data: { controller: 'bridge--nav', bridge_side: 'right', bridge_image: 'person.circle' } do %>
  <%= link_to 'Profile', edit_user_registration_path, data: { bridge__nav_target: 'item', bridge_image: 'person.circle' } %>
  <%= button_to 'Sign Out', destroy_user_session_path, method: :delete, data: { bridge__nav_target: 'item', bridge_image: 'return', turbo_method: :delete, turbo: true } %>
<% end %>

🚨 I tried using link_to data: { turbo_method: :delete, turbo_confirm: "Sure?" }, but it submitted even on clicking Cancel in the confirmation modal! This problem did not reoccur with button_to.

Notice that bridge_side can be right or left.

You can lookup icons to use in SF Symbols app.

Credit to beanman for coming up with this solution.

John Pollard also talked about this component in his RailsConf talk.