From 7f703024d850d65a2a4dc5712a415d83325c2381 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 9 Feb 2026 17:43:45 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A3=B0=EB=A0=9B=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B0=80=EC=8B=9C=EC=84=B1=EA=B3=BC=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EB=A9=94=EB=89=B4=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 룰렛 설정에서 입력 필드 포커스 시 항목을 중앙으로 이동한다. 키보드에 가려지지 않도록 입력 가시성을 높인다. 프로필 메뉴 오버레이의 하단 안전영역 패딩을 제거한다. --- .../Explorer/Profile/UserProfileView.swift | 2 - .../Routlette/Config/RouletteOption.swift | 8 ++-- .../Config/RouletteSettingsOptionView.swift | 12 +++++ .../Config/RouletteSettingsView.swift | 45 ++++++++++++++++--- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift index d7e6c43..23adf51 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift @@ -541,7 +541,6 @@ struct UserProfileView: View { viewModel.isShowPopup = true } .padding(.top, proxy.safeAreaInsets.top) - .padding(.bottom, proxy.safeAreaInsets.bottom) .padding(.trailing, proxy.safeAreaInsets.trailing) .padding(.leading, proxy.safeAreaInsets.leading) } @@ -549,7 +548,6 @@ struct UserProfileView: View { if isShowMenuSettings { MenuSettingsView(isShowing: $isShowMenuSettings) .padding(.top, proxy.safeAreaInsets.top) - .padding(.bottom, proxy.safeAreaInsets.bottom) .padding(.trailing, proxy.safeAreaInsets.trailing) .padding(.leading, proxy.safeAreaInsets.leading) } diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteOption.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteOption.swift index d7312f0..3a4a859 100644 --- a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteOption.swift +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteOption.swift @@ -7,9 +7,11 @@ import SwiftUI -class RouletteOption: ObservableObject { - var title: String - var percentage: String = "" +class RouletteOption: ObservableObject, Identifiable { + let id = UUID() + + @Published var title: String + @Published var percentage: String = "" init(title: String, percentage: String) { self.title = title diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsOptionView.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsOptionView.swift index 2aa3517..7ca394c 100644 --- a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsOptionView.swift +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsOptionView.swift @@ -12,6 +12,7 @@ struct RouletteSettingsOptionView: View { @ObservedObject var option: RouletteOption let index: Int + var focusedField: FocusState.Binding let onClickDelete: () -> Void let calculateTotalPercentage: () -> Void @@ -39,6 +40,7 @@ struct RouletteSettingsOptionView: View { .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) .keyboardType(.default) + .focused(focusedField, equals: .optionTitle(option.id)) .padding(.horizontal, 13.3) .padding(.vertical, 16.7) .frame(maxWidth: .infinity) @@ -52,6 +54,7 @@ struct RouletteSettingsOptionView: View { .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) .keyboardType(.decimalPad) + .focused(focusedField, equals: .optionPercentage(option.id)) .onChange(of: option.percentage) { newValue in if newValue.count > 5 { option.percentage = String(newValue.prefix(5)) @@ -76,9 +79,18 @@ struct RouletteSettingsOptionView: View { struct RouletteSettingsOptionView_Previews: PreviewProvider { static var previews: some View { + PreviewContainer() + } +} + +private struct PreviewContainer: View { + @FocusState private var focusedField: RouletteSettingsFocusField? + + var body: some View { RouletteSettingsOptionView( option: RouletteOption(title: "옵션1", percentage: ""), index: 2, + focusedField: $focusedField, onClickDelete: {}, calculateTotalPercentage: {} ) diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift index c017c63..577afae 100644 --- a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift @@ -7,10 +7,17 @@ import SwiftUI +enum RouletteSettingsFocusField: Hashable { + case canAmount + case optionTitle(UUID) + case optionPercentage(UUID) +} + struct RouletteSettingsView: View { @StateObject var keyboardHandler = KeyboardHandler() @StateObject var viewModel = RouletteSettingsViewModel() + @FocusState private var focusedField: RouletteSettingsFocusField? @Binding var isShowing: Bool @@ -25,8 +32,9 @@ struct RouletteSettingsView: View { isShowing = false } - ScrollView(.vertical, showsIndicators: false) { - VStack(spacing: 0) { + ScrollViewReader { scrollProxy in + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 0) { HStack(spacing: 13.3) { SelectedButtonView( title: I18n.Common.roulette1, @@ -100,6 +108,7 @@ struct RouletteSettingsView: View { .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) .keyboardType(.numberPad) + .focused($focusedField, equals: .canAmount) .padding(.horizontal, 13.3) .padding(.vertical, 16.7) .frame(maxWidth: .infinity) @@ -114,6 +123,7 @@ struct RouletteSettingsView: View { } } .padding(.top, 26.7) + .id("roulette_can_input") VStack(alignment: .leading, spacing: 21.3) { Text("룰렛 옵션 설정") @@ -146,20 +156,31 @@ struct RouletteSettingsView: View { } .padding(.top, 21.3) - LazyVStack(spacing: 21.3) { - ForEach(viewModel.options.indices, id: \.self) { index in + VStack(spacing: 21.3) { + ForEach(Array(viewModel.options.enumerated()), id: \.element.id) { index, option in RouletteSettingsOptionView( - option: viewModel.options[index], + option: option, index: index, + focusedField: $focusedField, onClickDelete: { viewModel.deleteOption(index: index) }, calculateTotalPercentage: { viewModel.calculateTotalPercentage() } ) + .id(option.id) } } .padding(.top, 21.3) } .padding(.horizontal, 13.3) .padding(.bottom, keyboardHandler.keyboardHeight) + .onChange(of: focusedField) { _ in + scrollToFocusedField(scrollProxy: scrollProxy) + } + .onChange(of: keyboardHandler.keyboardHeight) { height in + if height > 0, focusedField != nil { + scrollToFocusedField(scrollProxy: scrollProxy) + } + } + } } Spacer() @@ -204,6 +225,7 @@ struct RouletteSettingsView: View { } } .onTapGesture { + focusedField = nil hideKeyboard() } @@ -240,6 +262,19 @@ struct RouletteSettingsView: View { } } } + + private func scrollToFocusedField(scrollProxy: ScrollViewProxy) { + guard let focusedField else { return } + + withAnimation(.easeOut(duration: 0.2)) { + switch focusedField { + case .canAmount: + scrollProxy.scrollTo("roulette_can_input", anchor: .center) + case .optionTitle(let id), .optionPercentage(let id): + scrollProxy.scrollTo(id, anchor: .center) + } + } + } } struct RouletteSettingsView_Previews: PreviewProvider {