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
相关产品推荐
相关产品推荐

