呈现 API
alert、sheet、popover、confirmation dialog 和刷新动作。
本页覆盖弹窗、模态、确认框、菜单、刷新和滑动操作。页面跳转用 导航 API,控件样式和布局修饰符用 修饰符 API。
#什么时候用
| 目标 | 首选 API | 说明 |
|---|---|---|
| 简短提示 | .alert(...) | 单条消息、确认结果、错误提示。 |
| 危险操作确认 | .confirmation_dialog(...) | 删除、退出、清空等需要用户确认的动作。 |
| 局部任务流 | .sheet(...) | 选择器、编辑表单、短流程。 |
| 全屏任务 | .full_screen_cover(...) | 登录、拍摄、沉浸式流程。 |
| iPad 气泡层 | .popover(...) | 从某个按钮展开的轻量内容。 |
| 长按菜单 | .context_menu(...) | 行内次要操作。 |
| 下拉刷新 | .refreshable(...) 或 Refreshable | 列表重新加载数据。 |
| 行滑动操作 | .swipe_actions(...) 或 SwipeActions | 删除、归档、置顶等列表行操作。 |
#最小正确示例
已复制
import appui
state = appui.State(
show_sheet=False,
show_alert=False,
show_confirm=False,
count=0,
)
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 open_confirm():
state.show_confirm = True
def close_confirm():
state.show_confirm = False
def add_count():
state.count += 1
def reset_count():
state.count = 0
state.show_confirm = False
def sheet_content():
return appui.NavigationStack(
appui.Form([
appui.Section("Sheet", [
appui.Text(f"Count: {state.count}"),
appui.Button("+1", action=add_count),
appui.Button("Close", action=close_sheet),
])
]).navigation_title("Sheet")
)
def body():
root = (
appui.Form([
appui.Section("Actions", [
appui.Button("Open sheet", action=open_sheet),
appui.Button("Show alert", action=open_alert),
appui.Button("Reset count", action=open_confirm)
.foreground_color("systemRed"),
])
])
.navigation_title("Presentation")
.sheet(
is_presented=state.show_sheet,
content=sheet_content,
on_dismiss=close_sheet,
detents="medium_large",
drag_indicator="visible",
)
.alert(
"Notice",
message="Alert is visible.",
is_presented=state.show_alert,
on_dismiss=close_alert,
)
.confirmation_dialog(
"Reset count?",
is_presented=state.show_confirm,
message="This cannot be undone.",
actions=[
appui.Button("Reset", action=reset_count, role="destructive"),
appui.Button("Cancel", action=close_confirm),
],
on_dismiss=close_confirm,
)
)
return appui.NavigationStack(root)
appui.run(body, state=state)
#呈现组件
这些组件可以作为视图使用;大多数页面更常用对应的链式修饰符。
| API | 签名 | 分类 |
|---|---|---|
Alert | Alert(title: str = '', message: Optional[str] = None, is_presented: bool = False, actions: Optional[Sequence[View]] = None, isPresented: Optional[bool] = None) | presentation |
ConfirmationDialog | ConfirmationDialog(title: str = '', message: Optional[str] = None, is_presented: bool = False, actions: Optional[Sequence[View]] = None, isPresented: Optional[bool] = None) | presentation |
Popover | Popover(is_presented: bool = False, content: Optional[View] = None, trigger: Optional[View] = None, isPresented: Optional[bool] = None) | presentation |
Refreshable | Refreshable(on_refresh: Optional[Callable] = None, onRefresh: Optional[Callable] = None, content: Optional[ViewChild] = None) | collection |
SwipeActions | SwipeActions(content: Optional[View] = None, leading: Optional[Sequence[View]] = None, trailing: Optional[Sequence[View]] = None) | collection |
#呈现修饰符
| API | 签名 | 所属类型 |
|---|---|---|
.alert | .alert(title: str, message: str = '', is_presented: bool = False, on_dismiss: Optional[Callable] = None, actions: Optional[Sequence["View"]] = None, isPresented: Optional[bool] = None, onDismiss: Optional[Callable] = None) -> Self | View |
.sheet | .sheet(is_presented: bool = False, content: Optional[Union["View", Callable[[], "View"]]] = None, on_dismiss: Optional[Callable] = None, detents: Optional[str] = None, drag_indicator: Optional[str] = None, interactive_dismiss_disabled: bool = False, isPresented: Optional[bool] = None, onDismiss: Optional[Callable] = None, dragIndicator: Optional[str] = None, interactiveDismissDisabled: Optional[bool] = None) -> Self | View |
.full_screen_cover | .full_screen_cover(is_presented: bool = False, content: Optional[Union["View", Callable[[], "View"]]] = None, on_dismiss: Optional[Callable] = None, isPresented: Optional[bool] = None, onDismiss: Optional[Callable] = None) -> Self | View |
.confirmation_dialog | .confirmation_dialog(title: str, is_presented: bool = False, actions: Optional[Sequence["View"]] = None, message: str = '', on_dismiss: Optional[Callable] = None, isPresented: Optional[bool] = None, onDismiss: Optional[Callable] = None) -> Self | View |
.popover | .popover(is_presented: bool = False, content: Optional[Union["View", Callable[[], "View"]]] = None, on_dismiss: Optional[Callable] = None, isPresented: Optional[bool] = None, onDismiss: Optional[Callable] = None) -> Self | View |
.context_menu | .context_menu(content: Optional[Sequence["View"]] = None) -> Self | View |
.searchable | .searchable(text: str = '', on_change: Optional[Callable] = None, onChange: Optional[Callable] = None, placement: str = 'automatic', prompt: Optional[str] = None, on_submit: Optional[Callable] = None, onSubmit: Optional[Callable] = None) -> Self | View |
.swipe_actions | .swipe_actions(edge: str = 'trailing', content: Optional[Sequence["View"]] = None, actions: Optional[Sequence["View"]] = None) -> Self | View |
.refreshable | .refreshable(action: Optional[Callable] = None) -> Self | View |
.badge | .badge(count: Any) -> Self | View |
.inspector | .inspector(is_presented: bool = False, content: Optional[Union["View", Callable[[], "View"]]] = None, on_dismiss: Optional[Callable] = None) -> Self | View |
#列表行操作示例
已复制
import appui
state = appui.State(items=["Inbox", "Archive", "Later"], last="")
def item_key(item):
return item
def row_view(item):
def mark_done():
state.last = f"Done: {item}"
def remove_item():
state.items = [current for current in state.items if current != item]
state.last = f"Deleted: {item}"
return (
appui.Text(item)
.swipe_actions(actions=[
appui.Button("Done", action=mark_done),
appui.Button("Delete", action=remove_item, role="destructive"),
])
.context_menu([
appui.Button("Mark done", action=mark_done),
])
)
def refresh():
state.last = "Refreshed"
def body():
return appui.NavigationStack(
appui.List([
appui.Section("Items", [
appui.ForEach(state.items, row_builder=row_view, key=item_key)
]),
appui.Section("Status", [
appui.Text(state.last or "No action yet"),
]),
])
.navigation_title("Rows")
.refreshable(refresh)
)
appui.run(body, state=state)
#与相邻 API 的区别
| API | 不同点 |
|---|---|
.alert vs .confirmation_dialog | alert 用于提示;confirmation dialog 用于让用户在多个操作里确认。 |
.sheet vs .full_screen_cover | sheet 适合短任务;full screen 适合必须暂时离开主界面的完整流程。 |
.popover vs .sheet | popover 更适合 iPad 上从按钮展开的小面板;iPhone 上可能按系统规则表现为 sheet。 |
.context_menu vs .swipe_actions | context menu 是次要菜单;swipe actions 是列表行的高频快捷操作。 |
Refreshable vs .refreshable | 修饰符更常见;容器用于需要把刷新能力包成独立视图的场景。 |
#Presentation Engine 规则
- 呈现修饰符必须始终挂在最终返回的根视图上,只切换
is_presented/show_*字段。 - 禁止
if state.show_sheet: root = root.sheet(...)这种条件挂载。 content传命名函数,例如content=editor_sheet,不要content=editor_sheet()。- 规范全文见 Presentation Engine Spec。
#PresentationCoordinator(档 4)
show_* 字段变化时,框架优先走原生 PresentationCoordinator,跳过 body() 与整树 JSON:
已复制
state.show_sheet = True # 自动 coordinator
appui.presentation_present("show_sheet") # 显式 API
appui.presentation_dismiss("show_sheet")
appui.dismiss_all() # 关闭所有已注册弹层
混合更新时,presentation 字段先走 coordinator,content 字段仍 rebuild:
已复制
state.batch_update(show_sheet=True, token_draft="") # sheet 走 coordinator,draft 走 rebuild
Diagnostics:presentation.coordinator = 快路径;presentation.coordinator_fallback = 回退 JSON。
#常见错误
| 错误 | 正确做法 |
|---|---|
sheet 关闭后没有同步 State。 | 在 on_dismiss 中把 state.show_sheet = False。 |
| sheet 一闪而过,没有原生进出动画。 | 始终挂载 .sheet(is_presented=...),不要条件包裹修饰符。 |
把复杂业务逻辑直接写在 body() 的按钮参数里。 | 定义命名函数,按钮只传函数名。 |
alert / dialog 的 is_presented 一直为 True。 | 在确认、取消和 dismiss 回调中恢复为 False。 |
| 列表行删除只改本地变量。 | 修改 State 中的数据源,触发重新渲染。 |
| 用 sheet 做顶层页面导航。 | 顶层页面结构用 NavigationStack 或 TabView。 |