룰렛 설정 입력 가시성과 프로필 메뉴 레이아웃 개선

룰렛 설정에서 입력 필드 포커스 시 항목을 중앙으로 이동한다.
키보드에 가려지지 않도록 입력 가시성을 높인다.
프로필 메뉴 오버레이의 하단 안전영역 패딩을 제거한다.
This commit is contained in:
Yu Sung
2026-02-09 17:43:45 +09:00
parent 7cba6de2fc
commit 7f703024d8
4 changed files with 57 additions and 10 deletions

View File

@@ -541,7 +541,6 @@ struct UserProfileView: View {
viewModel.isShowPopup = true viewModel.isShowPopup = true
} }
.padding(.top, proxy.safeAreaInsets.top) .padding(.top, proxy.safeAreaInsets.top)
.padding(.bottom, proxy.safeAreaInsets.bottom)
.padding(.trailing, proxy.safeAreaInsets.trailing) .padding(.trailing, proxy.safeAreaInsets.trailing)
.padding(.leading, proxy.safeAreaInsets.leading) .padding(.leading, proxy.safeAreaInsets.leading)
} }
@@ -549,7 +548,6 @@ struct UserProfileView: View {
if isShowMenuSettings { if isShowMenuSettings {
MenuSettingsView(isShowing: $isShowMenuSettings) MenuSettingsView(isShowing: $isShowMenuSettings)
.padding(.top, proxy.safeAreaInsets.top) .padding(.top, proxy.safeAreaInsets.top)
.padding(.bottom, proxy.safeAreaInsets.bottom)
.padding(.trailing, proxy.safeAreaInsets.trailing) .padding(.trailing, proxy.safeAreaInsets.trailing)
.padding(.leading, proxy.safeAreaInsets.leading) .padding(.leading, proxy.safeAreaInsets.leading)
} }

View File

@@ -7,9 +7,11 @@
import SwiftUI import SwiftUI
class RouletteOption: ObservableObject { class RouletteOption: ObservableObject, Identifiable {
var title: String let id = UUID()
var percentage: String = ""
@Published var title: String
@Published var percentage: String = ""
init(title: String, percentage: String) { init(title: String, percentage: String) {
self.title = title self.title = title

View File

@@ -12,6 +12,7 @@ struct RouletteSettingsOptionView: View {
@ObservedObject var option: RouletteOption @ObservedObject var option: RouletteOption
let index: Int let index: Int
var focusedField: FocusState<RouletteSettingsFocusField?>.Binding
let onClickDelete: () -> Void let onClickDelete: () -> Void
let calculateTotalPercentage: () -> Void let calculateTotalPercentage: () -> Void
@@ -39,6 +40,7 @@ struct RouletteSettingsOptionView: View {
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.keyboardType(.default) .keyboardType(.default)
.focused(focusedField, equals: .optionTitle(option.id))
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
.padding(.vertical, 16.7) .padding(.vertical, 16.7)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -52,6 +54,7 @@ struct RouletteSettingsOptionView: View {
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.focused(focusedField, equals: .optionPercentage(option.id))
.onChange(of: option.percentage) { newValue in .onChange(of: option.percentage) { newValue in
if newValue.count > 5 { if newValue.count > 5 {
option.percentage = String(newValue.prefix(5)) option.percentage = String(newValue.prefix(5))
@@ -76,9 +79,18 @@ struct RouletteSettingsOptionView: View {
struct RouletteSettingsOptionView_Previews: PreviewProvider { struct RouletteSettingsOptionView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
PreviewContainer()
}
}
private struct PreviewContainer: View {
@FocusState private var focusedField: RouletteSettingsFocusField?
var body: some View {
RouletteSettingsOptionView( RouletteSettingsOptionView(
option: RouletteOption(title: "옵션1", percentage: ""), option: RouletteOption(title: "옵션1", percentage: ""),
index: 2, index: 2,
focusedField: $focusedField,
onClickDelete: {}, onClickDelete: {},
calculateTotalPercentage: {} calculateTotalPercentage: {}
) )

View File

@@ -7,10 +7,17 @@
import SwiftUI import SwiftUI
enum RouletteSettingsFocusField: Hashable {
case canAmount
case optionTitle(UUID)
case optionPercentage(UUID)
}
struct RouletteSettingsView: View { struct RouletteSettingsView: View {
@StateObject var keyboardHandler = KeyboardHandler() @StateObject var keyboardHandler = KeyboardHandler()
@StateObject var viewModel = RouletteSettingsViewModel() @StateObject var viewModel = RouletteSettingsViewModel()
@FocusState private var focusedField: RouletteSettingsFocusField?
@Binding var isShowing: Bool @Binding var isShowing: Bool
@@ -25,8 +32,9 @@ struct RouletteSettingsView: View {
isShowing = false isShowing = false
} }
ScrollView(.vertical, showsIndicators: false) { ScrollViewReader { scrollProxy in
VStack(spacing: 0) { ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
SelectedButtonView( SelectedButtonView(
title: I18n.Common.roulette1, title: I18n.Common.roulette1,
@@ -100,6 +108,7 @@ struct RouletteSettingsView: View {
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.keyboardType(.numberPad) .keyboardType(.numberPad)
.focused($focusedField, equals: .canAmount)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
.padding(.vertical, 16.7) .padding(.vertical, 16.7)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -114,6 +123,7 @@ struct RouletteSettingsView: View {
} }
} }
.padding(.top, 26.7) .padding(.top, 26.7)
.id("roulette_can_input")
VStack(alignment: .leading, spacing: 21.3) { VStack(alignment: .leading, spacing: 21.3) {
Text("룰렛 옵션 설정") Text("룰렛 옵션 설정")
@@ -146,20 +156,31 @@ struct RouletteSettingsView: View {
} }
.padding(.top, 21.3) .padding(.top, 21.3)
LazyVStack(spacing: 21.3) { VStack(spacing: 21.3) {
ForEach(viewModel.options.indices, id: \.self) { index in ForEach(Array(viewModel.options.enumerated()), id: \.element.id) { index, option in
RouletteSettingsOptionView( RouletteSettingsOptionView(
option: viewModel.options[index], option: option,
index: index, index: index,
focusedField: $focusedField,
onClickDelete: { viewModel.deleteOption(index: index) }, onClickDelete: { viewModel.deleteOption(index: index) },
calculateTotalPercentage: { viewModel.calculateTotalPercentage() } calculateTotalPercentage: { viewModel.calculateTotalPercentage() }
) )
.id(option.id)
} }
} }
.padding(.top, 21.3) .padding(.top, 21.3)
} }
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
.padding(.bottom, keyboardHandler.keyboardHeight) .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() Spacer()
@@ -204,6 +225,7 @@ struct RouletteSettingsView: View {
} }
} }
.onTapGesture { .onTapGesture {
focusedField = nil
hideKeyboard() 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 { struct RouletteSettingsView_Previews: PreviewProvider {