You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何实现.contextMenu预览点击全屏展开功能?

实现SwiftUI ContextMenu预览的点击全屏展开功能(仿Stocks应用)

需求描述

想要在ForEach列表的SwiftUI视图中,复刻苹果Stocks应用的交互逻辑:长按新闻项弹出ContextMenu预览,点击预览可全屏展开网页内容。当前代码的ContextMenu预览无法交互,需要修改实现这一功能。

当前代码

列表核心代码

if !vm.StockNews.isEmpty {
    ForEach(vm.StockNews, id: \.self) { news in
        if let url = URL(string: news.link) {
            VStack(alignment: .leading) {
                Text("\(news.title)")
                    .font(.title)
                    .fontWeight(.semibold)
                    .padding(EdgeInsets(top: 0, leading: 5, bottom: 5, trailing: 5))
                    .lineLimit(4)
                
                Text("\(news.description)")
                    .font(.headline)
                    .padding(5)
                    .lineLimit(3)
                    .fixedSize(horizontal: false, vertical: true)
                
                Text("\(vm.formattedTimeAgo(from: news.pubDate))")
                    .padding(EdgeInsets(top: 5, leading: 5, bottom: 0, trailing: 5))
            }
            .fixedSize(horizontal: false, vertical: true)
            
            .onTapGesture {
                isPresentingWebView = true
            }
            
            .fullScreenCover(isPresented: $isPresentingWebView) {
                SafariWebView(url: url)
                    .ignoresSafeArea()
            }
            
            
            .contextMenu {
                Button(action: {
                    UIPasteboard.general.string = news.link
                }) {
                    Text("Kopier lenke")
                    Image(systemName: "link")
                }
                
            } preview: {
                SafariWebView(url: url)
                
            }
            
            
        } else {
            VStack(alignment: .leading) {
                Text("\(news.title)")
                    .font(.title)
                    .fontWeight(.semibold)
                    .padding(5)
                    .lineLimit(3)
                
                Text("\(news.description)")
                    .font(.headline)
                    .padding(5)
                    .lineLimit(3)
                    .fixedSize(horizontal: false, vertical: true)
                
                Text("\(news.pubDate)")
                    .padding(5)
            }
            .fixedSize(horizontal: false, vertical: true)
        }
        
    }
} else {
    HStack {
        Spacer()
        Text("Ingen nylige nyheter")
            .font(.title2.bold())
        
        Spacer()
    }
    .padding(.top, -15)
    .padding(.bottom, 30)
    .listRowSeparator(.hidden)
}

SafariWebView代码

import SwiftUI
import SafariServices

struct SafariWebView: UIViewControllerRepresentable {
    let url: URL
    
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let config = SFSafariViewController.Configuration()
        return SFSafariViewController(url: url, configuration: config)
    }
    
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
        
    }
}

解决方案

默认的ContextMenu预览视图不可交互,需通过以下步骤改造:

  1. 替换状态变量:将单个布尔值isPresentingWebView改为可选URL变量,用于跟踪当前要全屏展示的新闻链接:
@State private var selectedNewsURL: URL?
  1. 给预览添加点击交互:在ContextMenu的预览视图上绑定点击手势,点击时设置目标URL并关闭ContextMenu:
.contextMenu {
    Button(action: {
        UIPasteboard.general.string = news.link
    }) {
        Text("Kopier lenke")
        Image(systemName: "link")
    }
} preview: {
    SafariWebView(url: url)
        .onTapGesture {
            selectedNewsURL = url
            // 强制关闭ContextMenu
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }
}
  1. 更新全屏展示逻辑:将fullScreenCover绑定到selectedNewsURL,利用item绑定自动关联对应URL,关闭时重置变量:
.fullScreenCover(item: $selectedNewsURL) { _ in
    SafariWebView(url: url)
        .ignoresSafeArea()
}
  1. 调整列表项点击逻辑:原列表项的点击手势同步更新为设置selectedNewsURL:
.onTapGesture {
    selectedNewsURL = url
}

修改后的核心代码片段

// 顶部新增状态变量
@State private var selectedNewsURL: URL?

// ... 其他代码 ...

if !vm.StockNews.isEmpty {
    ForEach(vm.StockNews, id: \.self) { news in
        if let url = URL(string: news.link) {
            VStack(alignment: .leading) {
                // 新闻标题、描述、时间代码保持不变
            }
            .fixedSize(horizontal: false, vertical: true)
            
            .onTapGesture {
                selectedNewsURL = url
            }
            
            .fullScreenCover(item: $selectedNewsURL) { _ in
                SafariWebView(url: url)
                    .ignoresSafeArea()
            }
            
            .contextMenu {
                Button(action: {
                    UIPasteboard.general.string = news.link
                }) {
                    Text("Kopier lenke")
                    Image(systemName: "link")
                }
            } preview: {
                SafariWebView(url: url)
                    .onTapGesture {
                        selectedNewsURL = url
                        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                    }
            }
        } else {
            // 无效URL的视图代码保持不变
        }
    }
} else {
    // 无新闻的视图代码保持不变
}

关键说明

  • 使用item绑定的fullScreenCover更适配多列表项场景,能自动关联对应URL,避免单个布尔值的状态冲突。
  • 调用resignFirstResponder可强制关闭ContextMenu,防止预览视图与全屏视图同时显示。
  • 预览视图的onTapGesture会覆盖默认预览的无交互状态,实现点击触发全屏的效果。

内容的提问来源于stack exchange,提问作者Kristoffer Melen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 13:07:55