From a931e6241d90beed1aae60297523eaba385e1c23 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 6 Mar 2026 00:49:06 +0900 Subject: [PATCH 1/6] =?UTF-8?q?ui:=20=EB=B3=BC=EB=93=9C=EC=B2=B4=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Common/Component/WebItemRow.swift | 1 - DevLog/UI/Home/HomeView.swift | 2 -- 2 files changed, 3 deletions(-) diff --git a/DevLog/UI/Common/Component/WebItemRow.swift b/DevLog/UI/Common/Component/WebItemRow.swift index a2dc07d1..b855728d 100644 --- a/DevLog/UI/Common/Component/WebItemRow.swift +++ b/DevLog/UI/Common/Component/WebItemRow.swift @@ -26,7 +26,6 @@ struct WebItemRow: View { VStack(alignment: .leading) { Text(item.title) .foregroundStyle(Color.primary) - .bold() .multilineTextAlignment(.leading) .lineLimit(2) Text(item.displayURL) diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index 2342e289..d5af8965 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -215,7 +215,6 @@ struct HomeView: View { } VStack(alignment: .leading) { Text(todo.title) - .bold() .foregroundStyle(Color.primary) Text(todo.dueDate? .formatted(date: .abbreviated, time: .omitted) ?? "마감일 없음" @@ -265,7 +264,6 @@ struct HomeView: View { .foregroundStyle(Color.primary) .font(.title2.bold()) Spacer() - } .listRowInsets(EdgeInsets()) } From 8a6a17626a7f1a7ea461acad078e044f1179364b Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 6 Mar 2026 08:37:21 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20iOS=2017=EC=97=90=EC=84=9C=20navigat?= =?UTF-8?q?ionbar=EA=B0=80=20=ED=88=AC=EB=AA=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=82=98=ED=83=80=EB=82=98=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UI/Common/NavigationBarConfigurator.swift | 31 +++++++++++++++++++ .../PushNotificationListView.swift | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/DevLog/UI/Common/NavigationBarConfigurator.swift b/DevLog/UI/Common/NavigationBarConfigurator.swift index 63fcd643..5831d208 100644 --- a/DevLog/UI/Common/NavigationBarConfigurator.swift +++ b/DevLog/UI/Common/NavigationBarConfigurator.swift @@ -7,11 +7,37 @@ import SwiftUI +/// NavigationBar의 배경색을 지정하고 shadowColor를 제거하는 구조체 +/// +/// 기본적으로 ``UIColor/systemBackground``를 배경색으로 사용하며, +/// 자체 `NavigationStack`을 가진 뷰에서는 `alwaysVisible`을 `true`로 설정하여 +/// 스크롤 위치와 관계없이 배경색이 항상 표시되도록 할 수 있다. struct NavigationBarConfigurator: UIViewControllerRepresentable { private let backgroundColor: UIColor + private let alwaysVisible: Bool + /// 지정된 배경색으로 Configurator를 생성한다. + /// + /// - Parameter backgroundColor: NavigationBar에 적용할 배경색. init(_ backgroundColor: UIColor = .systemBackground) { self.backgroundColor = backgroundColor + self.alwaysVisible = false + } + + /// 지정된 배경색과 상시 표시 옵션으로 Configurator를 생성한다. + /// + /// - Parameters: + /// - backgroundColor: NavigationBar에 적용할 배경색. + /// - alwaysVisible: `true`이면 스크롤 위치와 관계없이 배경색이 항상 표시된다. + /// 자체 `NavigationStack`을 가진 뷰에서 사용한다. + @available(iOS, deprecated: 18, message: "iOS 18 이상에서는 alwaysVisible 파라미터가 없는 생성자를 사용한다.") + init(_ backgroundColor: UIColor = .systemBackground, alwaysVisible: Bool) { + self.backgroundColor = backgroundColor + if #available(iOS 18.0, *) { + self.alwaysVisible = false + } else { + self.alwaysVisible = alwaysVisible + } } func makeCoordinator() -> Coordinator { @@ -31,6 +57,11 @@ struct NavigationBarConfigurator: UIViewControllerRepresentable { coordinator.originalShadowColor = navigationBar.standardAppearance.shadowColor coordinator.originalBackgroundColor = navigationBar.standardAppearance.backgroundColor } + if self.alwaysVisible, navigationBar.scrollEdgeAppearance == nil { + let appearance = UINavigationBarAppearance() + appearance.configureWithDefaultBackground() + navigationBar.scrollEdgeAppearance = appearance + } Self.applyAppearance( to: navigationBar, shadowColor: .clear, diff --git a/DevLog/UI/PushNotification/PushNotificationListView.swift b/DevLog/UI/PushNotification/PushNotificationListView.swift index 44f33554..efb8b3e7 100644 --- a/DevLog/UI/PushNotification/PushNotificationListView.swift +++ b/DevLog/UI/PushNotification/PushNotificationListView.swift @@ -60,7 +60,7 @@ struct PushNotificationListView: View { .listRowBackground(Color.clear) } .listStyle(.plain) - .background(NavigationBarConfigurator(.secondarySystemBackground)) + .background(NavigationBarConfigurator(.secondarySystemBackground, alwaysVisible: true)) .onScrollOffsetChange { offset in guard isScrollTrackingEnabled else { return } headerOffset = max(0, -offset) From 128d476b2b4ae53223b430081c5d48f3abd10adf Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 6 Mar 2026 09:02:15 +0900 Subject: [PATCH 3/6] =?UTF-8?q?style:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=B7=B0=20=EB=AA=A8=EB=94=94=ED=8C=8C=EC=9D=B4=EC=96=B4=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Home/TodoListView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DevLog/UI/Home/TodoListView.swift b/DevLog/UI/Home/TodoListView.swift index 3bf6049b..68921d1a 100644 --- a/DevLog/UI/Home/TodoListView.swift +++ b/DevLog/UI/Home/TodoListView.swift @@ -77,7 +77,6 @@ struct TodoListView: View { Label(viewModel.state.toastMessage, systemImage: "arrow.uturn.left") } .navigationTitle(viewModel.state.kind.localizedName) - .navigationBarTitleDisplayMode(.large) .fullScreenCover(isPresented: Binding( get: { viewModel.state.showEditor }, set: { viewModel.send(.setShowEditor($0)) } From 9920eaba55d4b2f0f618447f65a003bfb9ae168d Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 6 Mar 2026 09:06:42 +0900 Subject: [PATCH 4/6] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PushNotificationListView.swift | 124 +++++++++--------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/DevLog/UI/PushNotification/PushNotificationListView.swift b/DevLog/UI/PushNotification/PushNotificationListView.swift index efb8b3e7..75713fe8 100644 --- a/DevLog/UI/PushNotification/PushNotificationListView.swift +++ b/DevLog/UI/PushNotification/PushNotificationListView.swift @@ -18,71 +18,14 @@ struct PushNotificationListView: View { var body: some View { NavigationStack(path: $router.path) { - List { - Group { - if viewModel.state.notifications.isEmpty { - HStack { - Spacer() - Text("받은 알림이 없습니다.") - .foregroundStyle(Color.gray) - Spacer() - } - .listRowSeparator(.hidden) - } else { - let notifications = viewModel.state.notifications - ForEach(Array(zip(notifications.indices, notifications)), id: \.1.id) { idx, notification in - Button { - viewModel.send(.tapNotification(notification)) - } label: { - notificationRow(notification) - .padding(.vertical, 8) - } - .buttonStyle(.plain) - .onAppear { - let lastID = viewModel.state.notifications.last?.id - if notification.id == lastID, viewModel.state.hasMore { - viewModel.send(.loadNextPage) - } - } - .listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) - .overlay(alignment: .top) { - if #available(iOS 26.0, *) { - if idx == 0 { - Divider() - .padding(.horizontal, -16) - } - } - } - } - } - } - .listSectionSeparator(.hidden, edges: .top) - .listRowBackground(Color.clear) - } + notificationList .listStyle(.plain) .background(NavigationBarConfigurator(.secondarySystemBackground, alwaysVisible: true)) .onScrollOffsetChange { offset in guard isScrollTrackingEnabled else { return } headerOffset = max(0, -offset) } - .safeAreaInset(edge: .top) { - VStack(spacing: 4) { - headerView - .clipped() - if #unavailable(iOS 26) { - Divider() - .padding(.horizontal, -16) - } - } - .background { - if #available(iOS 26.0, *) { - Color.clear - } else { - Color(.secondarySystemBackground) - } - } - .offset(y: headerOffset) - } + .safeAreaInset(edge: .top) { safeAreaHeader } .background(Color(.secondarySystemBackground)) .onAppear { viewModel.send(.fetchNotifications) } .refreshable { viewModel.send(.fetchNotifications) } @@ -138,6 +81,69 @@ struct PushNotificationListView: View { } } + private var notificationList: some View { + List { + Group { + if viewModel.state.notifications.isEmpty { + HStack { + Spacer() + Text("받은 알림이 없습니다.") + .foregroundStyle(Color.gray) + Spacer() + } + .listRowSeparator(.hidden) + } else { + let notifications = viewModel.state.notifications + ForEach(Array(zip(notifications.indices, notifications)), id: \.1.id) { idx, notification in + Button { + viewModel.send(.tapNotification(notification)) + } label: { + notificationRow(notification) + .padding(.vertical, 8) + } + .buttonStyle(.plain) + .onAppear { + let lastID = viewModel.state.notifications.last?.id + if notification.id == lastID, viewModel.state.hasMore { + viewModel.send(.loadNextPage) + } + } + .listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) + .overlay(alignment: .top) { + if #available(iOS 26.0, *) { + if idx == 0 { + Divider() + .padding(.horizontal, -16) + } + } + } + } + } + } + .listSectionSeparator(.hidden, edges: .top) + .listRowBackground(Color.clear) + } + } + + private var safeAreaHeader: some View { + VStack(spacing: 4) { + headerView + .clipped() + if #unavailable(iOS 26) { + Divider() + .padding(.horizontal, -16) + } + } + .background { + if #available(iOS 26.0, *) { + Color.clear + } else { + Color(.secondarySystemBackground) + } + } + .offset(y: headerOffset) + } + private var headerView: some View { ScrollView(.horizontal) { HStack(spacing: 8) { From dc5b94cc234c97a6835882b18f9f018ea4dbe057 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 6 Mar 2026 09:40:35 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=ED=8A=B8=EB=9E=98=ED=82=B9=EC=9D=B4=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PushNotificationListView.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DevLog/UI/PushNotification/PushNotificationListView.swift b/DevLog/UI/PushNotification/PushNotificationListView.swift index 75713fe8..ae815a03 100644 --- a/DevLog/UI/PushNotification/PushNotificationListView.swift +++ b/DevLog/UI/PushNotification/PushNotificationListView.swift @@ -27,7 +27,14 @@ struct PushNotificationListView: View { } .safeAreaInset(edge: .top) { safeAreaHeader } .background(Color(.secondarySystemBackground)) - .onAppear { viewModel.send(.fetchNotifications) } + .onAppear { + viewModel.send(.fetchNotifications) + headerOffset = 0 + isScrollTrackingEnabled = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + isScrollTrackingEnabled = true + } + } .refreshable { viewModel.send(.fetchNotifications) } .navigationTitle("받은 푸시 알람") .alert( @@ -208,13 +215,6 @@ struct PushNotificationListView: View { .scrollIndicators(.never) .scrollDisabled(!isScrollTrackingEnabled) .contentMargins(.leading, 16, for: .scrollContent) - .onAppear { - headerOffset = 0 - isScrollTrackingEnabled = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - isScrollTrackingEnabled = true - } - } } private var filterBadge: some View { From 73ff725b5dd72f2d6d5d0a840ec5f46609b46281 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 6 Mar 2026 09:50:47 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20iOS=2017=EC=97=90=EC=84=9C=EB=8A=94?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A1=A4=EB=B7=B0=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=ED=95=98=EC=97=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PushNotificationListView.swift | 112 ++++++++++-------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/DevLog/UI/PushNotification/PushNotificationListView.swift b/DevLog/UI/PushNotification/PushNotificationListView.swift index ae815a03..faf04b82 100644 --- a/DevLog/UI/PushNotification/PushNotificationListView.swift +++ b/DevLog/UI/PushNotification/PushNotificationListView.swift @@ -152,69 +152,79 @@ struct PushNotificationListView: View { } private var headerView: some View { - ScrollView(.horizontal) { - HStack(spacing: 8) { - if 0 < viewModel.appliedFilterCount { - Menu { - Text("\(viewModel.appliedFilterCount)개 필터가 적용됨") - Button(role: .destructive) { - viewModel.send(.resetFilters) - } label: { - Text("모든 필터 지우기") - } - } label: { - HStack(spacing: 6) { - Image(systemName: "line.3.horizontal.decrease") - filterBadge - } - .adaptiveButtonStyle() - } - } - - Button { - viewModel.send(.toggleSortOption) - } label: { - let condition = viewModel.state.query.sortOrder == .oldest - Text("정렬: \(viewModel.state.query.sortOrder.title)") - .foregroundStyle(condition ? .white : Color(.label)) - .adaptiveButtonStyle(color: condition ? .blue : .clear) - } + Group { + if #available(iOS 18, *) { + ScrollView(.horizontal) { headerContent } + .scrollIndicators(.never) + .scrollDisabled(!isScrollTrackingEnabled) + .contentMargins(.leading, 16, for: .scrollContent) + } else { + headerContent + .padding(.leading, 16) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + } + private var headerContent: some View { + HStack(spacing: 8) { + if 0 < viewModel.appliedFilterCount { Menu { - Picker(selection: Binding( - get: { viewModel.state.query.timeFilter }, - set: { viewModel.send(.setTimeFilter($0)) } - )) { - ForEach(PushNotificationQuery.TimeFilter.availableOptions, id: \.self) { option in - Text(option.title).tag(option) - } + Text("\(viewModel.appliedFilterCount)개 필터가 적용됨") + Button(role: .destructive) { + viewModel.send(.resetFilters) } label: { - Text("기간") + Text("모든 필터 지우기") } } label: { - let condition = viewModel.state.query.timeFilter == .none - HStack { - Text("기간") - Image(systemName: "chevron.down") + HStack(spacing: 6) { + Image(systemName: "line.3.horizontal.decrease") + filterBadge } - .foregroundStyle(condition ? Color(.label) : .white) - .adaptiveButtonStyle(color: condition ? .clear : .blue) + .adaptiveButtonStyle() } + } - Button { - viewModel.send(.toggleUnreadOnly) + Button { + viewModel.send(.toggleSortOption) + } label: { + let condition = viewModel.state.query.sortOrder == .oldest + Text("정렬: \(viewModel.state.query.sortOrder.title)") + .foregroundStyle(condition ? .white : Color(.label)) + .adaptiveButtonStyle(color: condition ? .blue : .clear) + } + + Menu { + Picker(selection: Binding( + get: { viewModel.state.query.timeFilter }, + set: { viewModel.send(.setTimeFilter($0)) } + )) { + ForEach(PushNotificationQuery.TimeFilter.availableOptions, id: \.self) { option in + Text(option.title).tag(option) + } } label: { - let condition = viewModel.state.query.unreadOnly - Text("읽지 않음") - .foregroundStyle(condition ? .white : Color(.label)) - .adaptiveButtonStyle(color: condition ? .blue : .clear) + Text("기간") } + } label: { + let condition = viewModel.state.query.timeFilter == .none + HStack { + Text("기간") + Image(systemName: "chevron.down") + } + .foregroundStyle(condition ? Color(.label) : .white) + .adaptiveButtonStyle(color: condition ? .clear : .blue) + } + + Button { + viewModel.send(.toggleUnreadOnly) + } label: { + let condition = viewModel.state.query.unreadOnly + Text("읽지 않음") + .foregroundStyle(condition ? .white : Color(.label)) + .adaptiveButtonStyle(color: condition ? .blue : .clear) } - .frame(height: 36) } - .scrollIndicators(.never) - .scrollDisabled(!isScrollTrackingEnabled) - .contentMargins(.leading, 16, for: .scrollContent) + .frame(height: 36) } private var filterBadge: some View {