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

SwiftUI歌词视图中如何为选中行添加底部间距?

SwiftUI歌词视图:实现选中行增加底部间距

我正在开发一个SwiftUI歌词视图,该视图可展示歌词并允许用户点击选中最多5行歌词,希望增加选中行的底部间距。尝试直接为Text视图添加padding和spacer,但未达到预期效果。

当前代码

struct LyricsView: View {
    let artist: String
    let title: String
    @StateObject private var viewModel: LyricsViewModel
    @State private var showSideMenu = false
    @State private var selectedLines: Set<Int> = []
    @State private var selectedText = [String]()
    var isSideMenuEnabled: Bool {
        selectedLines.count == 5
    }
    
    init(artist: String, title: String) {
        self.artist = artist
        self.title = title
        self._viewModel = StateObject(wrappedValue: LyricsViewModel())
    }
    
    var body: some View {
        ZStack {
            ScrollView {
                VStack(alignment: .leading, spacing: 20) {
                    if viewModel.isLoading && selectedLines.count < 1 {
                        ProgressView()
                            .progressViewStyle(CircularProgressViewStyle())
                            .frame(maxWidth: .infinity, alignment: .center)
                    } else if !viewModel.errorMessage.isEmpty {
                        Text(viewModel.errorMessage)
                            .foregroundColor(.red)
                            .padding()
                    } else {
                        let lines = viewModel.lyrics.split(separator: "\n")
                        VStack(alignment: .leading, spacing: 0) {
                            ForEach(lines.indices, id: \.self) { index in
                                let line = lines[index]
                                Text(String(line))
                                    .font(.system(size: 22, weight: .medium))
                                    .foregroundColor(.black)
                                    .textSelection(.enabled)
                                    .background(selectedLines.contains(index) ? Color(red: 1.0, green: 0.8, blue: 0.0) : Color.clear)
                                    .onTapGesture {
                                        if selectedLines.contains(index) {
                                            selectedLines.remove(index)
                                        } else {
                                            if selectedLines.count <= 5 {
                                                selectedLines.insert(index)
                                            }
                                        }
                                    }
                            }
                        }
                        .padding(.leading)
                    }
                }
            }
            .navigationBarTitle("\(artist) - \(title)", displayMode: .inline)
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    Button(action: {
                        showSideMenu.toggle()
                    }) {
                        Image(systemName: "line.3.horizontal")
                            .foregroundColor(.blue)
                    }
                    .disabled(!isSideMenuEnabled)
                }
            }
            .onAppear {
                Task {
                    await viewModel.searchLyrics(searchText: "\(artist) - \(title)")
                }
            }
            
            SideMenu(isShowing: $showSideMenu, selectedLines: selectedLines, lyrics: viewModel.lyrics, viewModel: viewModel)
                .onChange(of: selectedLines) { selectedLines in
                    if selectedLines.count == 5 {
                        viewModel.fetchTranslation(for: selectedLines)
                    }
                }
                .onChange(of: showSideMenu) { isShowing in
                    print("Side menu is now \(isShowing ? \"shown\" : \"hidden\")")
                    if !isShowing {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                            selectedLines.removeAll()
                        }
                    }
                }
        }
    }
}
#Preview {
    LyricsView(artist: "wizkid", title: "balance")
}

修改方案

要实现选中行增加底部间距的效果,核心是将每个歌词行的Text包裹在容器视图中,通过容器的padding属性根据选中状态动态调整底部间距。同时修正选中逻辑中的计数判断(最多选5行,需限制为selectedLines.count < 5):

修改后的完整代码

struct LyricsView: View {
    let artist: String
    let title: String
    @StateObject private var viewModel: LyricsViewModel
    @State private var showSideMenu = false
    @State private var selectedLines: Set<Int> = []
    @State private var selectedText = [String]()
    var isSideMenuEnabled: Bool {
        selectedLines.count == 5
    }
    
    init(artist: String, title: String) {
        self.artist = artist
        self.title = title
        self._viewModel = StateObject(wrappedValue: LyricsViewModel())
    }
    
    var body: some View {
        ZStack {
            ScrollView {
                VStack(alignment: .leading, spacing: 20) {
                    if viewModel.isLoading && selectedLines.count < 1 {
                        ProgressView()
                            .progressViewStyle(CircularProgressViewStyle())
                            .frame(maxWidth: .infinity, alignment: .center)
                    } else if !viewModel.errorMessage.isEmpty {
                        Text(viewModel.errorMessage)
                            .foregroundColor(.red)
                            .padding()
                    } else {
                        let lines = viewModel.lyrics.split(separator: "\n")
                        VStack(alignment: .leading, spacing: 0) {
                            ForEach(lines.indices, id: \.self) { index in
                                let line = lines[index]
                                // 将Text包裹在VStack中,动态添加底部间距
                                VStack(alignment: .leading, spacing: 0) {
                                    Text(String(line))
                                        .font(.system(size: 22, weight: .medium))
                                        .foregroundColor(.black)
                                        .textSelection(.enabled)
                                        .background(selectedLines.contains(index) ? Color(red: 1.0, green: 0.8, blue: 0.0) : Color.clear)
                                }
                                // 选中时添加底部间距,未选中时间距为0
                                .padding(.bottom, selectedLines.contains(index) ? 15 : 0)
                                .onTapGesture {
                                    if selectedLines.contains(index) {
                                        selectedLines.remove(index)
                                    } else {
                                        // 修正判断:最多选5行,当前数量小于5时才允许添加
                                        if selectedLines.count < 5 {
                                            selectedLines.insert(index)
                                        }
                                    }
                                }
                            }
                        }
                        .padding(.leading)
                    }
                }
            }
            .navigationBarTitle("\(artist) - \(title)", displayMode: .inline)
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    Button(action: {
                        showSideMenu.toggle()
                    }) {
                        Image(systemName: "line.3.horizontal")
                            .foregroundColor(.blue)
                    }
                    .disabled(!isSideMenuEnabled)
                }
            }
            .onAppear {
                Task {
                    await viewModel.searchLyrics(searchText: "\(artist) - \(title)")
                }
            }
            
            SideMenu(isShowing: $showSideMenu, selectedLines: selectedLines, lyrics: viewModel.lyrics, viewModel: viewModel)
                .onChange(of: selectedLines) { selectedLines in
                    if selectedLines.count == 5 {
                        viewModel.fetchTranslation(for: selectedLines)
                    }
                }
                .onChange(of: showSideMenu) { isShowing in
                    print("Side menu is now \(isShowing ? \"shown\" : \"hidden\")")
                    if !isShowing {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                            selectedLines.removeAll()
                        }
                    }
                }
        }
    }
}
#Preview {
    LyricsView(artist: "wizkid", title: "balance")
}

关键修改点

  • 将单个Text视图包裹在VStack容器中,确保padding(.bottom)能作用于整个行区域
  • 新增.padding(.bottom, selectedLines.contains(index) ? 15 : 0),根据选中状态动态设置底部间距(数值15可根据需求调整)
  • 修正选中逻辑中的判断条件:将selectedLines.count <= 5改为selectedLines.count < 5,避免选中第6行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:39:50