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

如何用SwiftUI复刻macOS DNS配置对话框?

如何实现与macOS DNS配置窗口一致的SwiftUI对话框?

需求回顾

  • 支持通过+/-按钮添加、删除行
  • 单击行即可选中该行
  • 双击行直接进入编辑状态

之前尝试用带自定义单元格的Table()实现,但外观与行为和原生DNS对话框差异较大,希望找到更贴合的SwiftUI实现方案。

核心SwiftUI特性与实现技巧

1. 用List替代Table逼近原生风格

macOS原生DNS配置窗口的列表属于轻量单列样式,SwiftUI的List在macOS上的默认渲染更贴近系统原生NSTableView的视觉风格,相比Table更容易模拟DNS窗口的简洁感。

2. 行选中状态的原生还原

通过@State变量记录选中行的ID,给单元格添加background修饰器,使用系统原生选中背景色Color(NSColor.selectedContentBackgroundColor)实现高亮,完全匹配系统控件的选中视觉效果。

3. 双击触发编辑的交互逻辑

自定义单元格视图,通过@Binding绑定数据项,用onTapGesture(count:2)触发编辑状态切换;编辑时直接显示TextField并自动聚焦,按下Enter或失去焦点时自动退出编辑,无需额外的Done按钮,完全复刻原生交互。

4. +/-按钮的规范实现

在列表下方放置+/-按钮组,点击+按钮添加空数据项并自动进入编辑状态;点击-按钮删除当前选中行,且在无选中行时自动禁用删除按钮,符合系统控件的交互规范。

修改后的示例代码

import SwiftUI

struct DNSItem: Identifiable {
    let id = UUID()
    var address: String
}

struct DNSConfigView: View {
    @State private var dnsItems: [DNSItem] = [
        DNSItem(address: "8.8.8.8"),
        DNSItem(address: "8.8.4.4")
    ]
    @State private var selectedItemID: UUID?
    @State private var editingItemID: UUID?
    
    var body: some View {
        VStack(spacing: 12) {
            Text("DNS服务器")
                .font(.headline)
            
            List($dnsItems) { $item in
                DNSCell(item: $item, isSelected: selectedItemID == item.id, editingItemID: $editingItemID)
                    .onTapGesture {
                        selectedItemID = item.id
                    }
            }
            .listStyle(.plain)
            .frame(height: 150)
            
            HStack(spacing: 8) {
                Button(action: addDNS) {
                    Image(systemName: "plus")
                }
                Button(action: removeDNS) {
                    Image(systemName: "minus")
                }
                .disabled(selectedItemID == nil)
            }
            .buttonStyle(.borderless)
        }
        .padding()
        .frame(width: 400)
    }
    
    private func addDNS() {
        let newItem = DNSItem(address: "")
        dnsItems.append(newItem)
        selectedItemID = newItem.id
        editingItemID = newItem.id
    }
    
    private func removeDNS() {
        guard let id = selectedItemID else { return }
        dnsItems.removeAll { $0.id == id }
        selectedItemID = dnsItems.last?.id
    }
}

struct DNSCell: View {
    @Binding var item: DNSItem
    let isSelected: Bool
    @Binding var editingItemID: UUID?
    
    @FocusState private var isTextFieldFocused: Bool
    
    var body: some View {
        HStack {
            if editingItemID == item.id {
                TextField("输入DNS地址", text: $item.address)
                    .focused($isTextFieldFocused)
                    .onAppear {
                        isTextFieldFocused = true
                    }
                    .onSubmit {
                        editingItemID = nil
                    }
                    .onChange(of: isTextFieldFocused) { newValue in
                        if !newValue {
                            editingItemID = nil
                        }
                    }
            } else {
                Text(item.address.isEmpty ? "未输入地址" : item.address)
                    .foregroundColor(item.address.isEmpty ? .secondary : .primary)
            }
        }
        .padding(4)
        .background(isSelected ? Color(NSColor.selectedContentBackgroundColor) : Color.clear)
        .cornerRadius(4)
        .onTapGesture(count: 2) {
            editingItemID = item.id
        }
    }
}

// 预览
struct DNSConfigView_Previews: PreviewProvider {
    static var previews: some View {
        DNSConfigView()
    }
}

关键细节说明

  • 视觉统一:使用系统原生颜色常量保证选中背景、文字样式与macOS系统控件完全一致。
  • 交互流畅:双击后自动聚焦输入框,编辑完成后自动退出,完全贴合原生DNS窗口的操作逻辑。
  • 状态处理:空地址显示灰色提示文字,删除按钮随选中状态自动禁用,提升用户体验的同时符合系统规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:03:23