<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>초코모찌롤</title>
    <link>https://choco-mochi-roll.tistory.com/</link>
    <description>안녕하세요.
앱 개발을 공부하며
실무에서 활용할 수 있는 내용을 기록하고 공유하는 기술 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 27 May 2026 20:48:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>초코모찌롤</managingEditor>
    <image>
      <title>초코모찌롤</title>
      <url>https://tistory1.daumcdn.net/tistory/8591965/attach/1313bffcf6bf46f592e7b2663b4554df</url>
      <link>https://choco-mochi-roll.tistory.com</link>
    </image>
    <item>
      <title>아이폰 배터리 빨리 닳는 이유 7가지 (+ 바로 해결 방법)</title>
      <link>https://choco-mochi-roll.tistory.com/67</link>
      <description>&lt;h1&gt;아이폰 배터리 빨리 닳는 이유 7가지 (+ 바로 해결 방법)&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;배터리 잔량 14% 화면.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kQpML/dJMb99TwK1x/SK4k9uFA7NNNoqzjFVHiBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kQpML/dJMb99TwK1x/SK4k9uFA7NNNoqzjFVHiBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kQpML/dJMb99TwK1x/SK4k9uFA7NNNoqzjFVHiBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkQpML%2FdJMb99TwK1x%2FSK4k9uFA7NNNoqzjFVHiBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;450&quot; data-filename=&quot;배터리 잔량 14% 화면.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 아이폰 배터리가 눈에 띄게 빨리 줄어드는 느낌을 받은 적이 있다면, 단순한 기분 탓이 아닐 수 있습니다. 특히 iOS 업데이트 이후나 특정 앱을 설치한 뒤 배터리 소모가 급격히 늘어나는 경우가 많습니다. 이 글에서는 아이폰 배터리가 빨리 닳는 대표적인 원인과, 바로 적용 가능한 해결 방법을 실제 사용 기준으로 정리했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이폰 배터리가 빨리 닳는 주요 원인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. iOS 업데이트 이후 최적화 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 업데이트 직후에는 내부적으로 인덱싱, 사진 정리, 백그라운드 작업이 실행되면서 배터리 사용량이 증가할 수 있습니다. 보통 1~3일 정도 지나면 정상화되지만, 그 기간 동안은 평소보다 배터리가 빨리 닳는 것이 자연스러운 현상입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 백그라운드 앱 새로고침&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하지 않는 앱이 백그라운드에서 계속 데이터를 가져오면 배터리가 소모됩니다. 특히 SNS, 뉴스, 쇼핑 앱은 지속적으로 서버와 통신하기 때문에 영향을 크게 줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 위치 서비스 과다 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도, 배달, 날씨 앱 등이 위치 정보를 계속 요청하면 GPS가 활성화되면서 배터리 소모가 증가합니다. 필요하지 않은 앱까지 위치 권한이 허용된 경우가 많습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 화면 밝기 및 디스플레이 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밝기가 높을수록 배터리 소모는 증가합니다. 특히 자동 밝기가 꺼져 있는 경우 실내에서도 높은 밝기로 유지되어 불필요한 전력 소비가 발생합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 배터리 성능 저하&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배터리는 소모품이기 때문에 일정 시간이 지나면 성능이 떨어집니다. 설정에서 배터리 성능 상태를 확인했을 때 최대 용량이 80% 이하라면 체감 속도가 크게 달라질 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 특정 앱의 비정상적인 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 앱은 오류나 업데이트 문제로 배터리를 과도하게 사용하는 경우가 있습니다. 특히 영상 스트리밍, 게임, SNS 앱에서 자주 발생합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 네트워크 환경 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신호가 약한 지역에서는 아이폰이 계속해서 신호를 잡으려고 하면서 배터리를 더 많이 사용합니다. LTE나 5G 환경이 불안정할수록 소모가 커집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이폰 배터리 빨리 닳을 때 해결 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 백그라운드 앱 새로고침 끄기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 &amp;rarr; 일반 &amp;rarr; 백그라운드 앱 새로고침에서 필요 없는 앱을 비활성화하면 즉각적인 효과를 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 위치 서비스 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 &amp;rarr; 개인정보 보호 &amp;rarr; 위치 서비스에서 사용하지 않는 앱은 &amp;ldquo;사용 안 함&amp;rdquo; 또는 &amp;ldquo;앱 사용 중에만 허용&amp;rdquo;으로 변경하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 화면 밝기 자동 설정 활성화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 &amp;rarr; 손쉬운 사용 &amp;rarr; 디스플레이 및 텍스트 크기에서 자동 밝기를 켜면 상황에 맞게 밝기가 조절되어 배터리를 절약할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 배터리 사용량 확인하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 &amp;rarr; 배터리에서 어떤 앱이 배터리를 많이 사용하는지 확인할 수 있습니다. 특정 앱이 비정상적으로 높다면 업데이트하거나 삭제를 고려해볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 저전력 모드 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저전력 모드를 켜면 백그라운드 작업, 시각 효과 등이 제한되면서 배터리 사용량이 줄어듭니다. 급할 때 매우 효과적인 방법입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 네트워크 설정 재설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 &amp;rarr; 일반 &amp;rarr; 전송 또는 iPhone 재설정 &amp;rarr; 네트워크 설정 재설정을 통해 불필요한 연결 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 필요 없는 위젯 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 화면에 추가된 위젯도 지속적으로 데이터를 업데이트하기 때문에 배터리에 영향을 줄 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;절대 하면 안 되는 설정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 모든 앱에 위치 서비스 허용&lt;/li&gt;
&lt;li&gt;밝기 100% 고정 사용&lt;/li&gt;
&lt;li&gt;충전하면서 고사양 게임 실행&lt;/li&gt;
&lt;li&gt;불필요한 앱을 계속 백그라운드 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배터리 문제 체크리스트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 업데이트 후 2~3일 지났는가&lt;/li&gt;
&lt;li&gt;배터리 성능 상태가 80% 이상인가&lt;/li&gt;
&lt;li&gt;특정 앱이 과도하게 배터리를 사용하지 않는가&lt;/li&gt;
&lt;li&gt;위치 서비스가 과도하게 켜져 있지 않은가&lt;/li&gt;
&lt;li&gt;백그라운드 앱 새로고침이 필요한 앱만 켜져 있는가&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이폰 배터리가 빨리 닳는 문제는 대부분 설정이나 사용 습관에서 비롯됩니다. 위에서 정리한 방법을 하나씩 적용해보면 체감할 수 있을 정도로 배터리 사용 시간이 늘어나는 것을 확인할 수 있습니다.&lt;/p&gt;</description>
      <category>IT</category>
      <category>ios</category>
      <category>iphone</category>
      <category>발열</category>
      <category>배터리</category>
      <category>아이폰</category>
      <category>효율적인아이폰사용</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/67</guid>
      <comments>https://choco-mochi-roll.tistory.com/67#entry67comment</comments>
      <pubDate>Sun, 12 Apr 2026 14:19:31 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI 전체 앱 구조 설계 (Router + Tab + Navigation 통합 아키텍처)</title>
      <link>https://choco-mochi-roll.tistory.com/66</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI 전체 앱 구조 설계 (Router + Tab + Navigation 통합 아키텍처)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI에서 Router, TabView, NavigationStack을 통합하여 실무에서 사용하는 앱 아키텍처를 설계하는 방법을 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI 전체 앱 구조 설계 (Router + Tab + Navigation 통합 아키텍처)&lt;/h1&gt;

&lt;p&gt;
SwiftUI를 어느 정도 사용하다 보면
단순 View 구현을 넘어 &lt;strong&gt;앱 전체 구조 설계&lt;/strong&gt;가 필요해집니다.
&lt;/p&gt;

&lt;p&gt;
특히 아래 요소들이 동시에 등장하면 구조가 복잡해집니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NavigationStack&lt;/li&gt;
&lt;li&gt;TabView&lt;/li&gt;
&lt;li&gt;딥링크 / 푸시&lt;/li&gt;
&lt;li&gt;WebView 연동&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 이 모든 것을 통합하는 &lt;strong&gt;실무 아키텍처&lt;/strong&gt;를 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. 핵심 개념&lt;/h2&gt;

&lt;p&gt;
앱 구조의 핵심은 단 하나입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;모든 화면 이동을 &quot;데이터(Route)&quot;로 관리한다&lt;/strong&gt;
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. Route 정의&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
enum Route: Hashable {
    case home
    case detail(id: Int)
    case web(url: URL)
    case setting
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  모든 화면을 하나의 enum으로 관리
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;3. Router 설계&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
class Router: ObservableObject {
    @Published var path: [Route] = []
    @Published var selectedTab: Tab = .home

    func push(_ route: Route) {
        path.append(route)
    }

    func pop() {
        path.removeLast()
    }

    func popToRoot() {
        path.removeAll()
    }

    func switchTab(_ tab: Tab) {
        selectedTab = tab
        path.removeAll()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;4. Tab 정의&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
enum Tab {
    case home
    case profile
    case setting
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  탭도 상태로 관리
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;5. Root 구조&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
struct RootView: View {
    @StateObject private var router = Router()

    var body: some View {
        NavigationStack(path: $router.path) {

            TabView(selection: $router.selectedTab) {

                HomeView()
                    .tag(Tab.home)

                ProfileView()
                    .tag(Tab.profile)

                SettingView()
                    .tag(Tab.setting)
            }

            .navigationDestination(for: Route.self) { route in
                switch route {
                case .detail(let id):
                    DetailView(id: id)
                case .web(let url):
                    WebView(url: url)
                default:
                    EmptyView()
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  Navigation + Tab 통합
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;6. 딥링크 / 푸시 처리&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
func handleDeepLink(_ url: URL) {
    if url.path.contains(&quot;detail&quot;) {
        router.push(.detail(id: 1))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
func handlePush(_ data: [String: Any]) {
    if let urlString = data[&quot;url&quot;] as? String,
       let url = URL(string: urlString) {
        router.push(.web(url: url))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  외부 이벤트 → Route 변환
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. WebView 연동&lt;/h2&gt;

&lt;p&gt;
Web → App 이동도 동일한 구조를 사용합니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
router.push(.detail(id: 10))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  모든 흐름 통일
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;8. 구조 흐름 정리&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;사용자 이벤트 발생&lt;/li&gt;
&lt;li&gt;Route 생성&lt;/li&gt;
&lt;li&gt;Router.push()&lt;/li&gt;
&lt;li&gt;NavigationStack 반영&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Navigation과 Tab을 따로 관리&lt;/li&gt;
&lt;li&gt;View에서 직접 navigation 처리&lt;/li&gt;
&lt;li&gt;딥링크 로직 분산&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 실무 추천 구조&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Router 단일화&lt;/li&gt;
&lt;li&gt;Route enum 기반 설계&lt;/li&gt;
&lt;li&gt;모든 이동을 Router로 통제&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;11. 결론&lt;/h2&gt;

&lt;p&gt;
SwiftUI 앱 구조의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;UI가 아니라 상태로 앱을 설계하라&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigation → 상태&lt;/li&gt;
&lt;li&gt;Tab → 상태&lt;/li&gt;
&lt;li&gt;딥링크 → 상태&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 구조를 만들면 복잡한 앱도 안정적으로 유지할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI + TCA 구조 설계 (실무 아키텍처 확장)&lt;/strong&gt;를 다루면
고급 개발자 수준으로 확장할 수 있습니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/66</guid>
      <comments>https://choco-mochi-roll.tistory.com/66#entry66comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:10:05 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI NavigationBar 커스터마이징 완벽 가이드 (iOS 26 Liquid 제거 포함)</title>
      <link>https://choco-mochi-roll.tistory.com/65</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI NavigationBar 커스터마이징 완벽 가이드 (iOS 26 Liquid 제거 포함)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI NavigationBar를 커스터마이징하는 방법부터 iOS 26 Liquid 효과 제거, 커스텀 네비게이션 구조까지 실무 기준으로 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI NavigationBar 커스터마이징 완벽 가이드 (iOS 26 Liquid 제거 포함)&lt;/h1&gt;

&lt;p&gt;
SwiftUI NavigationBar는 기본 제공 기능만으로는
실무 요구사항을 만족시키기 어렵습니다.
&lt;/p&gt;

&lt;p&gt;
특히 최근 iOS 26에서는 &lt;strong&gt;Liquid 스타일&lt;/strong&gt;이 적용되면서
디자인 제어가 더욱 까다로워졌습니다.
&lt;/p&gt;

&lt;p&gt;
이 글에서는 NavigationBar를 완전히 제어하는 방법을 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. 기본 NavigationStack 구조&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
NavigationStack {
    Text(&quot;Hello&quot;)
        .navigationTitle(&quot;Title&quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  기본 구조는 간단하지만 커스터마이징 한계 존재
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. NavigationBar 숨기기&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
.navigationBarHidden(true)
&lt;/code&gt;&lt;/pre&gt;

또는 (iOS 16+)

&lt;pre&gt;&lt;code&gt;
.toolbar(.hidden, for: .navigationBar)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  커스텀 네비게이션을 만들기 위한 첫 단계
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;3. iOS 26 Liquid 효과 제거&lt;/h2&gt;

&lt;p&gt;
iOS 26에서는 NavigationBar가 반투명 / Liquid 스타일로 변경되었습니다.
&lt;/p&gt;

&lt;h3&gt;해결 방법 (UIKit Appearance)&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
appearance.shadowColor = .clear

UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  완전히 불투명 스타일로 변경
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;4. 커스텀 NavigationBar 만들기&lt;/h2&gt;

&lt;p&gt;
실무에서는 NavigationBar를 직접 구현하는 경우가 많습니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
VStack(spacing: 0) {

    HStack {
        Button(action: {
        }) {
            Image(systemName: &quot;chevron.left&quot;)
        }

        Spacer()

        Text(&quot;Title&quot;)

        Spacer()

        Image(systemName: &quot;bell&quot;)
    }
    .padding()
    .background(Color.white)

    Divider()

    contentView
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  완전한 UI 제어 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;5. SafeArea 대응&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
.safeAreaInset(edge: .top) {
    CustomNavigationBar()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  상단 고정 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;6. 뒤로가기 버튼 커스터마이징&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
.toolbar {
    ToolbarItem(placement: .navigationBarLeading) {
        Button(action: {
        }) {
            Image(systemName: &quot;chevron.left&quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  기본 버튼 대체 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. 타이틀 정렬 문제&lt;/h2&gt;

&lt;p&gt;
SwiftUI 기본 NavigationTitle은 중앙 정렬입니다.
&lt;/p&gt;

&lt;h3&gt;해결&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;커스텀 NavigationBar 사용&lt;/li&gt;
&lt;li&gt;HStack으로 직접 구성&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;8. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;기본 NavigationBar에 억지 커스터마이징&lt;/li&gt;
&lt;li&gt;SafeArea 고려 없이 overlay 사용&lt;/li&gt;
&lt;li&gt;NavigationStack과 상태 분리 안됨&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무 추천 구조&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NavigationStack + Custom NavigationBar&lt;/li&gt;
&lt;li&gt;Router 기반 화면 관리&lt;/li&gt;
&lt;li&gt;SafeAreaInset으로 위치 제어&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 결론&lt;/h2&gt;

&lt;p&gt;
NavigationBar의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;기본을 버리고 구조를 가져가라&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;기본 NavigationBar → 제한적&lt;/li&gt;
&lt;li&gt;Custom NavigationBar → 완전 제어&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
특히 iOS 26 이후에는 커스텀 구조가 사실상 필수입니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI 전체 앱 구조 설계 (Router + Tab + Navigation 통합)&lt;/strong&gt;를 다루면
실무 아키텍처까지 완성됩니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/65</guid>
      <comments>https://choco-mochi-roll.tistory.com/65#entry65comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:08:56 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI TabView 커스터마이징 완벽 가이드 (높이, 디자인, 상태 유지)</title>
      <link>https://choco-mochi-roll.tistory.com/64</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI TabView 커스터마이징 완벽 가이드 (높이, 디자인, 상태 유지)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI TabView를 커스터마이징하는 방법부터 높이 조절, 디자인 변경, 상태 유지까지 실무에서 사용하는 구조를 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI TabView 커스터마이징 완벽 가이드 (높이, 디자인, 상태 유지)&lt;/h1&gt;

&lt;p&gt;
SwiftUI에서 TabView는 간단하게 사용할 수 있지만,
실무에서는 거의 반드시 &lt;strong&gt;커스터마이징&lt;/strong&gt;이 필요합니다.
&lt;/p&gt;

&lt;p&gt;
특히 아래 문제를 많이 겪게 됩니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TabBar 높이 변경이 안됨&lt;/li&gt;
&lt;li&gt;디자인 커스터마이징 어려움&lt;/li&gt;
&lt;li&gt;탭 전환 시 상태 초기화&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 TabView를 실무에서 사용하는 방식으로 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. 기본 TabView 구조&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
TabView {
    Text(&quot;Home&quot;)
        .tabItem {
            Label(&quot;Home&quot;, systemImage: &quot;house&quot;)
        }

    Text(&quot;Profile&quot;)
        .tabItem {
            Label(&quot;Profile&quot;, systemImage: &quot;person&quot;)
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  기본은 간단하지만 커스터마이징 한계가 있음
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. Tab 선택 상태 관리&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
@State private var selectedTab = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
TabView(selection: $selectedTab) {
    Text(&quot;Home&quot;)
        .tag(0)

    Text(&quot;Profile&quot;)
        .tag(1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  상태 기반 제어 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;3. 탭 전환 시 상태 유지&lt;/h2&gt;

&lt;p&gt;
문제:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;탭 이동 시 View 초기화&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결 방법&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;View를 분리&lt;/li&gt;
&lt;li&gt;@StateObject 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code&gt;
struct HomeView: View {
    @StateObject private var vm = ViewModel()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  상태 유지 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;4. TabBar 디자인 커스터마이징&lt;/h2&gt;

&lt;p&gt;
기본 TabView는 제한이 많기 때문에
실무에서는 커스텀 TabBar를 사용합니다.
&lt;/p&gt;

&lt;h3&gt;구조&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
VStack(spacing: 0) {
    contentView

    HStack {
        Button(&quot;Home&quot;) { selectedTab = 0 }
        Button(&quot;Profile&quot;) { selectedTab = 1 }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  완전한 커스터마이징 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;5. 높이 변경 방법&lt;/h2&gt;

&lt;p&gt;
기본 TabView는 높이 변경이 어렵습니다.
&lt;/p&gt;

&lt;h3&gt;해결&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;커스텀 TabBar 사용&lt;/li&gt;
&lt;li&gt;safeAreaInset 활용&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code&gt;
.safeAreaInset(edge: .bottom) {
    CustomTabBar()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;6. 가운데 버튼 (FAB) 구현&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
ZStack {
    TabView { ... }

    VStack {
        Spacer()
        Button(&quot;+&quot;) {
        }
        .offset(y: -20)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  Floating Button 구현
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. UIKit Appearance 커스터마이징&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
UITabBar.appearance().backgroundColor = UIColor.white
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  기본 스타일 변경 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;8. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;기본 TabView로 모든 것을 해결하려고 함&lt;/li&gt;
&lt;li&gt;상태 관리 없이 View 생성&lt;/li&gt;
&lt;li&gt;Navigation과 혼합 구조 꼬임&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무 추천 구조&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NavigationStack + TabView 분리&lt;/li&gt;
&lt;li&gt;Router 기반 관리&lt;/li&gt;
&lt;li&gt;Custom TabBar 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 결론&lt;/h2&gt;

&lt;p&gt;
SwiftUI TabView의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;기본은 간단, 실무는 커스텀&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;상태 관리 필수&lt;/li&gt;
&lt;li&gt;디자인은 커스텀&lt;/li&gt;
&lt;li&gt;구조는 분리&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 구조를 이해하면 복잡한 탭 UI도 안정적으로 구현할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI NavigationBar 커스터마이징 (iOS 26 대응 포함)&lt;/strong&gt;을 다루면
UI 구조가 완전히 완성됩니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/64</guid>
      <comments>https://choco-mochi-roll.tistory.com/64#entry64comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:08:05 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI 키보드 대응 완벽 가이드 (입력창 가림, 자동 스크롤 문제 해결)</title>
      <link>https://choco-mochi-roll.tistory.com/63</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI 키보드 대응 완벽 가이드 (입력창 가림, 자동 스크롤 문제 해결)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI에서 키보드 올라올 때 입력창이 가려지는 문제, 자동 스크롤, safeArea와의 충돌까지 실무에서 바로 적용 가능한 해결 방법을 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI 키보드 대응 완벽 가이드 (입력창 가림, 자동 스크롤 문제 해결)&lt;/h1&gt;

&lt;p&gt;
SwiftUI에서 폼이나 채팅 UI를 만들다 보면
반드시 겪는 문제가 있습니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;키보드 올라오면 입력창 가림&lt;/li&gt;
&lt;li&gt;ScrollView가 자동으로 안 올라감&lt;/li&gt;
&lt;li&gt;SafeArea랑 충돌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 &lt;strong&gt;실무에서 바로 쓰는 키보드 대응 방법&lt;/strong&gt;을 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. 왜 키보드에 가려질까?&lt;/h2&gt;

&lt;p&gt;
SwiftUI는 기본적으로 키보드를 고려한 레이아웃 조정을 자동으로 해주지 않습니다.
&lt;/p&gt;

&lt;p&gt;
즉, 키보드가 올라와도 View는 그대로 유지됩니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. 기본 해결: safeAreaInset 활용&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
.safeAreaInset(edge: .bottom) {
    TextField(&quot;입력&quot;, text: $text)
        .padding()
        .background(Color.white)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  키보드 위에 입력창을 고정할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;3. ScrollView 자동 스크롤&lt;/h2&gt;

&lt;p&gt;
입력 시 하단으로 자동 이동하는 구조입니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
ScrollViewReader { proxy in
    ScrollView {
        LazyVStack {
            ForEach(messages) { message in
                Text(message.text)
                    .id(message.id)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  특정 위치로 이동 가능
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
proxy.scrollTo(messages.last?.id)
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;4. 키보드 높이 감지&lt;/h2&gt;

&lt;p&gt;
UIKit Notification을 활용합니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
NotificationCenter.default.addObserver(
    forName: UIResponder.keyboardWillShowNotification,
    object: nil,
    queue: .main
) { notification in
    if let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
        keyboardHeight = frame.height
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  직접 offset 조정 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;5. 입력창 위치 고정 전략&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
VStack {
    ScrollView { ... }

    TextField(&quot;입력&quot;, text: $text)
        .padding()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  단순하지만 많이 깨짐
&lt;/p&gt;

&lt;h3&gt;추천 구조&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;safeAreaInset 사용&lt;/li&gt;
&lt;li&gt;overlay 활용&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;6. dismiss keyboard&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
@FocusState private var isFocused: Bool
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
.isFocused($isFocused)
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
isFocused = false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  키보드 내리기
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ScrollView + TextField 단순 조합&lt;/li&gt;
&lt;li&gt;SafeArea 무시&lt;/li&gt;
&lt;li&gt;키보드 높이 직접 계산 안함&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;8. 실무 추천 구조 (채팅 UI 기준)&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ScrollView + LazyVStack&lt;/li&gt;
&lt;li&gt;ScrollViewReader로 위치 제어&lt;/li&gt;
&lt;li&gt;safeAreaInset으로 입력창 고정&lt;/li&gt;
&lt;li&gt;FocusState로 키보드 관리&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;9. 결론&lt;/h2&gt;

&lt;p&gt;
SwiftUI 키보드 대응 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;레이아웃을 바꾸지 말고 위치를 제어하라&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safeAreaInset → 고정&lt;/li&gt;
&lt;li&gt;ScrollViewReader → 이동&lt;/li&gt;
&lt;li&gt;FocusState → 제어&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 구조를 이해하면 대부분의 입력 UI 문제를 해결할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI TabView 커스터마이징 (높이, 디자인, 상태 유지)&lt;/strong&gt;를 다루면
앱 구조 설계까지 완성됩니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/63</guid>
      <comments>https://choco-mochi-roll.tistory.com/63#entry63comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:07:21 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI 애니메이션 실무 가이드 (버벅임 없이 자연스럽게 구현하는 방법)</title>
      <link>https://choco-mochi-roll.tistory.com/62</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI 애니메이션 실무 가이드 (버벅임 없이 자연스럽게 구현하는 방법)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI 애니메이션이 버벅이는 이유와 해결 방법, withAnimation, transition, matchedGeometryEffect까지 실무 기준으로 완벽 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI 애니메이션 실무 가이드 (버벅임 없이 자연스럽게 구현하는 방법)&lt;/h1&gt;

&lt;p&gt;
SwiftUI 애니메이션은 간단해 보이지만,
실제로는 &lt;strong&gt;버벅임, 끊김, 의도하지 않은 동작&lt;/strong&gt;이 자주 발생합니다.
&lt;/p&gt;

&lt;p&gt;
특히 아래 문제는 실무에서 많이 겪게 됩니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;애니메이션이 안 먹는다&lt;/li&gt;
&lt;li&gt;전체 View가 같이 움직인다&lt;/li&gt;
&lt;li&gt;스크롤과 함께 끊긴다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 &lt;strong&gt;실무에서 바로 적용 가능한 애니메이션 전략&lt;/strong&gt;을 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. SwiftUI 애니메이션 기본 구조&lt;/h2&gt;

&lt;p&gt;
SwiftUI 애니메이션은 상태 변화에 따라 자동으로 발생합니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
withAnimation {
    isExpanded.toggle()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  상태 변경 → 애니메이션 적용
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. withAnimation vs animation modifier&lt;/h2&gt;

&lt;h3&gt;withAnimation&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
withAnimation {
    state = true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;명시적 애니메이션&lt;/li&gt;
&lt;li&gt;해당 블록에만 적용&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;animation modifier&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
.animation(.easeInOut, value: state)
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;자동 애니메이션&lt;/li&gt;
&lt;li&gt;특정 값 변화에 반응&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;3. 애니메이션이 안 먹는 이유&lt;/h2&gt;

&lt;p&gt;
대표적인 원인:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State 변화 없음&lt;/li&gt;
&lt;li&gt;value 설정 누락&lt;/li&gt;
&lt;li&gt;Layout이 변경되지 않음&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;4. transition (View 등장/사라짐)&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
if isVisible {
    Text(&quot;Hello&quot;)
        .transition(.opacity)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
.withAnimation {
    isVisible.toggle()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  등장/퇴장 애니메이션
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;5. matchedGeometryEffect&lt;/h2&gt;

&lt;p&gt;
두 View 사이 자연스러운 이동을 구현합니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
@Namespace private var namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
RoundedRectangle(cornerRadius: 10)
    .matchedGeometryEffect(id: &quot;box&quot;, in: namespace)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  카드 이동, 탭 전환 등에 사용
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;6. 성능 좋은 애니메이션 패턴&lt;/h2&gt;

&lt;p&gt;
애니메이션 성능은 어떤 속성을 바꾸느냐에 따라 달라집니다.
&lt;/p&gt;

&lt;h3&gt;추천&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;opacity&lt;/li&gt;
&lt;li&gt;scaleEffect&lt;/li&gt;
&lt;li&gt;offset&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;주의&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;frame 변경&lt;/li&gt;
&lt;li&gt;layout 변화&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Layout이 바뀌면 비용 증가
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. 버벅임 해결 방법&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;View 분리&lt;/li&gt;
&lt;li&gt;animation 범위 최소화&lt;/li&gt;
&lt;li&gt;LazyStack 사용&lt;/li&gt;
&lt;li&gt;Heavy View 제거&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;8. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;전체 View에 animation 적용&lt;/li&gt;
&lt;li&gt;State 과도 사용&lt;/li&gt;
&lt;li&gt;ScrollView와 함께 무거운 애니메이션 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무 추천 구조&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;애니메이션 대상 View 분리&lt;/li&gt;
&lt;li&gt;withAnimation 최소 범위 적용&lt;/li&gt;
&lt;li&gt;Layout 변경 최소화&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 결론&lt;/h2&gt;

&lt;p&gt;
SwiftUI 애니메이션의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;상태 변화 + 최소한의 Layout 변경&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;가벼운 속성 사용&lt;/li&gt;
&lt;li&gt;불필요한 재렌더링 제거&lt;/li&gt;
&lt;li&gt;명확한 animation 범위 설정&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 원칙만 지키면 자연스럽고 부드러운 애니메이션을 구현할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI 키보드 대응 (입력창 가림, 자동 스크롤 문제 해결)&lt;/strong&gt;을 다루면
실무 UI 완성도가 더욱 올라갑니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/62</guid>
      <comments>https://choco-mochi-roll.tistory.com/62#entry62comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:05:31 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI 성능 문제 실전 케이스 (List, Navigation, WebView 최적화)</title>
      <link>https://choco-mochi-roll.tistory.com/61</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI 성능 문제 실전 케이스 (List, Navigation, WebView 최적화)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI에서 실제로 발생하는 성능 문제(List, NavigationStack, WKWebView)를 케이스별로 분석하고 해결 방법을 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI 성능 문제 실전 케이스 (List, Navigation, WebView 최적화)&lt;/h1&gt;

&lt;p&gt;
SwiftUI는 생산성이 높지만,
조금만 구조가 복잡해지면 &lt;strong&gt;성능 문제가 바로 드러납니다.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
특히 실무에서는 다음 3가지에서 문제가 많이 발생합니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List / Scroll 성능&lt;/li&gt;
&lt;li&gt;NavigationStack 상태 꼬임&lt;/li&gt;
&lt;li&gt;WKWebView 렌더링 문제&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 실제로 많이 발생하는 케이스와 해결 방법을 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. Case 1: List 스크롤 끊김&lt;/h2&gt;

&lt;h3&gt;문제&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;스크롤 시 버벅임&lt;/li&gt;
&lt;li&gt;프레임 드랍&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;원인&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Heavy View 포함&lt;/li&gt;
&lt;li&gt;이미지 로딩 과다&lt;/li&gt;
&lt;li&gt;불필요한 재렌더링&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;잘못된 코드&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
List(items) { item in
    HeavyView(item: item)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;해결 방법&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
struct ItemRow: View {
    let item: Item

    var body: some View {
        Text(item.title)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
List(items) { item in
    ItemRow(item: item)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;View 분리&lt;/li&gt;
&lt;li&gt;이미지 캐싱 사용&lt;/li&gt;
&lt;li&gt;필요한 부분만 렌더링&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;2. Case 2: NavigationStack push 지연&lt;/h2&gt;

&lt;h3&gt;문제&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;화면 전환 시 딜레이&lt;/li&gt;
&lt;li&gt;push 시 끊김&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;원인&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Destination View가 무거움&lt;/li&gt;
&lt;li&gt;onAppear에서 API 호출&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결 방법&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
.navigationDestination(for: Route.self) { route in
    switch route {
    case .detail(let id):
        DetailView(id: id)
            .task {
                await loadData()
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;View 생성 최소화&lt;/li&gt;
&lt;li&gt;데이터 로딩 지연 처리&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;3. Case 3: WebView 로딩 지연&lt;/h2&gt;

&lt;h3&gt;문제&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;WebView 진입 시 느림&lt;/li&gt;
&lt;li&gt;깜빡임 발생&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;원인&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;매번 새 WKWebView 생성&lt;/li&gt;
&lt;li&gt;쿠키 재설정&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결 방법&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
class WebViewStore: ObservableObject {
    let webView = WKWebView()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
@StateObject private var store = WebViewStore()
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;WebView 재사용&lt;/li&gt;
&lt;li&gt;쿠키 한 번만 설정&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;4. Case 4: body 과도 호출&lt;/h2&gt;

&lt;h3&gt;문제&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;View가 계속 다시 그려짐&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;원인&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;State 범위 과다&lt;/li&gt;
&lt;li&gt;불필요한 ObservableObject&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결 방법&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;State 최소화&lt;/li&gt;
&lt;li&gt;View 분리&lt;/li&gt;
&lt;li&gt;EquatableView 활용&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;5. Case 5: ScrollView 메모리 증가&lt;/h2&gt;

&lt;h3&gt;문제&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;메모리 계속 증가&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;원인&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LazyStack 미사용&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            ItemView(item: item)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;6. 성능 최적화 핵심 정리&lt;/h2&gt;

&lt;table border=&quot;1&quot; cellpadding=&quot;10&quot; cellspacing=&quot;0&quot;&gt;
&lt;tr&gt;
&lt;th&gt;문제&lt;/th&gt;
&lt;th&gt;해결&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List 렉&lt;/td&gt;
&lt;td&gt;View 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation 지연&lt;/td&gt;
&lt;td&gt;lazy 로딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebView 느림&lt;/td&gt;
&lt;td&gt;재사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;body 호출 과다&lt;/td&gt;
&lt;td&gt;State 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 증가&lt;/td&gt;
&lt;td&gt;LazyStack&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;hr&gt;

&lt;h2&gt;7. 실무 체크리스트&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;LazyStack 사용 여부&lt;/li&gt;
&lt;li&gt;State 범위 최소화&lt;/li&gt;
&lt;li&gt;WebView 재사용&lt;/li&gt;
&lt;li&gt;이미지 캐싱&lt;/li&gt;
&lt;li&gt;View 분리&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;8. 결론&lt;/h2&gt;

&lt;p&gt;
SwiftUI 성능의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;불필요한 View 생성과 Layout 계산을 줄이는 것&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State → 최소화&lt;/li&gt;
&lt;li&gt;View → 분리&lt;/li&gt;
&lt;li&gt;Layout → 단순화&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 원칙만 지켜도 대부분의 성능 문제를 해결할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI 애니메이션 실무 (버벅임 없이 구현하는 방법)&lt;/strong&gt;을 다루면
UI 완성도가 크게 올라갑니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/61</guid>
      <comments>https://choco-mochi-roll.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:04:28 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI Navigation + WebView 연동 완벽 가이드 (딥링크, 푸시, 라우팅까지)</title>
      <link>https://choco-mochi-roll.tistory.com/60</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI Navigation + WebView 연동 완벽 가이드 (딥링크, 푸시, 라우팅까지)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI NavigationStack과 WKWebView를 연동하여 딥링크, 푸시 알림, 웹-앱 라우팅까지 실무에서 사용하는 구조를 완벽 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI Navigation + WebView 연동 완벽 가이드 (딥링크, 푸시, 라우팅까지)&lt;/h1&gt;

&lt;p&gt;
실무에서 가장 복잡한 구조 중 하나는 
&lt;strong&gt;Navigation + WebView + 딥링크&lt;/strong&gt; 조합입니다.
&lt;/p&gt;

&lt;p&gt;
특히 아래 상황에서 문제가 발생합니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;푸시 클릭 시 특정 WebView 화면 이동&lt;/li&gt;
&lt;li&gt;웹에서 특정 앱 화면으로 이동&lt;/li&gt;
&lt;li&gt;NavigationStack과 WebView 상태 충돌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 이 구조를 안정적으로 구현하는 방법을 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. 전체 구조 이해&lt;/h2&gt;

&lt;p&gt;
핵심 구조는 다음과 같습니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Router (Navigation 관리)&lt;/li&gt;
&lt;li&gt;WebView (웹 화면)&lt;/li&gt;
&lt;li&gt;DeepLink Handler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  모든 이동은 Router를 통해 처리
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. Route 정의&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
enum Route: Hashable {
    case home
    case web(url: URL)
    case detail(id: Int)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  모든 화면을 하나의 enum으로 관리
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;3. Router 구현&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
class Router: ObservableObject {
    @Published var path: [Route] = []

    func push(_ route: Route) {
        path.append(route)
    }

    func pop() {
        path.removeLast()
    }

    func reset(_ route: Route) {
        path = [route]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  Navigation의 단일 진입점
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;4. NavigationStack 연결&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
NavigationStack(path: $router.path) {
    HomeView()
        .navigationDestination(for: Route.self) { route in
            switch route {
            case .home:
                HomeView()
            case .web(let url):
                WebView(url: url)
            case .detail(let id):
                DetailView(id: id)
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;5. 푸시 알림 → WebView 이동&lt;/h2&gt;

&lt;p&gt;
푸시 데이터 예시:
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
{
    &quot;type&quot;: &quot;web&quot;,
    &quot;url&quot;: &quot;https://example.com&quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
처리 코드:
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
func handlePush(data: [String: Any]) {
    if let urlString = data[&quot;url&quot;] as? String,
       let url = URL(string: urlString) {
        router.push(.web(url: url))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  앱 어디서든 동일한 방식으로 처리
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;6. 딥링크 처리&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
func handleDeepLink(url: URL) {
    if url.path.contains(&quot;detail&quot;) {
        router.push(.detail(id: 1))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  URL → Route 변환
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. Web → App 이동 (JS 브릿지)&lt;/h2&gt;

&lt;p&gt;
웹에서 메시지 전달:
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
window.webkit.messageHandlers.callback.postMessage({
    type: &quot;detail&quot;,
    id: 10
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
앱에서 처리:
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
func userContentController(_ userContentController: WKUserContentController,
                           didReceive message: WKScriptMessage) {

    if let data = message.body as? [String: Any],
       let type = data[&quot;type&quot;] as? String {

        if type == &quot;detail&quot; {
            router.push(.detail(id: 10))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  Web → Native 라우팅 연결
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;8. 상태 충돌 방지&lt;/h2&gt;

&lt;p&gt;
자주 발생하는 문제:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;중복 push&lt;/li&gt;
&lt;li&gt;NavigationStack 꼬임&lt;/li&gt;
&lt;li&gt;WebView reload 문제&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결 전략&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 중복 체크&lt;/li&gt;
&lt;li&gt;Router 단일화&lt;/li&gt;
&lt;li&gt;WebView 상태 분리&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무 추천 구조&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;모든 이동 → Router&lt;/li&gt;
&lt;li&gt;WebView는 화면 역할만 수행&lt;/li&gt;
&lt;li&gt;딥링크 / 푸시 → Route 변환 후 처리&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 전체 흐름 정리&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;푸시 / 딥링크 수신&lt;/li&gt;
&lt;li&gt;Route 변환&lt;/li&gt;
&lt;li&gt;Router.push()&lt;/li&gt;
&lt;li&gt;NavigationStack 반영&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;h2&gt;11. 결론&lt;/h2&gt;

&lt;p&gt;
Navigation + WebView의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;모든 이동을 &quot;데이터(Route)&quot;로 통제한다&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;푸시 → Route&lt;/li&gt;
&lt;li&gt;딥링크 → Route&lt;/li&gt;
&lt;li&gt;웹 이벤트 → Route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 구조를 만들면 복잡한 앱에서도 안정적인 네비게이션이 가능합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI 성능 문제 실전 케이스 (List, Navigation, WebView)&lt;/strong&gt;를 다루면
전체 구조가 완성됩니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/60</guid>
      <comments>https://choco-mochi-roll.tistory.com/60#entry60comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:03:17 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI WKWebView 완전 정리 (쿠키, 로그인 유지, JS 통신까지)</title>
      <link>https://choco-mochi-roll.tistory.com/59</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI WKWebView 완전 정리 (쿠키, 로그인 유지, JS 통신까지)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI에서 WKWebView를 사용하는 방법부터 쿠키 동기화, 로그인 유지, JavaScript 브릿지까지 실무 기준으로 완벽 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI WKWebView 완전 정리 (쿠키, 로그인 유지, JS 통신까지)&lt;/h1&gt;

&lt;p&gt;
SwiftUI에서 WebView를 붙이는 순간 난이도가 급격히 올라갑니다.
&lt;/p&gt;

&lt;p&gt;
특히 아래 문제는 거의 반드시 겪게 됩니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;쿠키가 안 붙음&lt;/li&gt;
&lt;li&gt;로그인이 유지되지 않음&lt;/li&gt;
&lt;li&gt;웹 ↔ 앱 통신이 안됨&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 글에서는 &lt;strong&gt;WKWebView를 SwiftUI에서 제대로 사용하는 방법&lt;/strong&gt;을 실무 기준으로 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. SwiftUI에서 WKWebView 사용하는 기본 구조&lt;/h2&gt;

&lt;p&gt;
SwiftUI는 UIKit을 직접 사용할 수 없기 때문에
&lt;strong&gt;UIViewRepresentable&lt;/strong&gt;을 사용해야 합니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {

    let url: URL

    func makeUIView(context: Context) -&gt; WKWebView {
        return WKWebView()
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        webView.load(request)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  기본 구조는 간단하지만 실무에서는 이것만으로 부족합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. 쿠키가 안 붙는 이유&lt;/h2&gt;

&lt;p&gt;
WKWebView는 &lt;strong&gt;앱의 쿠키 저장소와 분리&lt;/strong&gt;되어 있습니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URLSession → HTTPCookieStorage&lt;/li&gt;
&lt;li&gt;WKWebView → WKHTTPCookieStore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  서로 다른 공간을 사용합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;3. 쿠키 동기화 방법&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
func setCookie() {
    let cookie = HTTPCookie(properties: [
        .domain: &quot;.example.com&quot;,
        .path: &quot;/&quot;,
        .name: &quot;token&quot;,
        .value: &quot;1234&quot;,
        .secure: &quot;TRUE&quot;
    ])!

    let store = WKWebsiteDataStore.default().httpCookieStore
    store.setCookie(cookie)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  반드시 WebView 로드 전에 설정해야 합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;4. 로그인 유지 전략&lt;/h2&gt;

&lt;p&gt;
실무에서는 다음 방식으로 처리합니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;앱 로그인 → 토큰 저장&lt;/li&gt;
&lt;li&gt;WebView 진입 시 쿠키 주입&lt;/li&gt;
&lt;li&gt;웹에서 세션 유지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  핵심은 &lt;strong&gt;WebView 생성 전에 쿠키 세팅&lt;/strong&gt;
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;5. WKWebView 설정 (중요)&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
let config = WKWebViewConfiguration()
config.websiteDataStore = .default()
let webView = WKWebView(frame: .zero, configuration: config)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  데이터 저장소 설정이 중요합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;6. JavaScript ↔ Native 통신&lt;/h2&gt;

&lt;h3&gt;JS → Native&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
config.userContentController.add(context.coordinator, name: &quot;callback&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
func userContentController(_ userContentController: WKUserContentController,
                           didReceive message: WKScriptMessage) {
    print(message.body)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Native → JS&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
webView.evaluateJavaScript(&quot;alert('Hello')&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  Web ↔ App 연동의 핵심
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. Coordinator 활용&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
class Coordinator: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        print(message.body)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;
func makeCoordinator() -&gt; Coordinator {
    Coordinator()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  delegate 처리를 위해 필요합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;8. WKUserScript (초기 스크립트 주입)&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
let script = WKUserScript(
    source: &quot;document.body.style.background = 'red';&quot;,
    injectionTime: .atDocumentStart,
    forMainFrameOnly: true
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
옵션:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.atDocumentStart&lt;/li&gt;
&lt;li&gt;.atDocumentEnd&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  로그인, 쿠키, JS 초기화에 활용
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;쿠키 설정 후 WebView 생성&lt;/li&gt;
&lt;li&gt;updateUIView에서 계속 load 호출&lt;/li&gt;
&lt;li&gt;JS handler 중복 등록&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 실무 추천 구조&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WebView 생성 전에 쿠키 세팅&lt;/li&gt;
&lt;li&gt;Coordinator로 이벤트 처리&lt;/li&gt;
&lt;li&gt;Router와 연결해서 네비게이션 관리&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;11. 결론&lt;/h2&gt;

&lt;p&gt;
SwiftUI에서 WebView의 핵심은 다음입니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;쿠키는 별도 저장소&lt;/li&gt;
&lt;li&gt;생성 전에 설정해야 함&lt;/li&gt;
&lt;li&gt;JS 브릿지로 연동&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 구조를 이해하면 대부분의 WebView 문제를 해결할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI Navigation + WebView 연동 (딥링크, 푸시 포함)&lt;/strong&gt;을 다루면
실무 구조가 완성됩니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/59</guid>
      <comments>https://choco-mochi-roll.tistory.com/59#entry59comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:01:52 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI SafeArea 완벽 이해 (layout 깨짐, NavigationBar 겹침 해결)</title>
      <link>https://choco-mochi-roll.tistory.com/58</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;SwiftUI SafeArea 완벽 이해 (layout 깨짐, NavigationBar 겹침 해결)&lt;/title&gt;
&lt;meta name=&quot;description&quot; content=&quot;SwiftUI SafeArea 개념부터 layout 깨짐, NavigationBar 겹침 문제까지 실무에서 바로 적용 가능한 해결 방법을 정리합니다.&quot;&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;SwiftUI SafeArea 완벽 이해 (layout 깨짐, NavigationBar 겹침 해결)&lt;/h1&gt;

&lt;p&gt;
SwiftUI에서 UI를 만들다 보면 반드시 마주치는 문제가 있습니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NavigationBar랑 겹침&lt;/li&gt;
&lt;li&gt;상단/하단 여백 이상&lt;/li&gt;
&lt;li&gt;전체 화면인데 일부만 채워짐&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 문제의 핵심 원인은 &lt;strong&gt;SafeArea&lt;/strong&gt;입니다.
&lt;/p&gt;

&lt;p&gt;
이 글에서는 SafeArea의 개념부터 실무 해결 방법까지 정리합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;1. SafeArea란 무엇인가?&lt;/h2&gt;

&lt;p&gt;
SafeArea는 &lt;strong&gt;시스템 UI를 침범하지 않는 안전한 영역&lt;/strong&gt;입니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;상단: 상태바, 노치&lt;/li&gt;
&lt;li&gt;하단: 홈 인디케이터&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
SwiftUI는 기본적으로 SafeArea 안에서만 View를 배치합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;2. 왜 layout이 깨질까?&lt;/h2&gt;

&lt;p&gt;
문제는 다음 상황에서 발생합니다.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;전체 화면을 쓰고 싶은 경우&lt;/li&gt;
&lt;li&gt;NavigationStack과 함께 사용하는 경우&lt;/li&gt;
&lt;li&gt;background를 설정할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;3. ignoresSafeArea&lt;/h2&gt;

&lt;p&gt;
SafeArea를 무시하고 전체 화면을 사용합니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
Color.blue
    .ignoresSafeArea()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  화면 전체를 덮음
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;4. 특정 방향만 무시하기&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
.ignoresSafeArea(edges: .top)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
옵션:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.top&lt;/li&gt;
&lt;li&gt;.bottom&lt;/li&gt;
&lt;li&gt;.horizontal&lt;/li&gt;
&lt;li&gt;.all&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;5. NavigationBar와 겹치는 문제&lt;/h2&gt;

&lt;p&gt;
가장 많이 발생하는 문제입니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
NavigationStack {
    Color.red
        .ignoresSafeArea()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  NavigationBar 아래로 침범
&lt;/p&gt;

&lt;h3&gt;해결 방법&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
.safeAreaInset(edge: .top) {
    Color.clear.frame(height: 0)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
또는 NavigationBar 숨김 처리
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;6. safeAreaInset 활용&lt;/h2&gt;

&lt;p&gt;
SafeArea 안쪽에 UI를 추가할 수 있습니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
.safeAreaInset(edge: .bottom) {
    Button(&quot;확인&quot;) {
    }
    .frame(maxWidth: .infinity)
    .padding()
    .background(Color.blue)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  하단 고정 버튼 구현 가능
&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;7. background가 안 채워지는 문제&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
VStack {
    Text(&quot;Hello&quot;)
}
.background(Color.yellow)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  SafeArea 밖은 채워지지 않음
&lt;/p&gt;

&lt;h3&gt;해결&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
.background(Color.yellow.ignoresSafeArea())
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;8. ScrollView와 SafeArea&lt;/h2&gt;

&lt;p&gt;
ScrollView는 SafeArea를 자동으로 적용합니다.
&lt;/p&gt;

&lt;p&gt;
문제:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;상단 여백 이상&lt;/li&gt;
&lt;li&gt;content offset 문제&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;해결&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;
.scrollContentBackground(.hidden)
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;h2&gt;9. 실무에서 자주 하는 실수&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ignoresSafeArea 남용&lt;/li&gt;
&lt;li&gt;NavigationStack과 함께 무조건 전체화면 사용&lt;/li&gt;
&lt;li&gt;background 위치 잘못 설정&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;10. 실무 추천 전략&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;기본은 SafeArea 유지&lt;/li&gt;
&lt;li&gt;필요한 부분만 선택적으로 무시&lt;/li&gt;
&lt;li&gt;safeAreaInset 적극 활용&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;h2&gt;11. 결론&lt;/h2&gt;

&lt;p&gt;
SafeArea의 핵심은 다음입니다.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;기본은 보호, 필요할 때만 해제&lt;/strong&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;전체화면 → ignoresSafeArea&lt;/li&gt;
&lt;li&gt;부분 제어 → edges 옵션&lt;/li&gt;
&lt;li&gt;추가 UI → safeAreaInset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
이 구조를 이해하면 대부분의 layout 문제를 해결할 수 있습니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;
다음 글에서는 &lt;strong&gt;SwiftUI WebView (WKWebView) 연동 및 쿠키 처리&lt;/strong&gt;를 다루면
실무 활용도가 크게 올라갑니다.
&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>IT</category>
      <author>초코모찌롤</author>
      <guid isPermaLink="true">https://choco-mochi-roll.tistory.com/58</guid>
      <comments>https://choco-mochi-roll.tistory.com/58#entry58comment</comments>
      <pubDate>Fri, 3 Apr 2026 10:00:41 +0900</pubDate>
    </item>
  </channel>
</rss>