PythonIDE Docs
中文
简体中文

导航 API

NavigationStack、NavigationLink、TabView、ToolbarItem。

本页覆盖 NavigationStackNavigationLinkNavigationSplitViewTabViewTabNavigationPathToolbarItemToolbarSpacer。页面级结构优先用这些原生导航容器,不要用手写按钮去模拟系统导航。

#什么时候用

目标首选 API说明
普通推入详情页NavigationStack + NavigationLink列表到详情、设置项到二级页。
代码控制 push/popNavigationStack(path=...) + NavigationPath登录流程、分步表单、点击完成后跳转。
多个主栏目TabView + Tab首页、搜索、我的等顶层导航。
iPad 多列NavigationSplitView侧边栏 + 详情页,可加 supplementary 第三列。
顶栏/底栏命令.toolbar([...]) + ToolbarItem保存、关闭、编辑、分享等页面命令。

#最小正确示例

python
import appui

items = [
    {"id": "a", "title": "Alpha", "status": "Ready"},
    {"id": "b", "title": "Beta", "status": "Draft"},
]


def item_key(item):
    return item["id"]


def detail_view(item):
    return (
        appui.Form([
            appui.Section("详情", [
                appui.LabeledContent("Title", value=item["title"]),
                appui.LabeledContent("Status", value=item["status"]),
            ])
        ])
        .navigation_title(item["title"])
        .toolbar([
            appui.ToolbarItem(
                placement="navigation_bar_trailing",
                content=appui.CloseButton(),
                role="close",
            )
        ])
    )


def row_view(item):
    return appui.NavigationLink(
        destination=detail_view(item),
        label=appui.Label(item["title"], system_image="doc.text"),
    )


def body():
    root = appui.List([
        appui.Section("Items", [
            appui.ForEach(items, row_builder=row_view, key=item_key)
        ])
    ]).navigation_title("Navigation")

    return appui.NavigationStack(root)


appui.run(body)

#导航容器签名

API签名分类
NavigationStackNavigationStack(content: Optional[View] = None, path: Optional[NavigationPath] = None, destinations: Optional[Dict[str, Callable]] = None)navigation
NavigationViewNavigationView = NavigationStack兼容别名
NavigationLinkNavigationLink(title: Optional[str] = None, destination: Optional[View] = None, label: Optional[View] = None)navigation
NavigationSplitViewNavigationSplitView(sidebar: Optional[View] = None, detail: Optional[View] = None, supplementary: Optional[View] = None, column_visibility: str = 'all')navigation
TabViewTabView(tabs: Optional[Sequence["Tab"]] = None, selection: Optional[int] = None, on_change: Optional[Callable] = None, onChange: Optional[Callable] = None)navigation
TabTab(title: str = '', system_image: Optional[str] = None, image: Optional[str] = None, content: Optional[View] = None, badge: Optional[int] = None, tag: Optional[int] = None, systemImage: Optional[str] = None, role: Optional[str] = None, key: Optional[str] = None)navigation
NavigationPathNavigationPath()公开类型
ToolbarItemToolbarItem(placement: str = 'automatic', content: Optional[View] = None, role: Optional[str] = None)presentation
ToolbarSpacerToolbarSpacer(sizing: str = 'fixed', placement: str = 'automatic')presentation
API签名所属类型
appendappend(view_or_value: Union["View", str, int, Dict[str, Any]]) -> NoneNavigationPath
poppop(count: int = 1) -> NoneNavigationPath
pop_to_rootpop_to_root() -> NoneNavigationPath
replacereplace(items: Sequence[Any]) -> NoneNavigationPath
python
import appui

path = appui.NavigationPath()


def open_profile():
    path.append("profile")


def go_root():
    path.pop_to_root()


def make_destination(route):
    if route == "profile":
        return appui.Form([
            appui.Section("Profile", [
                appui.Text("Programmatic destination"),
                appui.Button("Back to root", action=go_root),
            ])
        ]).navigation_title("Profile")
    return appui.Text("Unknown").navigation_title("Unknown")


def body():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("Actions", [
                appui.Button("Open profile", action=open_profile),
            ])
        ]).navigation_title("Root"),
        path=path,
        destinations={"profile": make_destination},
    )


appui.run(body)

#TabView 示例

python
import appui

state = appui.State(tab=0, enabled=True)


def set_tab(value):
    state.tab = value


def set_enabled(value):
    state.enabled = value


def home_view():
    return appui.NavigationStack(
        appui.Text("Home").padding().navigation_title("Home")
    )


def settings_view():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("Settings", [
                appui.Toggle(
                    "Enabled",
                    is_on=state.enabled,
                    on_change=set_enabled,
                ),
            ])
        ]).navigation_title("Settings")
    )


def body():
    return appui.TabView(
        tabs=[
            appui.Tab("Home", system_image="house", content=home_view(), tag=0),
            appui.Tab("Settings", system_image="gear", content=settings_view(), tag=1),
        ],
        selection=state.tab,
        on_change=set_tab,
    )


appui.run(body, state=state)

#底部附件与原生 Sheet

持续任务可以用 .tab_view_bottom_accessory(...) 显示底部常驻状态条,再用 .sheet(...) 打开完整面板。这样底部条仍由 iOS 26 TabView 原生区域承载,展开页则交给系统 sheet 处理圆角、拖拽条、下拉关闭和 detents。

python
import appui

state = appui.State(show_panel=False)


def open_panel():
    state.show_panel = True


def close_panel():
    state.show_panel = False


def compact_bar():
    return appui.HStack([
        appui.Image(system_name="arrow.down.circle.fill").foreground_color("systemBlue"),
        appui.VStack([
            appui.Text("下载中").font("subheadline").bold(),
            appui.Text("42% · 3 个任务").font("caption").foreground_style("secondary"),
        ], alignment="leading", spacing=2)
        .frame(max_width=appui.infinity, alignment="leading"),
        appui.Image(system_name="chevron.up").foreground_color("secondaryLabel"),
    ], spacing=10).padding(horizontal=14, vertical=10)


def expanded_panel():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("任务", [
                appui.LabeledContent("当前进度", value="42%"),
                appui.LabeledContent("剩余任务", value="3"),
                appui.Button("完成", action=close_panel),
            ])
        ]).navigation_title("下载")
    )


def body():
    tabs = appui.TabView(tabs=[
        appui.Tab("首页", system_image="house", content=appui.Text("Home"), tag=0),
        appui.Tab("设置", system_image="gear", content=appui.Text("Settings"), tag=1),
    ])
    return tabs.tab_view_bottom_accessory(
        compact_bar().content_shape("rect").on_tap(open_panel)
    ).sheet(
        is_presented=state.show_panel,
        on_dismiss=close_panel,
        content=expanded_panel,
        detents="medium_large",
        drag_indicator="visible",
    )


appui.run(body, state=state)

紧凑条会放在底部系统区域;点击后打开原生 sheet。不要再额外给同一个 TabView 挂底部 safe_area_bar,否则会和底部附件争同一块区域。需要更像系统媒体 App 的体验时,优先把完整播放器设计成适合 sheet 的内容,而不是自定义全屏 overlay。

#常用修饰符

API签名所属类型
.navigation_title.navigation_title(title: Union[str, "View"]) -> SelfView
.navigation_bar_title_display_mode.navigation_bar_title_display_mode(mode: str) -> SelfView
.navigation_bar_back_button_hidden.navigation_bar_back_button_hidden(value: bool = True) -> SelfView
.toolbar.toolbar(items: Any) -> SelfView
.toolbar_background.toolbar_background(visibility: str = 'visible', bars: str = 'navigation_bar') -> SelfView
.toolbar_color_scheme.toolbar_color_scheme(scheme: str = 'dark', bars: str = 'navigation_bar') -> SelfView
.navigation_destination.navigation_destination(is_presented: bool = False, content: Optional["View"] = None, on_dismiss: Optional[Callable] = None, isPresented: Optional[bool] = None, onDismiss: Optional[Callable] = None) -> SelfView
.safe_area_bar.safe_area_bar(edge: str = 'bottom', content: Optional["View"] = None, alignment: str = 'center', spacing: Optional[float] = None, safeAreaEdge: Optional[str] = None) -> SelfView
.tab_view_bottom_accessory.tab_view_bottom_accessory(content: Optional["View"] = None, enabled: bool = True, is_enabled: Optional[bool] = None, isEnabled: Optional[bool] = None) -> SelfView
.tab_bar_minimize_behavior.tab_bar_minimize_behavior(behavior: str = 'automatic') -> SelfView
.tab_view_search_activation.tab_view_search_activation(activation: str = 'search_tab_selection') -> SelfView

#与相邻 API 的区别

API不同点
NavigationLink vs NavigationPathNavigationLink 适合用户点击某一行进入详情;NavigationPath 适合业务逻辑主动跳转。
TabView vs NavigationStackTabView 是顶层栏目切换;每个 Tab 里面通常再放自己的 NavigationStack
NavigationSplitView vs TabViewNavigationSplitView 是同一任务的多列信息架构;TabView 是多个任务域的顶层切换。
.toolbar vs 页面正文按钮页面级命令放工具栏;内容相关动作放在表单、列表或卡片中。
.tab_view_bottom_accessory vs .sheet前者只显示底部常驻紧凑条;后者打开完整面板、表单或播放器页。
.tab_view_bottom_accessory vs .safe_area_bar前者是 TabView 的系统底部附件;后者是普通安全区插入内容,不要同时挂在同一个底部区域。
CloseButton vs 普通 Button关闭 MiniApp 时使用 CloseButtonToolbarItem(role="close"),系统能识别关闭语义。
ToolbarSpacer vs 空白 SpacerToolbarSpacer 是工具栏项;正文布局留白继续用 Spacer

#常见错误

错误正确做法
只用 VStack 加按钮模拟页面切换。使用 NavigationStackNavigationLinkNavigationPath
TabView 外面只包一个全局 NavigationStack每个 Tab 里面放自己的 NavigationStack,避免标题和返回栈互相污染。
用“播放”或“下载”单独占一个 Tab,但又需要全局状态条。.tab_view_bottom_accessory(...) 放紧凑状态条,点击后用 .sheet(...) 打开完整面板。
隐藏返回按钮但没有替代路径。用 toolbar 放明确的返回、取消或关闭按钮。
ForEach 行没有稳定 keyForEach 传稳定键函数,避免列表刷新后导航状态错位。
ToolbarItem 使用旧 placement 名称。使用 AppUI 文档中的 navigation_bar_trailing

#相关文档

文档用途
导航与页面结构TabView、NavigationStack、NavigationPath 和 sheet 的完整模式。
呈现 APIsheet、alert、popover、confirmation dialog。
形状 APIToolbarItem 也在形状/工具栏页中保留了签名。