PythonIDE Docs
中文
简体中文

导航与页面结构

TabView、NavigationStack、NavigationLink 和 toolbar 的组合方式。

导航的目标是让页面关系清楚:栈式跳转用 NavigationStack,多 tab 用 TabView,iPad 双栏用 NavigationSplitView,临时任务用 sheet / alert / popover。

#预期效果

示例会展示固定跳转、数据驱动导航、Tab、sheet 和 alert 的页面层级。

#选择表

需求API
页面逐层进入、支持返回NavigationStack
固定列表进入详情NavigationLink
通过数据驱动跳转NavigationPath + destinations
底部标签页TabView / Tab
iPad / Mac 侧栏 + 详情NavigationSplitView
临时编辑页.sheet(...)
强制流程.full_screen_cover(...)
确认、错误、危险操作.alert(...) / .confirmation_dialog(...)
小浮层.popover(...)

最小写法是给根内容包一层 NavigationStack,再在子页面里设置标题。

python
import appui

state = appui.State(last_opened="None")


def mark_opened(title):
    state.last_opened = title


def settings_page():
    def open_settings():
        mark_opened("设置")

    return appui.Form([
        appui.Section("设置", [
            appui.Text("这里是详情内容"),
            appui.Button("记录打开", action=open_settings),
        ]),
        appui.Section("状态", [
            appui.LabeledContent("Last opened", value=state.last_opened),
        ]),
    ]).navigation_title("设置")


def about_page():
    return appui.Text("实时快路径 appui").padding().navigation_title("关于")


def body():
    return appui.NavigationStack(
        appui.List([
            appui.NavigationLink(title="设置", destination=settings_page()),
            appui.NavigationLink(title="关于", destination=about_page()),
        ]).navigation_title("首页")
    )


appui.run(body, state=state)

固定页面跳转用 NavigationLink;动态路由用 NavigationPath

NavigationPath 适合“从按钮打开指定页面”“根据 tag 和 data 打开详情”等数据驱动场景。

python
import appui

path = appui.NavigationPath()
state = appui.State(opened="None")


def open_settings():
    path.append({"tag": "settings", "data": None})


def open_user():
    path.append({"tag": "user", "data": 42})


def go_back():
    path.pop()


def go_home():
    path.pop_to_root()


def settings_destination(data):
    return appui.VStack([
        appui.Text("设置").font("title2"),
        appui.Button("返回", action=go_back),
    ], spacing=12).padding().navigation_title("设置")


def user_destination(user_id):
    return appui.VStack([
        appui.Text(f"用户 {user_id}").font("title2"),
        appui.Button("返回首页", action=go_home),
    ], spacing=12).padding().navigation_title("用户")


def body():
    return appui.NavigationStack(
        content=appui.VStack([
            appui.Text("首页").font("title2"),
            appui.Button("打开设置", action=open_settings).button_style("bordered"),
            appui.Button("打开用户 42", action=open_user).button_style("bordered_prominent"),
        ], spacing=16).padding().navigation_title("示例"),
        path=path,
        destinations={
            "settings": settings_destination,
            "user": user_destination,
        },
    )


appui.run(body, state=state)

path.append({"tag": "...", "data": ...})tag 用来找目标构建函数,data 会传给目标函数。

#TabView

Tab 适合并列的顶层区域,不要用来承载一次性流程。每个 tab 中如果还需要进入详情,再单独放自己的 NavigationStack

python
import appui

state = appui.State(selection=0)


def set_selection(value):
    state.selection = value


def home_view():
    return appui.NavigationStack(
        appui.List([
            appui.Section("Home", [
                appui.Text("Home content")
            ])
        ]).navigation_title("首页")
    )


def settings_view():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("Settings", [
                appui.LabeledContent("Selected tab", value=str(state.selection))
            ])
        ]).navigation_title("设置")
    )


def body():
    return appui.TabView(
        selection=state.selection,
        on_change=set_selection,
        tabs=[
            appui.Tab("首页", system_image="house", tag=0, content=home_view()),
            appui.Tab("设置", system_image="gearshape", tag=1, content=settings_view()),
        ],
    )


appui.run(body, state=state)

#Sheet、Alert、ConfirmationDialog

弹层用状态控制显示和关闭。复杂 sheet 内容抽成命名函数。

python
import appui

state = appui.State(show_sheet=False, show_alert=False, name="")


def set_name(value):
    state.name = value


def open_sheet():
    state.show_sheet = True


def close_sheet():
    state.show_sheet = False


def open_alert():
    state.show_alert = True


def close_alert():
    state.show_alert = False


def editor_sheet():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("编辑", [
                appui.TextField("名称", text=state.name, on_change=set_name),
                appui.Button("完成", action=close_sheet).button_style("bordered_prominent"),
            ])
        ]).navigation_title("编辑")
    )


def body():
    root = (
        appui.VStack([
            appui.Button("编辑", action=open_sheet).button_style("bordered"),
            appui.Button("删除", action=open_alert)
                .foreground_color("systemRed"),
        ], spacing=16)
        .padding()
        .sheet(is_presented=state.show_sheet, on_dismiss=close_sheet, content=editor_sheet)
        .alert(
            "确认删除?",
            is_presented=state.show_alert,
            on_dismiss=close_alert,
            actions=[
                appui.Button("取消", role="cancel", action=close_alert),
                appui.Button("删除", role="destructive", action=close_alert),
            ],
            message="删除后无法恢复。",
        )
    )
    return appui.NavigationStack(root.navigation_title("呈现"))


appui.run(body, state=state)

普通编辑用 sheet,破坏性操作先 alert 或 confirmation dialog。

NavigationSplitView 适合大屏。窄屏上系统会自动退化为栈式体验。

python
appui.NavigationSplitView(
    sidebar=appui.List([...]).navigation_title("项目"),
    detail=appui.Text("选择一个项目").navigation_title("详情"),
)

如果目标设备主要是 iPhone,不要为了“看起来高级”强行使用 SplitView。

#常见问题

问题处理
页面没有标题在内容 View 上加 .navigation_title(...)
返回后状态丢失把状态放在外层 State,不要在目标页面函数里重新创建。
destinations 回调报参数错回调写成接收 data 的命名函数。
sheet 点了没反应确认 is_presented 读取的是状态字段,关闭时会把字段改回 False
Tab 和导航栈混乱Tab 做顶层分区,Stack 做分区内跳转。
工具栏图标按钮报错Button 的图标放在 content=Image(...)content=Label(...)

#下一步