Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,74 +29,99 @@ struct TargetDdaySettingView: View {
@Binding var month: String
@Binding var day: String
@Binding var warningMessage: String
@State private var keyboardHeight: CGFloat = 0

var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack {
Spacer()
.frame(height: 50.adjustedH)

HStack(spacing:0){
VStack(alignment: .leading, spacing: 0) {
TypographyText("ํšŒ๋ณต์„ ๊ณ„ํšํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ• ", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
TypographyText("์ค‘์š”ํ•œ ์ผ์ •์ด ์žˆ๋‚˜์š”?", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
ScrollViewReader { proxy in
ScrollView(.vertical, showsIndicators: false) {
VStack {
Spacer()
.frame(height: 50.adjustedH)

HStack(spacing:0){
VStack(alignment: .leading, spacing: 0) {
TypographyText("ํšŒ๋ณต์„ ๊ณ„ํšํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ• ", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
TypographyText("์ค‘์š”ํ•œ ์ผ์ •์ด ์žˆ๋‚˜์š”?", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
}
Spacer()
}
Spacer()
}
Spacer()
.frame(height: 40.adjustedH)

HStack(spacing: 12.adjustedW) {
ForEach(DdayState.allCases, id: \.self) { state in
SelectionChip(
title: state.title,
isSelected: Binding(
get: {
dDayState == state
},
set: {isSelected in
guard isSelected else {
return
.frame(height: 40.adjustedH)
HStack(spacing: 12.adjustedW) {
ForEach(DdayState.allCases, id: \.self) { state in
SelectionChip(
title: state.title,
isSelected: Binding(
get: {
dDayState == state
},
set: {isSelected in
guard isSelected else {
return
}
dDayState = state
}
dDayState = state
}
)
)
)
}
}
}

Spacer()
.frame(height: 56.adjustedH)

if let state = dDayState {
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 0) {
switch state {
case .yes:
TypographyText("์–ธ์ œ๊นŒ์ง€ ํšŒ๋ณต์ด ์™„๋ฃŒ๋˜๋ฉด ์ข‹์„๊นŒ์š”?", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
case .no:
TypographyText("๋Œ€๋žต์ ์ธ ํšŒ๋ณต ๋ชฉํ‘œ์ผ์„ ์ •ํ•ด๋ณผ๊นŒ์š”?", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
Spacer()
.frame(height: 56.adjustedH)

if let state = dDayState {
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 0) {
switch state {
case .yes:
TypographyText("์–ธ์ œ๊นŒ์ง€ ํšŒ๋ณต์ด ์™„๋ฃŒ๋˜๋ฉด ์ข‹์„๊นŒ์š”?", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
case .no:
TypographyText("๋Œ€๋žต์ ์ธ ํšŒ๋ณต ๋ชฉํ‘œ์ผ์„ ์ •ํ•ด๋ณผ๊นŒ์š”?", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)
}
}
Spacer()

}

Spacer()
.frame(height: 24.adjustedH)

DateTextBox(year: $year, month: $month, day: $day)
.id("inputField")
.padding(.bottom, 12.adjustedH)
TreatmentWarningMessgeView(text: warningMessage)

Spacer()
.frame(height: keyboardHeight)
}

Spacer()
.frame(height: 24.adjustedH)

DateTextBox(year: $year, month: $month, day: $day)
.padding(.bottom, 12.adjustedH)
TreatmentWarningMessgeView(text: warningMessage)
Spacer()
}

.padding(.horizontal, 34.adjustedW)
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
keyboardHeight = keyboardFrame.height
}
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
withAnimation(.easeOut(duration: 0.25)) {
keyboardHeight = 0
}
}
.onChange(of: keyboardHeight) { _, newValue in
if newValue > 0 {
withAnimation(.easeOut(duration: 0.25)) {
proxy.scrollTo("inputField", anchor: .center)
}
Comment on lines +107 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์™€์šฐ๊ตฟ ์ˆ˜๊ณ ํ•˜์…จ์Šต๋‹ˆ๋‹ค

}
Comment on lines +107 to 122
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

find . -name "TargetDdaySettingView.swift" -type f 2>/dev/null

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 170


๐Ÿ Script executed:

cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/TargetDdaySettingView.swift | sed -n '100,130p'

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1342


๐Ÿ Script executed:

wc -l Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/TargetDdaySettingView.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 172


๐Ÿ Script executed:

cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/TargetDdaySettingView.swift | head -50

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1826


๐Ÿ Script executed:

rg "keyboardHeight|keyboardWill" Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/TargetDdaySettingView.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 576


๐ŸŒ Web query:

iOS UIResponder keyboard notifications keyboardWillChangeFrameNotification vs keyboardWillShow keyboardWillHide

๐Ÿ’ก Result:

Short answer

  • keyboardWillShow/keyboardDidShow and keyboardWillHide/keyboardDidHide are high-level "visibility" notifications (keyboard being shown or hidden). Use them when you only care about whether the keyboard is appearing or disappearing. [1][2]
  • keyboardWillChangeFrame/keyboardDidChangeFrame fire whenever the keyboardโ€™s frame is about to change (or has changed) โ€” that covers showing/hiding plus frame moves: size changes, language/layout switch, docking/undocking, split keyboard, inputAccessory changes, user dragging the keyboard, etc. Use these when you need the keyboardโ€™s actual frame/height or to animate UI with the keyboard. [3][1]

Practical notes

  • The change-frame notifications include userInfo keys youโ€™ll typically need: UIKeyboardFrameBeginUserInfoKey / UIKeyboardFrameEndUserInfoKey, UIKeyboardAnimationDurationUserInfoKey and UIKeyboardAnimationCurveUserInfoKey โ€” read the end frame and animation info and animate your layout to match. [3]
  • On iPad the distinction matters more (docking/undocking and dragging can change frame without a simple show/hide). [3]
  • Many apps subscribe to keyboardWillChangeFrameNotification (and keyboardWillHide) as a robust way to adjust insets/constraints; some guard against spurious show/hide notifications by tracking a keyboardShowing flag. [3][2]

Sources

  1. Apple Developer: UIResponder.keyboardWillShow / keyboardWillHide pages. [1][2]
  2. Explanation and examples (UIKeyboard notifications, including keyboardWillChangeFrame): apeth.com and community threads. [3]

๐ŸŒ Web query:

iOS keyboard frame changes rotation orientation UIResponder notifications

๐Ÿ’ก Result:

Short summary / recipe

  • Observe UIResponder.keyboardWillChangeFrameNotification (and keyboardDidChangeFrameNotification if you need the final state). Use WillChangeFrame to animate in sync with the keyboard. [1][2]

  • Read the end frame from notification.userInfo[UIResponder.keyboardFrameEndUserInfoKey] (CGRect wrapped in NSValue). These frames are in screen coordinates and may not reflect your viewโ€™s orientation, so convert into your view/window coordinates before using: e.g. let endFrameInView = view.convert(keyboardEndFrame, from: nil) or window?.convert(...). Also check UIKeyboardIsLocalUserInfoKey to ignore another-app keyboards. [2][3]

  • Match the keyboard animation: read UIKeyboardAnimationDurationUserInfoKey and UIKeyboardAnimationCurveUserInfoKey from userInfo and animate your layout changes with the same duration/curve (or use UIView.animate with options: animationCurve << 16 mapping, or use UIViewPropertyAnimator). That keeps movement smooth during rotation. [1][3]

  • Handle rotation edge cases:

    • Rotation can cause keyboard frame changes (keyboardWillChangeFrame fires) rather than separate show/hide events; rely on endFrame to decide whether keyboard is visible (endFrame.origin.y >= UIScreen.main.bounds.height => hidden). [2]
    • On iPad and with floating/split keyboards or external keyboards, frames/behavior differ โ€” prefer adjusting safeAreaInsets or bottom constraints instead of moving the whole view. [1][3]

Minimal example (Swift):

  • add observer for keyboardWillChangeFrameNotification
  • in handler:
    • guard let frame = (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
    • let frameInView = view.convert(frame, from: nil)
    • let duration = userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
    • let curve = (userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int) ?? 0
    • update bottom constraint = max(0, view.bounds.maxY - frameInView.minY)
    • animate layoutIfNeeded with duration and matching curve.

References

  • Apple UIResponder keyboard notifications (docs). [1]
  • Keyboard frame keys & screen-coordinate conversion discussion (StackOverflow / Managing the Keyboard). [2]
  • Practical guides / examples showing convertRect, animation matching and iPad considerations. [3]

If you want, I can paste a ready-to-use Swift handler class using these practices.


๐ŸŒ Web query:

iOS keyboard safe area inset calculation UIScreen bounds keyboardFrameEndUserInfoKey

๐Ÿ’ก Result:

Short answer โ€” use the keyboardโ€™s end frame in the keyboardโ€™s coordinate space and convert it into your viewโ€™s coordinate space, then take the intersection height with your view bounds (that intersection is the keyboard overlap / safe-area bottom inset to apply). This avoids orientation/rotation and homeโ€‘indicator double-counting.

Recommended Swift pattern (concise):

  • Observe UIResponder.keyboardWillChangeFrameNotification / .keyboardDidChangeFrame.
  • From notification: get the CGRect and the UIScreen (notification.object on iOS 16.1+).
  • Convert the keyboard rect into your viewโ€™s coordinate space using the screenโ€™s coordinateSpace (or window conversion).
  • Compute intersection with view.bounds; use intersection.height as the inset.

Example:

let info = notification.userInfo!
let endFrame = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
if let screen = notification.object as? UIScreen {
let converted = screen.coordinateSpace.convert(endFrame, to: view)
let overlap = view.bounds.intersection(converted)
let bottomInset = overlap.isNull ? 0 : overlap.height
// apply bottomInset (or animate constraint using animation info from userInfo)
}

Notes:

  • If you instead take keyboard height directly, subtract window.safeAreaInsets.bottom to avoid adding the homeโ€‘indicator area twice.

ํ‚ค๋ณด๋“œ ํ”„๋ ˆ์ž„ ๋ณ€๊ฒฝ(ํšŒ์ „/ํƒ€์ž… ์ „ํ™˜)์— ๋†’์ด๊ฐ€ ๊ฐฑ์‹ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

keyboardWillShow/Hide ์•Œ๋ฆผ๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ํ‚ค๋ณด๋“œ ํšŒ์ „, ์ž…๋ ฅ๊ธฐ ํƒ€์ž… ๋ณ€๊ฒฝ, ํ‚ค๋ณด๋“œ ๋†’์ด ๋ณ€ํ™” ์ค‘์—๋Š” ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์•„ ์ž…๋ ฅ์ฐฝ์ด ๊ฐ€๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. keyboardWillChangeFrameNotification์œผ๋กœ ํ†ตํ•ฉํ•˜๊ณ , ์•Œ๋ฆผ์˜ UIKeyboardAnimationDurationUserInfoKey๋ฅผ ์‚ฌ์šฉํ•ด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋™๊ธฐํ™”ํ•˜๋ฉฐ, ์•ˆ์ „ ์˜์—ญ ์ธ์…‹ ์ฐจ๊ฐ์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.

๐Ÿ”ง ๊ฐœ์„  ์ œ์•ˆ
-            .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in
-                if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
-                    keyboardHeight = keyboardFrame.height
-                }
-            }
-            .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
-                withAnimation(.easeOut(duration: 0.25)) {
-                    keyboardHeight = 0
-                }
-            }
+            .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
+                guard
+                    let info = notification.userInfo,
+                    let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
+                    let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
+                else { return }
+                let screenHeight = UIScreen.main.bounds.height
+                let height = max(0, screenHeight - keyboardFrame.minY)
+                withAnimation(.easeOut(duration: duration)) {
+                    keyboardHeight = height
+                }
+            }
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
keyboardHeight = keyboardFrame.height
}
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
withAnimation(.easeOut(duration: 0.25)) {
keyboardHeight = 0
}
}
.onChange(of: keyboardHeight) { _, newValue in
if newValue > 0 {
withAnimation(.easeOut(duration: 0.25)) {
proxy.scrollTo("inputField", anchor: .center)
}
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
guard
let info = notification.userInfo,
let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
else { return }
let screenHeight = UIScreen.main.bounds.height
let height = max(0, screenHeight - keyboardFrame.minY)
withAnimation(.easeOut(duration: duration)) {
keyboardHeight = height
}
}
.onChange(of: keyboardHeight) { _, newValue in
if newValue > 0 {
withAnimation(.easeOut(duration: 0.25)) {
proxy.scrollTo("inputField", anchor: .center)
}
}
๐Ÿค– Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/TargetDdaySettingView.swift`
around lines 107 - 122, Replace the separate keyboardWillShow/keyboardWillHide
handlers with a single listener for
UIResponder.keyboardWillChangeFrameNotification; in that handler read
keyboardFrame from
notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] and the
animation duration from UIKeyboardAnimationDurationUserInfoKey, compute the
effective height by subtracting the view's safe area bottom inset (use your
geometry/proxy or a stored safeAreaInset variable) and set keyboardHeight inside
withAnimation(.easeOut(duration: duration)) so the change is synchronized; keep
the scrollTo("inputField", anchor: .center) call triggered when keyboardHeight >
0 but ensure it runs after the animated height change (same animation block or
DispatchQueue.main.asyncAfter matching duration) so the input field isn't
obscured.

}
.padding(.horizontal, 34.adjustedW)
}
.scrollDismissesKeyboard(.interactively)
}
}