肇鑫的技术博客

业精于勤,荒于嬉

原来Swift的Sheet里还不能这么做

今天在调试一个应用崩溃的问题的时候,发现有个sheet弹出后,被反复执行了多次。于是询问AI怎么回事。

原始的代码是这样的

.sheet(isPresented: $showAITranslationGuideView) {
  let xtTransUnits: [XTTransUnit] = {
    let xtTransUnits = Xliff_Tool.getXTTransUnits(from: trees)
    return xtTransUnits.filter { $0.target == nil || $0.target?.isEmpty == true }
  }()

  AITranslationView(xtTransUnits: xtTransUnits)
}

询问AI啥原因,AI是这么回答的。

.sheet(isPresented: $showAITranslationGuideView)为什么会多次执行?

在 .sheet(isPresented: $showAITranslationGuideView) 代码块中,每次 showAITranslationGuideView 状态变化时都会重新执行闭包内的代码。这里的问题是 xtTransUnits 的计算被放在了视图构建的闭包中,而不是在实际需要时才执行。

建议将这些逻辑移到 AITranslationView 的初始化或 onAppear 中。以下是修改建议:

.sheet(isPresented: $showAITranslationGuideView) {
    AITranslationView(trees: trees)
}

然后在 AITranslationView 中处理数据:

struct AITranslationView: View {
    let trees: [Tree<IDTree>]
    @State private var xtTransUnits: [XTTransUnit] = []
    
    var body: some View {
        // 你的视图内容
        .onAppear {
            xtTransUnits = getXTTransUnits(from: trees)
                .filter { $0.target == nil || $0.target?.isEmpty == true }
        }
    }
}

这样可以确保计算只在实际需要时执行一次。

需要注意的是,AI的解释是对的,但是不是真正的原因。但是解法又同样是对的。只要移出这个初始化的代码之后,的确就不会重复计算了。

我认为真正原因是这样的。像.sheet(isPresented: $showAITranslationGuideView)的sheet,是在View初始化时就会被生成的。它是不变的。这就是为什么还有一个同名的但是使用Optional的sheet,那个才是动态的。但是我这里面的代码,在sheet使用前重新进行了计算。这导致原本应该只能是静态的sheet,变成了动态的,从而导致它从新被加载,而这种情形,其实是为定义的,因为这里不应该使用动态的。这个才是出错的真正原因。

TabView自动跳转Tab问题的解决

今天我在开发应用的时候遇到了一个奇怪的问题。当TabView的最后一个tab被拖动图片之后,应用会自动跳到第一个tab去。我很奇怪为什么有这个行为,于是拿来问AI。没想到很快就解决了。

提示词:阅读代码,尝试解决问题。当最后一个tab被拖动图片后,它会自动跳到第一个tab,我不明白这是什么原因造成的。

AI回复

问题分析

我已经查看了您的代码,找到了为什么当最后一个watchOSTabView被拖动图片后会自动跳回第一个标签页的问题。

这是由于在TabView中没有设置选中的标签页状态,所以当watchOSTabView中的State变量更新时,视图会重新绘制,导致TabView回到默认的第一个标签页。

解决方案

我们需要在MainView中添加一个State变量来跟踪当前选中的标签页,并将其绑定到TabView。这样,即使视图重新绘制,也会保持在当前选中的标签页上。

//
//  MainView.swift
//  My Icon Gear
//
//  Created by zhaoxin on 2025/3/5.
//

import SwiftUI

struct MainView: View {
  // 添加一个状态变量来跟踪选中的标签页
  @State private var selectedTab = 0
  private let platforms = ["iOS", "macOS", "watchOS"]

  var body: some View {
    TabView(selection: $selectedTab) {
      macOSTabView()
        .tag(0)
      iOSTabView()
        .tag(1)
      watchOSTabView()
        .tag(2)
    }
    .toolbar(content: {
      Button {

      } label: {
        Image(systemName: "square.and.arrow.up")
        Text("Export to Folder")
      }
      .bold()
    })
    .padding()
  }
}

#Preview {
  MainView()
}

解决方案解释

我对MainView.swift文件进行了以下修改:

  1. 添加了一个@State private var selectedTab = 0状态变量来跟踪当前选中的标签页。

  2. 修改了TabView,使用TabView(selection: $selectedTab)将选中状态绑定到TabView。

  3. 为每个TabView的子视图添加了.tag()修饰符,分别标记为0、1、2,这样系统就能知道哪个标签页被选中。

这样修改后,当您在watchOSTabView(最后一个标签页)上拖放图片时,即使视图状态发生变化并重新绘制,TabView也会保持在当前选中的标签页(即tag为2的watchOSTabView),而不会跳回第一个标签页。

这是因为现在TabView的选择状态由selectedTab变量控制,而不是由系统自动决定。无论是哪个子视图更新状态并触发重绘,TabView都会保持在用户上次选择的标签页。

List更改选中状态的背景色正确办法

macOS中,默认List选中之后的背景是深蓝色。这会导致我的按钮变得不明显,我希望修改这个背景色。

询问AI,AI给我的反馈是通过background修改。但是通过background修改的背景色,会比系统自带的小一圈,十分丑陋。我再次询问AI,如果要修改系统的默认选中背景,要如何做。AI回答说,要修改那个很复杂。然后给了我一个错误的解法。

我只要换个AI来问。第二个AI给出的也是通过background修改。不过它在说思路的时候,应该是通过listRowBackground修改。于是我自己尝试用listRowBackground来修改。这次成功了。

.listRowBackground(selectedLanguage == locale ? Color.gray.opacity(0.15) : Color.clear)

当使用listRowBackground,List的选中就不要绑定selectedLanguage了。因为那样会导致.listRowBackground和高亮色被同时使用。

高亮色就是List选中的背景色的正式说法。

因为上面说的原因,我们需要手动指令selectedLanguage,可以通过点击或者onHover。看你的界面的需要。