<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>GonsiOS</title>
    <link>https://gonslab.tistory.com/</link>
    <description>iOS Swift Application Development Blog</description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 02:55:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>GonsLab</managingEditor>
    <image>
      <title>GonsiOS</title>
      <url>https://tistory1.daumcdn.net/tistory/3911551/attach/3e7a80f3e3ea4149aa6e215cc969eaa2</url>
      <link>https://gonslab.tistory.com</link>
    </image>
    <item>
      <title>WWDC 2025: Foundation Models &amp;mdash; Apple이 공개한 온디바이스 LLM API</title>
      <link>https://gonslab.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC 2025에서 발표된 &lt;b&gt;Foundation Models Framework&lt;/b&gt;는 애플 생태계의 개발자들에게 그야말로 '게임 체인저'였습니다. 이제 외부 API 라이브러리 없이도 OS 내장 모델을 직접 제어할 수 있게 되었으니까요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;[Swift] Apple 인텔리전스의 정수: Foundation Models 프레임워크 완벽 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC 2025는 애플이 AI를 단순한 기능을 넘어 &lt;b&gt;'개발자들의 도구'&lt;/b&gt;로 완전히 개방한 해로 기억될 것입니다. 그 중심에는 바로 &lt;b&gt;Foundation Models Framework&lt;/b&gt;가 있습니다. 이제 우리는 거창한 서버나 비싼 API 호출 비용 없이도 온디바이스 LLM의 강력함을 앱에 녹여낼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 왜 Foundation Models인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 LLM을 앱에 도입하려면 OpenAI나 Anthropic 같은 클라우드 기반 API를 쓰거나, 무거운 오픈소스 모델을 직접 포팅해야 했습니다. 하지만 애플의 새로운 프레임워크는 이 패러다임을 바꿨습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Privacy-First:&lt;/b&gt; 모든 연산이 사용자 기기 안에서 일어납니다. 데이터 유출 걱정이 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Zero Cost:&lt;/b&gt; API 호출 당 비용(Token)이 들지 않습니다. 개발자에게도, 사용자에게도 축복이죠.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Swift-Native:&lt;/b&gt; Swift 언어의 철학을 그대로 따릅니다. 복잡한 파이썬 스크립트나 JSON 파싱 지옥에서 벗어날 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 핵심 구성 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크의 핵심은 단순함과 강력함의 조화입니다.&lt;/p&gt;
&lt;table style=&quot;height: 94px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;th style=&quot;height: 18px;&quot;&gt;기능&lt;/th&gt;
&lt;th style=&quot;height: 18px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;&lt;code&gt;LanguageModelSession&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모델과 대화하고 작업을 지시하는 메인 엔트리 포인트입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;&lt;code&gt;@Generable&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;구조화된 데이터 출력을 보장하는 매크로입니다. (JSON 대신 Swift 객체 반환)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;&lt;code&gt;LoRA Adapters&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;특정 도메인(의료, 법률, 디자인 등)에 맞게 모델을 가볍게 미세 조정할 때 사용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;&lt;code&gt;Tool Calling&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모델이 직접 앱 내 함수를 호출하여 액션을 수행하도록 연결합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 실전 코드 샘플: 텍스트 요약 앱 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode 26에서 &lt;code&gt;FoundationModels&lt;/code&gt;를 가져오는 것만으로 충분합니다. 다음은 사용자의 입력 데이터를 분석해 구조화된 요약을 만드는 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import FoundationModels

// 1. 모델이 반환할 데이터 구조 정의 (Type-safe!)
@Generable
struct TaskSummary {
    let title: String
    let priority: Int // 1 to 5
    let estimatedTime: String
}

class AIManager {
    let session = LanguageModelSession()

    func analyzeInput(_ text: String) async throws -&amp;gt; TaskSummary {
        // 2. 자연어로 모델 호출
        let result = try await session.generate(
            &quot;다음 텍스트에서 할 일을 추출해줘: \(text)&quot;,
            as: TaskSummary.self
        )
        return result
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 온디바이스 AI vs 클라우드 AI&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 상황에서 이 프레임워크를 써야 할까요? 간단한 비교 테이블을 확인해 보세요.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Foundation Models (On-device)&lt;/th&gt;
&lt;th&gt;Cloud LLM (GPT-4 등)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;로컬 처리로 매우 빠름&lt;/td&gt;
&lt;td&gt;네트워크 상태에 의존&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;보안&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;완벽한 개인정보 보호&lt;/td&gt;
&lt;td&gt;외부 서버로 데이터 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;비용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;무료&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용량에 따른 과금&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;복잡도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;상대적으로 낮은 파라미터(3B~7B)&lt;/td&gt;
&lt;td&gt;초거대 모델의 고도화된 추론&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pro Tip:&lt;/b&gt; 복잡한 추론이나 방대한 데이터 처리는 클라우드를 쓰되, 사용자 인터랙션, 개인화 추천, 텍스트 요약 같은 '프라이버시가 중요한 일상 기능'은 Foundation Models로 구현하는 하이브리드 전략을 추천합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플의 Foundation Models Framework는 단순히 AI 기술을 추가한 것이 아니라, &lt;b&gt;'개인화된 앱 경험'&lt;/b&gt;의 기준을 바꿨습니다. 이제 더 이상 서버 유지비 걱정 없이 창의적인 기능을 마음껏 구현해 보세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 문서 더보기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundationmodels&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apple Developer Documentation - Foundation Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/wwdc25/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC 2025: Introducing Liquid Glass Design and AI integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>@Generable</category>
      <category>AI</category>
      <category>Apple</category>
      <category>FoundationModel</category>
      <category>ios</category>
      <category>macos</category>
      <category>objective-c</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>Xcode</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/110</guid>
      <comments>https://gonslab.tistory.com/110#entry110comment</comments>
      <pubDate>Sun, 1 Mar 2026 00:00:16 +0900</pubDate>
    </item>
    <item>
      <title>[macOS] iOS 개발자가 처음 macOS 앱을 출시하며 배운 것들</title>
      <link>https://gonslab.tistory.com/109</link>
      <description>&lt;h1&gt;첫 macOS 앱 출시: 개발 회고 및 기술 공유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱을 macOS로 확장하면서 &quot;SwiftUI니까 금방 되겠지&quot;라고 생각했다. 결론부터 말하면, 생각보다 신경 쓸 부분이 많았다. 같은 Swift, 같은 SwiftUI인데도 플랫폼마다 다른 부분들이 꽤 있었고, App Store 제출 과정에서도 예상치 못한 요구사항들을 마주했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 첫 macOS 앱을 출시하면서 겪은 경험들을 정리해본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UI/UX 차이점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;System Color가 공유되지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서 자주 사용하던 &lt;code&gt;UIColor.systemBackground&lt;/code&gt;, &lt;code&gt;UIColor.label&lt;/code&gt; 같은 시스템 컬러들이 macOS에서는 그대로 사용할 수 없다. macOS는 &lt;code&gt;NSColor&lt;/code&gt;를 사용하며, 일부 시스템 컬러의 이름이나 동작 방식이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 &lt;code&gt;Color.primary&lt;/code&gt;, &lt;code&gt;Color.secondary&lt;/code&gt; 같은 semantic color는 양쪽에서 동작하지만, 플랫폼별로 세밀한 색상 조정이 필요하다면 &lt;code&gt;#if os(macOS)&lt;/code&gt; 분기 처리가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;#if os(macOS)
let backgroundColor = Color(NSColor.windowBackgroundColor)
#else
let backgroundColor = Color(UIColor.systemBackground)
#endif&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;.navigationBarTitleDisplayMode(.inline)이 없다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서 네비게이션 타이틀 스타일을 조정할 때 흔히 사용하는 &lt;code&gt;.navigationBarTitleDisplayMode(.inline)&lt;/code&gt;이 macOS에는 존재하지 않는다. macOS의 윈도우 타이틀바는 iOS의 네비게이션 바와 개념 자체가 다르기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS에서는 &lt;code&gt;.navigationTitle()&lt;/code&gt;만 사용하거나, 필요하다면 toolbar를 활용해 커스텀하게 구성해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NavigationSplitView 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS 앱에서는 사이드바 기반의 레이아웃이 일반적이다. &lt;code&gt;NavigationSplitView&lt;/code&gt;를 활용하면 macOS다운 2단 또는 3단 레이아웃을 쉽게 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;NavigationSplitView {
    // Sidebar
    List(items, selection: $selectedItem) { item in
        Text(item.name)
    }
} detail: {
    // Detail View
    if let selectedItem {
        DetailView(item: selectedItem)
    } else {
        Text(&quot;항목을 선택하세요&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서는 &lt;code&gt;NavigationStack&lt;/code&gt;으로 push/pop 방식을 주로 사용했다면, macOS에서는 &lt;code&gt;NavigationSplitView&lt;/code&gt;로 마스터-디테일 패턴을 적용하는 것이 더 자연스럽다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;플랫폼별 API 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UIPasteboard 대신 NSPasteboard&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클립보드 기능을 구현할 때 iOS에서는 &lt;code&gt;UIPasteboard.general&lt;/code&gt;을 사용하지만, macOS에서는 &lt;code&gt;NSPasteboard.general&lt;/code&gt;을 사용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// iOS
UIPasteboard.general.string = &quot;복사할 텍스트&quot;

// macOS
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(&quot;복사할 텍스트&quot;, forType: .string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS의 &lt;code&gt;NSPasteboard&lt;/code&gt;는 &lt;code&gt;clearContents()&lt;/code&gt;를 먼저 호출해야 하는 점도 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UIApplicationDelegate 대신 NSApplicationDelegate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 생명주기 관리를 위해 iOS에서는 &lt;code&gt;UIApplicationDelegate&lt;/code&gt;를 사용하지만, macOS에서는 &lt;code&gt;NSApplicationDelegate&lt;/code&gt;를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// iOS
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, 
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
        return true
    }
}

// macOS
class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        // 초기화 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI의 &lt;code&gt;@main&lt;/code&gt; App 구조체를 사용한다면 &lt;code&gt;@NSApplicationDelegateAdaptor&lt;/code&gt;로 연결할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;App Store 제출 시 필수 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;macOS 전용 아이콘 필수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 아이콘만 있으면 될 줄 알았는데, macOS 전용 아이콘을 별도로 추가하지 않으면 아카이브 업로드 자체가 거부된다. macOS 아이콘은 1024x1024 크기에 둥근 사각형이 아닌 원형 마스크가 적용된 형태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Asset Catalog에서 &lt;code&gt;AppIcon&lt;/code&gt;에 macOS용 슬롯을 채워넣어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LSApplicationCategoryType 설정 필수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Info.plist&lt;/code&gt;에 &lt;code&gt;LSApplicationCategoryType&lt;/code&gt;을 반드시 설정해야 한다. 설정하지 않으면 App Store Connect 업로드가 실패한다.&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;LSApplicationCategoryType&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;public.app-category.utilities&amp;lt;/string&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리 값은 &lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apple 문서&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;App Sandbox 활성화 필수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS 앱은 App Sandbox가 반드시 활성화되어 있어야 App Store에 제출할 수 있다. Xcode의 Signing &amp;amp; Capabilities에서 App Sandbox를 추가하고, 필요한 권한만 체크하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 통신이 필요하다면 Outgoing Connections, 파일 접근이 필요하다면 해당 권한을 활성화해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빌드 및 배포 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아카이브와 업로드를 각각 진행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS와 macOS 빌드는 별도로 아카이브하고 각각 업로드해야 한다. Universal 앱이라 하더라도 하나의 아카이브로 두 플랫폼을 동시에 제출하는 것은 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode에서 빌드 타겟을 macOS로 변경한 후 Product &amp;gt; Archive를 진행하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;심사도 각각 진행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Store 심사 역시 iOS와 macOS가 별개로 진행된다. 한쪽이 승인되어도 다른 쪽은 여전히 심사 중일 수 있고, 리젝 사유도 플랫폼마다 다를 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TestFlight Universal 앱 설치 시 주의점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TestFlight에 Universal 앱으로 모든 플랫폼 빌드를 올렸을 때 예상치 못한 동작이 있었다. macOS에서 TestFlight로 앱을 설치하면 &lt;b&gt;네이티브 macOS 빌드가 아닌 iOS 빌드(Designed for iPad)가 우선 설치&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple이 iOS 앱을 Mac에서 실행 가능하다고 판단하여 iOS 빌드에 우선순위를 부여하기 때문이다. 테스터에게 네이티브 macOS 버전을 테스트하게 하려면 별도 안내가 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서드파티 SDK 호환성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Google AdMob은 macOS 미지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수익화를 위해 Google AdMob을 사용하고 있었는데, AdMob SDK는 iOS 전용이라 macOS에서는 사용할 수 없다. macOS 앱에서 광고를 넣으려면 다른 광고 네트워크를 찾거나, 광고 없이 유료 앱 또는 인앱 결제 모델을 고려해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 개발 경험이 있다면 macOS 앱 개발 자체는 크게 어렵지 않다. SwiftUI 덕분에 많은 코드를 공유할 수 있고, 플랫폼별 분기 처리만 잘 해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 App Store 제출 과정에서 macOS 특유의 요구사항들(아이콘, 카테고리, 샌드박스)을 미리 알아두지 않으면 업로드 단계에서 막힐 수 있다. 이 글이 첫 macOS 앱을 준비하는 분들에게 도움이 되었으면 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;macOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Apple&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;App Store&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;macOS 개발&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;macOS 앱&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;NavigationSplitView&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;NSPasteboard&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;App Sandbox&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;TestFlight&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Universal App&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS 개발자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;앱 출시&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;앱스토어 심사&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;크로스 플랫폼&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Apple Developer&lt;/span&gt;&lt;/p&gt;</description>
      <category>macOS</category>
      <category>App Sandbox</category>
      <category>app store</category>
      <category>Apple</category>
      <category>ios</category>
      <category>macos</category>
      <category>NavigationSplitView</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>Xcode</category>
      <category>맥앱</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/109</guid>
      <comments>https://gonslab.tistory.com/109#entry109comment</comments>
      <pubDate>Sun, 1 Feb 2026 00:00:11 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] App Store Connect Reject 전화 회신 (앱스토어 커넥트 심사 가이드라인 리젝 극복기)</title>
      <link>https://gonslab.tistory.com/108</link>
      <description>&lt;h1&gt;App Store Connect 앱 심사 리젝 극복 후기 - 1년간의 여정&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트 중에 하나가 계속 속을 썩였습니다.&lt;br /&gt;2024년 10월 3일, 첫 번째 앱 심사 제출부터 2025년 12월 19일 최종 승인까지, 약 1년 2개월간의 길고 지난한 여정이었습니다. 10번이 넘는 리젝을 경험하며 포기하고 싶었던 순간도 많았지만, 결국 전화 한 통으로 모든 것이 해결되었습니다.&lt;br /&gt;같은 상황으로 고민하시는 분들께 조금이나마 도움이 되길 바라며 이 후기를 작성합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리젝 히스토리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;받았던 주요 리젝 사유들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 4.2.2 - Design - Minimum Functionality&lt;/b&gt;&lt;/p&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 4.1 - Design - Copycats&lt;/b&gt;&lt;/p&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 2.3.3 - Performance - Accurate Metadata&lt;/b&gt;&lt;/p&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 4.3(a) - Design - Spam&lt;/b&gt;&lt;/p&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;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시도했던 방법들&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 기능 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리젝될 때마다 새로운 기능을 추가했습니다. 피드백을 받을 때마다 &quot;더 많은 기능이 필요하다&quot;는 내용이 있어서 계속해서 기능을 보강했지만, 번번이 같은 사유로 리젝되었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 국가 제한 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 세계를 기준으로 비슷한 앱이 많은 것 같아 사용 가능 국가를 &lt;b&gt;대한민국으로만 제한&lt;/b&gt;해서 제출했습니다. 하지만 이 역시 리젝되었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 유사 앱 사례 제시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나와 비슷한 컨셉의 앱들 중에서 기능이 더 적은 앱 5개의 링크를 첨부하며 설명했습니다.&lt;br /&gt;하지만 돌아온 답변은:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;다른 앱을 언급하지 마세요. 다른 앱의 심사 내용은 알 수 없습니다.&quot;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Apple Intelligence 기능 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple이 자사 기능 사용을 선호한다는 정보를 보고, 마지막으로 &lt;b&gt;Apple Intelligence AI 기능&lt;/b&gt;을 추가했습니다. 하지만 이것도 소용없었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이의 신청까지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 방법이 없다고 생각해 &lt;b&gt;App Review Board에 이의 신청&lt;/b&gt;을 했습니다.&lt;br /&gt;며칠 후 받은 답변:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;앱 심사 위원회는 귀하의 앱을 평가한 결과, 당초 거부 사유가 타당하다고 판단했습니다.&quot;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;마지막 수단으로 &lt;b&gt;전화 문의를 신청&lt;/b&gt;했습니다. 사실 큰 기대는 하지 않았습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전화 통화 내용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신청 한지 한 2시간 뒤인 점심시간에 전화가 왔습니다. 전화를 하신 분은 한국어를 하시는 담당자였고, 본인이 리젝 관련 전화 문의를 담당한다고 소개하셨습니다.&lt;br /&gt;담당자: &quot;앱을 확인해봤는데, 제가 볼 때는 기능적으로 충분합니다.&quot;&lt;br /&gt;저: &quot;그럼... 어떻게 하면 될까요?&quot;&lt;br /&gt;담당자: &quot;더 추가하실 내용이 없으시면, 마지막으로 올리신 빌드로 통과시켜드리겠습니다.&quot;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 믿기지 않았지만, &lt;b&gt;그 전화를 끊자마자 바로 앱이 승인&lt;/b&gt;되었습니다.&lt;br /&gt;1년 넘게 10번도 넘게 리젝당하고, 이의 신청까지 거부당했던 앱이 전화 한 통으로 해결된 것입니다.&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;문서로만 소통하다 보면 미묘한 뉘앙스나 상황 설명이 제대로 전달되지 않을 수 있습니다. 전화로 직접 대화하면 훨씬 빠르게 해결될 수 있습니다.&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;제 경험상, 여러 번 리젝되는 과정에서 실제로 앱을 꼼꼼히 검토했는지 의문이 들었습니다. 전화 담당자는 &quot;충분하다&quot;고 했으니까요.&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;1년이라는 시간이 정말 길게 느껴졌지만, 결국 해결되었습니다. 여러분의 앱도 충분히 가치가 있다면, 방법을 찾을 수 있을 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Store 심사는 정말 예측하기 어렵습니다. 같은 내용으로 여러 번 리젝될 수도 있고, 전화 한 통으로 모든 것이 해결될 수도 있습니다.&lt;br /&gt;혹시 지금 비슷한 상황으로 고민하고 계신 분들이 있다면, &lt;b&gt;전화 문의를 꼭 시도해보시길&lt;/b&gt; 권장합니다.&lt;br /&gt;긴 여정이었지만, 결국 앱을 출시할 수 있어서 정말 기쁩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w1R0x/dJMcag5bBhz/13GJE0dkg8G9hyh4At2ZXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w1R0x/dJMcag5bBhz/13GJE0dkg8G9hyh4At2ZXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w1R0x/dJMcag5bBhz/13GJE0dkg8G9hyh4At2ZXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw1R0x%2FdJMcag5bBhz%2F13GJE0dkg8G9hyh4At2ZXK%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;210&quot; height=&quot;958&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS, Swift, Xcode, Objective-C, Reject, Apple AppStore Connect Guideline, 앱스토어 커넥트, 리젝, 심사 가이드 라인, 이의 신청, 전화 회신, 리젝 극복기, 리젝 후기, 앱스토어 심사, App Review, 앱 심사 거부, 앱 출시, iOS 개발, 심사 통과, 앱 배포, 개발자 경험, 트러블슈팅, App Store Review, App Rejection, iOS Developer, Apple Guidelines, 앱스토어 심사 기간, 리젝 사유, 전화 통화 후기, 앱스토어 심사 거부 사유, App Store Review 프로세스, iOS 앱 배포 트러블슈팅, 애플 심사팀과 전화 통화 후기, 앱 출시 지연 해결 방법, 개발자 이의 신청 경험담&lt;/span&gt; &lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>app store</category>
      <category>Apple</category>
      <category>connect</category>
      <category>guidelines</category>
      <category>ios</category>
      <category>Reject</category>
      <category>리젝</category>
      <category>심사</category>
      <category>앱스토어</category>
      <category>이의신청</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/108</guid>
      <comments>https://gonslab.tistory.com/108#entry108comment</comments>
      <pubDate>Thu, 1 Jan 2026 00:00:19 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Auto Scrolling Infinite Carousel Timer Animation</title>
      <link>https://gonslab.tistory.com/107</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/COmfN/btsObt1ToR1/z6UrKKOmHq51ckiu7ZzwD0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/COmfN/btsObt1ToR1/z6UrKKOmHq51ckiu7ZzwD0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/COmfN/btsObt1ToR1/z6UrKKOmHq51ckiu7ZzwD0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/COmfN/btsObt1ToR1/z6UrKKOmHq51ckiu7ZzwD0/img.gif&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;434&quot; height=&quot;900&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748237052430&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct InfiniteCarouselView: View {
    
    init(imageNames: [String], velocity: CGFloat = 1) {
        self.imageNames = imageNames
        
        var items: [Item] = []
        
        items.append(contentsOf: imageNames.map { Item(id: UUID(), imageName: $0) })
        items.append(contentsOf: imageNames.map { Item(id: UUID(), imageName: $0) })
        items.append(contentsOf: imageNames.map { Item(id: UUID(), imageName: $0) })
        
        self.items = items
        self.velocity = velocity
        
        let length = (CarouselCard.itemSize.width + itemSpacing) * CGFloat(imageNames.count)
        self.x = length
        self.carouselLength = length
    }
    
    private let imageNames: [String]
    private let items: [Item]
    private let velocity: CGFloat
    
    @State private var scrollPosition = ScrollPosition()
    @State private var timer = Timer
        .publish(every: 0.01, on: .main, in: .common)
        .autoconnect()
    
    @State private var x: CGFloat
    
    private let itemSpacing: CGFloat = 8
    private let carouselLength: CGFloat
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: itemSpacing) {
                ForEach(items) { item in
                    CarouselCard(imageName: item.imageName)
                        .id(item.id)
                }
            }
            .safeAreaPadding(.horizontal)
        }
        .scrollClipDisabled()
        .scrollPosition($scrollPosition)
        .onReceive(timer) { _ in
            if x &amp;gt;= carouselLength * 2 || x &amp;lt;= 0 {
                x = carouselLength
            } else {
                x += velocity
            }
        }
        .onChange(of: x) {
            scrollPosition.scrollTo(x: x)
        }
        // 1. we need to detect when the scroll view is manually started / stopped
        .onScrollPhaseChange { oldPhase, newPhase in
            switch (oldPhase, newPhase) {
            case (.idle, .idle):
                break
                
            case (_, .interacting):
                timer.upstream.connect().cancel()
                
            case (_, .idle):
                timer = Timer
                    .publish(every: 0.01, on: .main, in: .common)
                    .autoconnect()
                
            default:
                break
            }
        }
        // 2. we need to observe the distance the scroll view was moved during the period
        .onScrollGeometryChange(for: Double.self) { scrollGeometry in
            scrollGeometry.contentOffset.x
        } action: { oldValue, newValue in
            x = newValue
        }
        
    }
}

struct Item: Identifiable {
    let id: UUID
    let imageName: String
}

struct CarouselCard: View {
    
    let imageName: String
    
    static let itemSize = CGSize(width: 100, height: 120)
    
    var body: some View {
        Image(imageName)
            .resizable()
            .scaledToFill()
            .frame(width: Self.itemSize.width, height: Self.itemSize.height)
            .clipShape(RoundedRectangle(cornerRadius: 16))
            .shadow(color: .black.opacity(0.3), radius: 4, x: 0, y: 4)
    }
}

#Preview {
    let imageNames = (1...7).map { String($0) }
    InfiniteCarouselView(imageNames: imageNames)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748237206238&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct AnimatedMeshGradientView: View {
    
    @State private var isAnimated = false
    
    var body: some View {
        MeshGradient(
            width: 3,
            height: 3,
            points: [
                [0.0, 0.0], isAnimated ? [0.75, 0.0] : [0.25, 0.0], [1.0, 0.0],
                isAnimated ? [0.0, 0.8] : [0.0, 0.4], isAnimated ? [0.7, 0.2] : [0.9, 0.4], [1.0, 0.5],
                [0.0, 1.0], isAnimated ? [0.3, 1.0] : [0.6, 1.0], [1.0, 1.0]
            ],
            colors: [
                .softLavender, .paleSkyBlue, .mintGreen,
                .dustyRose, .peachCream, .babyBlue,
                .lilacMist, .seafoamPastel, .blushPink
            ]
        )
        .ignoresSafeArea()
        .onAppear {
            withAnimation(.spring(.smooth).repeatForever(autoreverses: true)) {
                isAnimated = true
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748237227555&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct OnboardingView: View {
    var body: some View {
        let imageNames1 = (1...7).map { String($0) }
        let imageNames2 = (8...14).map { String($0) }
        let imageNames3 = (15...21).map { String($0) }
        let imageNames4 = (22...28).map { String($0) }
        
        ZStack {
            AnimatedMeshGradientView()
            
            VStack {
                VStack {
                    InfiniteCarouselView(imageNames: imageNames1, velocity: 0.5)
                    InfiniteCarouselView(imageNames: imageNames2, velocity: -0.25)
                    InfiniteCarouselView(imageNames: imageNames3, velocity: 0.3)
                    InfiniteCarouselView(imageNames: imageNames4, velocity: -0.2)
                }
                .rotationEffect(.degrees(-10))
                
                Spacer()
            }
            
            LinearGradient(
                stops: [
                    .init(color: .clear, location: 0.0),
                    .init(color: .white, location: 0.6),
                    .init(color: .white, location: 1.0),
                ],
                startPoint: .top,
                endPoint: .bottom
            )
            .ignoresSafeArea()
            
            VStack(spacing: 24) {
                Spacer()
                
                Text(&quot;Discover Your Best\nAI Companion&quot;)
                    .font(.system(.largeTitle, design: .default, weight: .bold))
                    .multilineTextAlignment(.center)
                    .foregroundStyle(
                        LinearGradient(
                            colors: [.aiPink, .roboticBlue, .futuristicViolet],
                            startPoint: .leading,
                            endPoint: .trailing
                        )
                    )
                
                Text(&quot;Find and connect with the best AI companions, personalized just for you. Chat, learn, and experience AI like never before!&quot;)
                    .font(.system(.subheadline, design: .default, weight: .regular))
                    .multilineTextAlignment(.center)
                    .foregroundStyle(.gray)
                    .padding(.horizontal, 32)
                
                Button {
                    // TODO
                } label: {
                    Text(&quot;Get Started&quot;)
                        .font(.system(.headline, design: .default, weight: .semibold))
                        .foregroundStyle(.white)
                        .frame(height: 64)
                        .frame(maxWidth: .infinity)
                        .background(Color.roboticBlue)
                        .clipShape(Capsule())
                }
                .padding(.horizontal, 32)
            }
            
        }
    }
}

#Preview {
    OnboardingView()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748237321561&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Color {
    static let deepPurple = Color(red: 89/255, green: 46/255, blue: 131/255)    // Deep learning purple
    static let neonBlue = Color(red: 66/255, green: 218/255, blue: 245/255)     // Neural network blue
    static let techTeal = Color(red: 23/255, green: 190/255, blue: 187/255)     // Tech teal
    static let futuristicViolet = Color(red: 147/255, green: 51/255, blue: 234/255) // Futuristic violet
    static let quantumGreen = Color(red: 0/255, green: 255/255, blue: 179/255)   // Quantum computing green
    static let dataRed = Color(red: 255/255, green: 71/255, blue: 87/255)        // Data analysis red
    static let aiPink = Color(red: 255/255, green: 107/255, blue: 255/255)       // AI assistant pink
    static let roboticBlue = Color(red: 0/255, green: 150/255, blue: 255/255)    // Robotic blue
    static let cyberYellow = Color(red: 255/255, green: 214/255, blue: 10/255)   // Cybernetic yellow
    
    static let softLavender = Color(red: 220/255, green: 208/255, blue: 255/255)   // Soft lavender
    static let paleSkyBlue = Color(red: 176/255, green: 218/255, blue: 255/255)    // Pale sky blue
    static let mintGreen = Color(red: 198/255, green: 255/255, blue: 226/255)      // Mint green
    static let dustyRose = Color(red: 255/255, green: 198/255, blue: 218/255)      // Dusty rose
    static let peachCream = Color(red: 255/255, green: 224/255, blue: 196/255)     // Peach cream
    static let babyBlue = Color(red: 198/255, green: 222/255, blue: 255/255)       // Baby blue
    static let lilacMist = Color(red: 232/255, green: 208/255, blue: 238/255)      // Lilac mist
    static let seafoamPastel = Color(red: 202/255, green: 255/255, blue: 242/255)  // Seafoam pastel
    static let blushPink = Color(red: 255/255, green: 218/255, blue: 233/255)      // Blush pink
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748237339414&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

@main
struct AppDuckInfiniteCarouselTutorialApp: App {
    var body: some Scene {
        WindowGroup {
            OnboardingView()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Sources&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/appbeyond-io/AutoScrollingInfiniteCarousel-iOS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/appbeyond-io/AutoScrollingInfiniteCarousel-iOS&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Auto Scrolling&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Infinite Carousel&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Timer Animation&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Android&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;MeshGradient&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;repeatForever&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;LinearGradient&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;rotationEffect&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;ScrollPosition&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;onScrollGeometryChange&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>auto scrolling</category>
      <category>Infinite Carousel</category>
      <category>ios</category>
      <category>meshgradient</category>
      <category>onscrollgeometrychange</category>
      <category>rotationEffect</category>
      <category>scrollPosition</category>
      <category>swiftUI</category>
      <category>timer animation</category>
      <category>Xcode</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/107</guid>
      <comments>https://gonslab.tistory.com/107#entry107comment</comments>
      <pubDate>Mon, 1 Sep 2025 00:00:24 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Animation repeatForever autoreverses 반복 애니메이션</title>
      <link>https://gonslab.tistory.com/106</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ju1as/btsMUWYqE4g/K8kFaBkmAhNoK0NyAiQJ5k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ju1as/btsMUWYqE4g/K8kFaBkmAhNoK0NyAiQJ5k/img.gif&quot; data-alt=&quot;dog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ju1as/btsMUWYqE4g/K8kFaBkmAhNoK0NyAiQJ5k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Ju1as/btsMUWYqE4g/K8kFaBkmAhNoK0NyAiQJ5k/img.gif&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;380&quot; height=&quot;380&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;dog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1742893778914&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

/// 강아지 반복 이동 뷰
struct MovingDogView: View {
    @State private var position: CGFloat = -(UIScreen.main.bounds.width / 2 - 50)
    @State private var isFlipped = false // 이미지 반전 여부
    @State private var timer: Timer?
    
    private let duration: TimeInterval = 2
    
    var body: some View {
        VStack {
            GifImageView(&quot;dog&quot;)
                .frame(width: 50, height: 50)
                .scaleEffect(x: isFlipped ? -1 : 1, y: 1) // 좌우 반전 효과
                .offset(x: position)
                .animation(.linear(duration: duration).repeatForever(autoreverses: true), value: position)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onAppear {
            startAnimation()
        }
        .onDisappear {
            timer?.invalidate()
        }
    }
    
    private func startAnimation() {
        DispatchQueue.main.async {
            position = UIScreen.main.bounds.width / 2 - 50
            
            timer = .scheduledTimer(withTimeInterval: duration, repeats: true) { _ in
                isFlipped.toggle() // 방향이 바뀔 때마다 이미지 반전
            }
        }
    }
}

#Preview {
    MovingDogView()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1742893795664&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI
import WebKit

// https://medium.com/@venkateshmandapati/displaying-gifs-in-swiftui-using-gifimageview-04179d926552
/// GIF 뷰
struct GifImageView: UIViewRepresentable {
    
    private let name: String
    
    init(_ name: String) {
        self.name = name
    }
    
    func makeUIView(context: Context) -&amp;gt; WKWebView {
        let webview = WKWebView()
        if let url = Bundle.main.url(forResource: name, withExtension: &quot;gif&quot;),
           let data = try? Data(contentsOf: url) {
            webview.load(data, mimeType: &quot;image/gif&quot;, characterEncodingName: &quot;UTF-8&quot;, baseURL: url.deletingLastPathComponent())
        }
        return webview
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.reload()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Android&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Animation&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;scaleEffect&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;offset&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;duration&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;linear&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;repeatForever&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;autoreverses&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>Animation</category>
      <category>autoreverses</category>
      <category>Awesome</category>
      <category>ios</category>
      <category>Linear</category>
      <category>offset</category>
      <category>repeatforever</category>
      <category>scaleEffect</category>
      <category>swiftUI</category>
      <category>timer</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/106</guid>
      <comments>https://gonslab.tistory.com/106#entry106comment</comments>
      <pubDate>Fri, 1 Aug 2025 00:00:48 +0900</pubDate>
    </item>
    <item>
      <title>문자열 카탈로그에서 자동 추출 비활성화 Disable Automatic Extraction in String Catalog Localizable.xcstrings</title>
      <link>https://gonslab.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내에 문자열 자동 추출 비활성화&lt;/p&gt;
&lt;pre id=&quot;code_1740489187307&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LOCALIZED_STRING_SWIFTUI_SUPPORT
NO

SWIFT_EMIT_LOC_STRINGS
NO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;%@ 등 argument 문자열 자동&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;추출&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;비활성화&lt;/p&gt;
&lt;pre id=&quot;code_1765270989162&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Localization Prefers String Catalogs
NO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 설정에 CFBundleName, CFBundleSpokenName 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InfoPlist.xcstrings &lt;span&gt;로&lt;/span&gt; &lt;span&gt;자동&lt;/span&gt; &lt;span&gt;추출&lt;/span&gt; &lt;span&gt;비활성화&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741150688883&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Localization Export Supported

NO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-05-09 오후 4.35.21.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VqCCE/btsNSeB8Jrg/6DNKs6jmxXZDRMrXZlitfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VqCCE/btsNSeB8Jrg/6DNKs6jmxXZDRMrXZlitfK/img.png&quot; data-alt=&quot;LOCALIZED_STRING_SWIFTUI_SUPPORT&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VqCCE/btsNSeB8Jrg/6DNKs6jmxXZDRMrXZlitfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVqCCE%2FbtsNSeB8Jrg%2F6DNKs6jmxXZDRMrXZlitfK%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;1530&quot; height=&quot;512&quot; data-filename=&quot;스크린샷 2025-05-09 오후 4.35.21.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LOCALIZED_STRING_SWIFTUI_SUPPORT&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/a/77741813&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/a/77741813&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740489157157&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Disable Automatic Extraction in String Catalog&quot; data-og-description=&quot;Is there a way to disable automatic extraction when using String Catalog? The following code block invokes an initializer with LocalizedStringKey and not with StringProtocol. Text(&amp;quot;Hello World...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/a/77741813&quot; data-og-url=&quot;https://stackoverflow.com/questions/77647428/disable-automatic-extraction-in-string-catalog&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ekt38V/hyYjy46TtV/Nn10O0jjhYg2Ys9oXojW3k/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/a/77741813&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/a/77741813&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ekt38V/hyYjy46TtV/Nn10O0jjhYg2Ys9oXojW3k/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Disable Automatic Extraction in String Catalog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Is there a way to disable automatic extraction when using String Catalog? The following code block invokes an initializer with LocalizedStringKey and not with StringProtocol. Text(&quot;Hello World...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;String Catalog&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Localizable.xcstrings&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Android&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #ffffff; text-align: start;&quot;&gt;translation&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #ffffff; text-align: start;&quot;&gt;multilingual&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #ffffff; text-align: start;&quot;&gt;Localization&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>Automatic</category>
      <category>catalog</category>
      <category>Extraction</category>
      <category>localizable</category>
      <category>string</category>
      <category>xcstrings</category>
      <category>다국어</category>
      <category>로컬라이징</category>
      <category>번역</category>
      <category>현지화</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/105</guid>
      <comments>https://gonslab.tistory.com/105#entry105comment</comments>
      <pubDate>Tue, 1 Jul 2025 00:00:24 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI DropDownMenu 드롭다운 메뉴 (Custom Picker)</title>
      <link>https://gonslab.tistory.com/104</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zEtiX/btsMdJRj7Wb/Y7pLXFBCOM24cuwXRiyMuk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zEtiX/btsMdJRj7Wb/Y7pLXFBCOM24cuwXRiyMuk/img.gif&quot; data-alt=&quot;DropDownMenu&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zEtiX/btsMdJRj7Wb/Y7pLXFBCOM24cuwXRiyMuk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zEtiX/btsMdJRj7Wb/Y7pLXFBCOM24cuwXRiyMuk/img.gif&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;400&quot; height=&quot;400&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DropDownMenu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739161256401&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://stackademic.com/blog/swiftui-dropdown-menu-3-ways-picker-menu-and-custom-from-scratch
/// 드롭다운 메뉴
struct DropDownMenu: View {
    
    let placeholder: String
    let options: [String]
    
    var menuWdith: CGFloat? = 150
    var buttonHeight: CGFloat = 50
    var maxItemDisplayed: Int = 3
    
    @Binding var selectedOptionIndex: Int?
    @Binding var showDropdown: Bool
    
    @State private var scrollPosition: Int?
    
    private var menuTitle: String {
        if let selectedOptionIndex {
            options[selectedOptionIndex]
        } else {
            placeholder
        }
    }
    
    var body: some  View {
        VStack(spacing: 0) {
            // selected item
            Button(action: {
                withAnimation {
                    showDropdown.toggle()
                }
            }, label: {
                HStack(spacing: nil) {
                    Text(menuTitle)
                    Spacer()
                    Image(systemName: &quot;chevron.down&quot;)
                        .rotationEffect(.degrees((showDropdown ?  -180 : 0)))
                }
            })
            .padding(.horizontal, 12)
            .frame(height: buttonHeight)
            
            
            // selection menu
            if showDropdown {
                let scrollViewHeight = options.count &amp;gt; maxItemDisplayed ?
                buttonHeight * CGFloat(maxItemDisplayed) + buttonHeight / 2 :
                buttonHeight * CGFloat(options.count)
                
                ScrollView {
                    LazyVStack(spacing: 0) {
                        ForEach(0..&amp;lt;options.count, id: \.self) { index in
                            Button(action: {
                                withAnimation {
                                    selectedOptionIndex = index
                                    showDropdown.toggle()
                                }
                            }, label: {
                                HStack {
                                    Text(options[index])
                                    Spacer()
                                    if (index == selectedOptionIndex) {
                                        Image(systemName: &quot;checkmark.circle.fill&quot;)
                                    }
                                }
                                .frame(maxWidth: menuWdith, alignment: .leading)
                                .padding(.horizontal, 12)
                                .frame(height: buttonHeight)
                            })
                        }
                    }
                    .scrollTargetLayout()
                }
                .scrollPosition(id: $scrollPosition)
                .scrollDisabled(options.count &amp;lt;= maxItemDisplayed)
                .frame(height: scrollViewHeight)
                .onAppear {
                    scrollPosition = selectedOptionIndex
                }
            }
        }
        .background(.white)
        .clipShape(.rect(cornerRadius: 16))
        .overlay {
            RoundedRectangle(cornerRadius: 16)
                .stroke(.gray, lineWidth: 1)
        }
        .foregroundStyle(.black)
        .font(.callout)
        .frame(maxWidth: menuWdith)
        .frame(height: buttonHeight, alignment: .top) // 높이 제한 시 드롭다운 형식으로 표시되고, 해제 시 스크롤뷰 크기만큼 상위 뷰 늘어남
        .zIndex(100)
    }
}

#Preview {
    @Previewable @State var selectedOptionIndex: Int?
    @Previewable @State var showDropdown =  false
    let fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;orange&quot;, &quot;kiwi&quot;, &quot;cherry&quot;, &quot;melon&quot;]
    
    ZStack {
        DropDownMenu(
            placeholder: &quot;메뉴 선택&quot;,
            options: fruits,
            selectedOptionIndex: $selectedOptionIndex,
            showDropdown: $showDropdown
        )
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(.white)
    .onTapGesture {
        withAnimation {
            showDropdown =  false
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objectivew-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;드롭다운&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;드랍다운&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Drop Down&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Menu&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;선택 메뉴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Select menu&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Spinner&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Picker&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Segment&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;커스텀 피커&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>DropDownMenu</category>
      <category>ios</category>
      <category>picker</category>
      <category>segmentcontrol</category>
      <category>selectmenu</category>
      <category>spinner</category>
      <category>swiftUI</category>
      <category>드롭다운</category>
      <category>메뉴</category>
      <category>사용자 정의</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/104</guid>
      <comments>https://gonslab.tistory.com/104#entry104comment</comments>
      <pubDate>Sun, 1 Jun 2025 00:00:56 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Infinite Carousel 무한 스크롤 배너 + Timer</title>
      <link>https://gonslab.tistory.com/103</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/moDut/btsMdpyE9br/tw1nWKHegLRkiRhOkkgTNK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/moDut/btsMdpyE9br/tw1nWKHegLRkiRhOkkgTNK/img.gif&quot; data-alt=&quot;InfiniteCarousel&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/moDut/btsMdpyE9br/tw1nWKHegLRkiRhOkkgTNK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/moDut/btsMdpyE9br/tw1nWKHegLRkiRhOkkgTNK/img.gif&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;600&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;InfiniteCarousel&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739154992120&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://calliek.tistory.com/63
// https://velog.io/@jujube0/SwiftUI로-네이버웹툰-상단-배너-만들기
/// 무한 스크롤 + 타이머
struct InfiniteCarousel: View {
    /// 순환배열
    @State var items: [Color] = [.red, .green, .blue]
    /// 타이머
    @State var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
    /// 현재 인덱스
    @State private var currentIndex: Int = 0
    /// 드래그중 여부
    @State private var isDragging = false
    
    var body: some View {
        TabView(selection: $currentIndex) {
            
            // 순환 리스트에서 첫 번째와 마지막 색을 추가
            ForEach(-1..&amp;lt;items.count + 1, id: \.self) { i in
                
                let item = items[i &amp;lt; 0 ? items.count - 1 : (i &amp;gt;= items.count ? 0 : i)]
                item
                    .tag(i) // 현재 인덱스를 구분하기 위한 태그
                
            } //: ForEach
            .ignoresSafeArea()
            
        } //: Tabview
        .ignoresSafeArea()
        .tabViewStyle(.page(indexDisplayMode: .never)) // 기본 인디케이터 숨김
        // https://stackoverflow.com/a/79163904
        // 드래그 시 타이머 초기화가 안되서 gesture -&amp;gt; simultaneousGesture 수정
        .simultaneousGesture(
            DragGesture()
                .onChanged { value in // 드래그 중 타이머 중지
                    isDragging = true
                    timer.upstream.connect().cancel()
                }
                .onEnded { _ in // 드래그 종료 타이머 활성화
                    isDragging = false
                    getInfiniteScrollIndex()
                    timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
                }
        )
        .onChange(of: currentIndex) { _, _ in
            if !isDragging { // 드래그 중이 아닐 경우에만 페이지 세팅
                getInfiniteScrollIndex() // 무한 스크롤 구현
            }
        } //: onChange
        .onReceive(timer) { _ in
            // 타이머 가동 시 인덱스 하나씩 옮김
            withAnimation(.easeIn) {
                currentIndex += 1
            }
        }
    } //: Body
    
    /// 무한 스크롤 구현
    private func getInfiniteScrollIndex() {
        
        if currentIndex == items.count {
            // 처음으로 갔을 때 끝쪽으로 이동 (딜레이 주지 않으면 애니메이션이 부자연스럽게 빠르게 변경됨)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                currentIndex = 0
            }
        } else if currentIndex &amp;lt; 0 {
            // 마지막으로 갔을 때 첫쪽으로 이동
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                currentIndex = items.count - 1
            }
        }
    }
} //: View&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objectivew-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Infinite Carousel&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Banner&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;무한 스크롤&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&quot;&gt;타이머 배너&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; letter-spacing: 0px;&quot;&gt;자동 스크롤 메뉴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; letter-spacing: 0px;&quot;&gt;ScrollView&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; letter-spacing: 0px;&quot;&gt;TabView&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; letter-spacing: 0px;&quot;&gt;캐러셀&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>Infinite Carousel</category>
      <category>ios</category>
      <category>scrollview</category>
      <category>Slide</category>
      <category>swiftUI</category>
      <category>TabView</category>
      <category>timer banner</category>
      <category>무한 스크롤</category>
      <category>캐러셀</category>
      <category>타이머 배너</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/103</guid>
      <comments>https://gonslab.tistory.com/103#entry103comment</comments>
      <pubDate>Thu, 1 May 2025 00:00:09 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift withTaskGroup, withThrowingTaskGroup 비동기 반복문 (async await loop)</title>
      <link>https://gonslab.tistory.com/102</link>
      <description>&lt;pre id=&quot;code_1736924324379&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func test() async {
    await withTaskGroup(of: Int.self) { taskGroup in
        
        [3,1,2].forEach { i in
            taskGroup.addTask {
                print(&quot;시작 \(i)&quot;)
                try? await Task.sleep(for: .seconds(i))
                return i
            }
        }
        
        for await element in taskGroup {
            print(&quot;완료 \(element)&quot;)
        }
    }
    print(&quot;모두 완료&quot;)
}

Task {
    await test()
}

/*
 시작 3
 시작 1
 시작 2
 한번에 호출되고
 1초 후
 완료 1
 2초 후
 완료 2
 3초 후
 완료 3
 모두 완료
 */&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jan-15-2025 15-41-42.gif&quot; data-origin-width=&quot;120&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AODq0/btsLPkdlhRV/LFQVj0kpzMkB1GaNX3kTCk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AODq0/btsLPkdlhRV/LFQVj0kpzMkB1GaNX3kTCk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AODq0/btsLPkdlhRV/LFQVj0kpzMkB1GaNX3kTCk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/AODq0/btsLPkdlhRV/LFQVj0kpzMkB1GaNX3kTCk/img.gif&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;120&quot; height=&quot;220&quot; data-filename=&quot;Jan-15-2025 15-41-42.gif&quot; data-origin-width=&quot;120&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736924814276&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func test2() async -&amp;gt; [Int] {
    await withTaskGroup(of: Int.self) { taskGroup in
        
        [3,1,2].forEach { i in
            taskGroup.addTask {
                try? await Task.sleep(for: .seconds(i))
                return i
            }
        }
        
        // TaskGroup 은 AsyncSequence 를 따르기 때문에 reduce 가능
        return await taskGroup
            .reduce(into: [Int](), { $0.append($1)} )
    }
}

Task {
    print(await test2())
    // 완료된 순서대로 [1, 2, 3] 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;AsyncSequence&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;withTaskGroup&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;withThrowingTaskGroup&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;async&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;await&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;loop&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;dispatchGroup&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>async</category>
      <category>asyncsequence</category>
      <category>await</category>
      <category>DispatchGroup</category>
      <category>ios</category>
      <category>LOOP</category>
      <category>SWIFT</category>
      <category>withtaskgroup</category>
      <category>withthrowingtaskgroup</category>
      <category>Xcode</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/102</guid>
      <comments>https://gonslab.tistory.com/102#entry102comment</comments>
      <pubDate>Tue, 1 Apr 2025 00:00:38 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI get color from image (이미지에서 평균 색상 추출)</title>
      <link>https://gonslab.tistory.com/100</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byZVEL/btsK1rMnF4Q/KeK693C9LUmN6cx0aN9quK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byZVEL/btsK1rMnF4Q/KeK693C9LUmN6cx0aN9quK/img.png&quot; data-alt=&quot;Extract Colors&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byZVEL/btsK1rMnF4Q/KeK693C9LUmN6cx0aN9quK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyZVEL%2FbtsK1rMnF4Q%2FKeK693C9LUmN6cx0aN9quK%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;652&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Extract Colors&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 이미지에서 평균 색상 추출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732868052443&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension UIImage {
    // https://www.rudrank.com/exploring-core-graphics-extract-prominent-unique-colors-uiimage/
    /// 이미지에서 대표 색상 추출 Extracts the most prominent and unique colors from the image.
    ///
    /// - Parameter numberOfColors: The number of prominent colors to extract (default is 1).
    /// - Returns: An array of UIColors representing the prominent colors.
    func extractColors(numberOfColors: Int = 1) throws -&amp;gt; [UIColor] {
        // Ensure the image has a CGImage
        guard let _ = self.cgImage else {
            throw NSError(domain: &quot;Invalid image&quot;, code: 0, userInfo: nil)
        }
        
        let size = CGSize(width: 100, height: 100 * self.size.height / self.size.width)
        UIGraphicsBeginImageContext(size)
        self.draw(in: CGRect(origin: .zero, size: size))
        guard let resizedImage = UIGraphicsGetImageFromCurrentImageContext() else {
            UIGraphicsEndImageContext()
            throw NSError(domain: &quot;Failed to resize image&quot;, code: 0, userInfo: nil)
        }
        UIGraphicsEndImageContext()
        
        guard let inputCGImage = resizedImage.cgImage else {
            throw NSError(domain: &quot;Invalid resized image&quot;, code: 0, userInfo: nil)
        }
        
        let width = inputCGImage.width
        let height = inputCGImage.height
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8
        
        guard let data = calloc(height * width, MemoryLayout&amp;lt;UInt32&amp;gt;.size) else {
            throw NSError(domain: &quot;Failed to allocate memory&quot;, code: 0, userInfo: nil)
        }
        
        defer { free(data) }
        
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
        
        guard let context = CGContext(data: data, width: width, height: height,
                                      bitsPerComponent: bitsPerComponent,
                                      bytesPerRow: bytesPerRow,
                                      space: colorSpace,
                                      bitmapInfo: bitmapInfo) else {
            throw NSError(domain: &quot;Failed to create CGContext&quot;, code: 0, userInfo: nil)
        }
        
        context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
        
        let pixelBuffer = data.bindMemory(to: UInt8.self, capacity: width * height * bytesPerPixel)
        var pixelData = [PixelData]()
        for y in 0..&amp;lt;height {
            for x in 0..&amp;lt;width {
                let offset = ((width * y) + x) * bytesPerPixel
                let r = pixelBuffer[offset]
                let g = pixelBuffer[offset + 1]
                let b = pixelBuffer[offset + 2]
                pixelData.append(PixelData(red: Double(r), green: Double(g), blue: Double(b)))
            }
        }
        
        let clusters = kMeansCluster(pixels: pixelData, k: numberOfColors)
        
        let colors = clusters.map { cluster -&amp;gt; UIColor in
            UIColor(red: CGFloat(cluster.center.red / 255.0),
                    green: CGFloat(cluster.center.green / 255.0),
                    blue: CGFloat(cluster.center.blue / 255.0),
                    alpha: 1.0)
        }
        
        return colors
    }
    
    private struct PixelData {
        let red: Double
        let green: Double
        let blue: Double
    }
    
    private struct Cluster {
        var center: PixelData
        var points: [PixelData]
    }
    
    private func kMeansCluster(pixels: [PixelData], k: Int, maxIterations: Int = 10) -&amp;gt; [Cluster] {
        var clusters = [Cluster]()
        for _ in 0..&amp;lt;k {
            if let randomPixel = pixels.randomElement() {
                clusters.append(Cluster(center: randomPixel, points: []))
            }
        }
        
        for _ in 0..&amp;lt;maxIterations {
            for clusterIndex in 0..&amp;lt;clusters.count {
                clusters[clusterIndex].points.removeAll()
            }
            
            for pixel in pixels {
                var minDistance = Double.greatestFiniteMagnitude
                var closestClusterIndex = 0
                for (index, cluster) in clusters.enumerated() {
                    let distance = euclideanDistance(pixel1: pixel, pixel2: cluster.center)
                    if distance &amp;lt; minDistance {
                        minDistance = distance
                        closestClusterIndex = index
                    }
                }
                clusters[closestClusterIndex].points.append(pixel)
            }
            
            for clusterIndex in 0..&amp;lt;clusters.count {
                let cluster = clusters[clusterIndex]
                if cluster.points.isEmpty { continue }
                let sum = cluster.points.reduce(PixelData(red: 0, green: 0, blue: 0)) { (result, pixel) -&amp;gt; PixelData in
                    return PixelData(red: result.red + pixel.red, green: result.green + pixel.green, blue: result.blue + pixel.blue)
                }
                let count = Double(cluster.points.count)
                clusters[clusterIndex].center = PixelData(red: sum.red / count, green: sum.green / count, blue: sum.blue / count)
            }
        }
        
        return clusters
    }
    
    private func euclideanDistance(pixel1: PixelData, pixel2: PixelData) -&amp;gt; Double {
        let dr = pixel1.red - pixel2.red
        let dg = pixel1.green - pixel2.green
        let db = pixel1.blue - pixel2.blue
        return sqrt(dr * dr + dg * dg + db * db)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 사용하는 쪽 뷰&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732868090647&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 색상 추출 뷰
struct ProminentColorsView: View {
    @State private var colors: [UIColor] = []
    @State private var errorMessage: String?
    private let image: UIImage = .example
    
    var body: some View {
        VStack {
            Image(uiImage: image)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .cornerRadius(16)
                .padding(.horizontal)
            
            if !colors.isEmpty {
                ColorPaletteView(colors: colors)
            }
            
            if let errorMessage = errorMessage {
                Text(&quot;Error: \(errorMessage)&quot;)
                    .foregroundColor(.red)
            }
        }
        .task {
            do {
                colors = try image.extractColors(numberOfColors: 8)
            } catch {
                errorMessage = error.localizedDescription
            }
        }
    }
}


/// 색상 팔레트 뷰
struct ColorPaletteView: View {
    let colors: [UIColor]
    
    let columns = [GridItem(.flexible()), GridItem(.flexible())]
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 16) {
                ForEach(0..&amp;lt;colors.count, id: \.self) { index in
                    let uiColor = colors[index]
                    
                    VStack {
                        Rectangle()
                            .fill(Color(uiColor))
                            .frame(height: 60)
                            .cornerRadius(12)
                        
                        Text(uiColor.toHexString())
                    }
                    .font(.caption)
                    .foregroundColor(.primary)
                }
            }
            .padding()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 색상 헥사 코드 문자열 형식&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732868154158&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension UIColor {
    /// 헥사 코드 문자열로 변환
    func toHexString() -&amp;gt; String {
        var rFloat: CGFloat = 0
        var gFloat: CGFloat = 0
        var bFloat: CGFloat = 0
        var aFloat: CGFloat = 0
        
        self.getRed(&amp;amp;rFloat, green: &amp;amp;gFloat, blue: &amp;amp;bFloat, alpha: &amp;amp;aFloat)
        
        let rInt = Int(rFloat * 255)
        let gInt = Int(gFloat * 255)
        let bInt = Int(bFloat * 255)
        
        return String(format: &quot;#%02X%02X%02X&quot;, rInt, gInt, bInt)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkMZm/btsK1VzvCuu/lHywDBI96j0asCuphGjQg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkMZm/btsK1VzvCuu/lHywDBI96j0asCuphGjQg0/img.png&quot; data-alt=&quot;Extract Colors&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkMZm/btsK1VzvCuu/lHywDBI96j0asCuphGjQg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkMZm%2FbtsK1VzvCuu%2FlHywDBI96j0asCuphGjQg0%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;652&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Extract Colors&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Extract&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIColor&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIImage&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIGraphicsBeginImageContext&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;CGColorSpaceCreateDeviceRGB&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;k-means&amp;nbsp;클러스터링&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Clustering&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;CGImageAlphaInfo&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;premultipliedLast&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;bindMemory&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;hex&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;pixel&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>bindmemory</category>
      <category>cgcolorspacecreatedevicergb</category>
      <category>cgimagealphainfo</category>
      <category>clustering</category>
      <category>extract</category>
      <category>HEX</category>
      <category>k-means</category>
      <category>Pixel</category>
      <category>premultipliedlast</category>
      <category>UIGraphicsBeginImageContext</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/100</guid>
      <comments>https://gonslab.tistory.com/100#entry100comment</comments>
      <pubDate>Sat, 1 Mar 2025 00:00:09 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI View Aligned Carousel Scrollview Paging (다음 뷰 약간 보이는 페이징 스크롤)</title>
      <link>https://gonslab.tistory.com/99</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx4aUv/btsKA717i3e/UurU9mScp47a7aUiR40KoK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx4aUv/btsKA717i3e/UurU9mScp47a7aUiR40KoK/img.gif&quot; data-alt=&quot;View Aligned Carousel Scrollview Paging&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx4aUv/btsKA717i3e/UurU9mScp47a7aUiR40KoK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bx4aUv/btsKA717i3e/UurU9mScp47a7aUiR40KoK/img.gif&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;420&quot; height=&quot;420&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;View Aligned Carousel Scrollview Paging&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731048129695&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://developer.apple.com/documentation/swiftui/scrolltargetbehavior/viewaligned
/// 다음 뷰 약간 보이는 페이징 스크롤
struct ContentView: View {
    
    var items: [Color] = [.yellow, .green, .blue, .brown, .orange, .red]
    
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: 20) {
                ForEach(items, id: \.self) { item in
                    item
                        .frame(width: 300, height: 300)
                }
            }
            .scrollTargetLayout() // &amp;lt;-
        }
        .scrollTargetBehavior(.viewAligned) // &amp;lt;-
        .contentMargins(20)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;viewAligned&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Carousel&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Scrollview&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Paging&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;scrollTargetBehavior&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;scrollTargetLayout&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>AppStore</category>
      <category>Carousel</category>
      <category>Horizontal</category>
      <category>paging</category>
      <category>ScrollTargetBehavior</category>
      <category>scrolltargetlayout</category>
      <category>scrolltransition</category>
      <category>scrollview</category>
      <category>viewaligned</category>
      <category>수평</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/99</guid>
      <comments>https://gonslab.tistory.com/99#entry99comment</comments>
      <pubDate>Sat, 1 Feb 2025 00:00:55 +0900</pubDate>
    </item>
    <item>
      <title>2024년 회고와 2025년 다짐</title>
      <link>https://gonslab.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 &lt;b&gt;Gons&lt;/b&gt; 입니다.&lt;br /&gt;또 한 해가 지나갔네요.&lt;br /&gt;정말 정신없이 보낸 것 같아요.&lt;br /&gt;개인적으로 정말 힘든 일이 있었는데 잘 이겨내지 못하고 여전히 슬프게 버티고 있습니다.&lt;br /&gt;그럼에도 불구하고 개발적으로는 열심히 성장을 한 것 같아요.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2024년을 돌아보며...&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난해에는 SwiftUI 를 많이 공부했습니다.&lt;br /&gt;실무에서 사용하기로 마음먹고부터 본격적으로 학습을 시작했는데요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 19.png&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNRkA/btsLRpf4vhO/8UUf1R6CHnxdrZoUxaKIy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNRkA/btsLRpf4vhO/8UUf1R6CHnxdrZoUxaKIy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNRkA/btsLRpf4vhO/8UUf1R6CHnxdrZoUxaKIy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNRkA%2FbtsLRpf4vhO%2F8UUf1R6CHnxdrZoUxaKIy0%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;385&quot; data-filename=&quot;Frame 19.png&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 해 동안 8개의 사이드 프로젝트를 만들어서 출시 했습니다.&lt;br /&gt;UIKit 으로 되어 있는 회사 프로젝트도 리팩토링을 시작하여 50% 이상 SwiftUI 로 교체를 했습니다.&lt;br /&gt;그리고 처음으로 iOS 컨퍼런스도 참가를 해봤습니다.&lt;br /&gt;KWDC24 , Let'Swift&amp;nbsp;2024 이렇게 2개를 참가했는데요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dElB9e/btsLPlxURPC/1QKWIry2xX0zaB6Pn6rRBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dElB9e/btsLPlxURPC/1QKWIry2xX0zaB6Pn6rRBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dElB9e/btsLPlxURPC/1QKWIry2xX0zaB6Pn6rRBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdElB9e%2FbtsLPlxURPC%2F1QKWIry2xX0zaB6Pn6rRBk%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;200&quot; height=&quot;356&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 기술, 최신 트렌드도 알게 되고 정말 유익한 시간이었던 것 같아요.&lt;br /&gt;처음으로 네트워킹도 해봤는데 여러 iOS 개발자들과 정보도 공유하고 많은 인사이트를 얻을 수 있었습니다.&lt;br /&gt;기술블로그도 한달에 하나씩은 꼭 글을 쓰자는 마음으로 한 해 동안 14개의 포스팅을 했네요.&lt;br /&gt;나름 SwiftUI 사용하게 되어서 뭐라도 얻은 한 해였던 것 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2025년을 준비하며&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올 해에는 크로스 플랫폼 기술을 한번 공부해보고 싶습니다.&lt;br /&gt;대기업이 아닌 이상 더 이상 네이티브가 메리트가 없는 것 같아서요.&lt;br /&gt;Flutter 나 React Native 를 한번 학습해보겠습니다.&lt;br /&gt;그럼 화이팅!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NkakU/btsLPPebfr8/0XwPRjmxjfpKSZ56246YL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NkakU/btsLPPebfr8/0XwPRjmxjfpKSZ56246YL1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NkakU/btsLPPebfr8/0XwPRjmxjfpKSZ56246YL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNkakU%2FbtsLPPebfr8%2F0XwPRjmxjfpKSZ56246YL1%2Fimg.jpg&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;735&quot; height=&quot;589&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Android&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>android</category>
      <category>ios</category>
      <category>SWIFT</category>
      <category>Xcode</category>
      <category>건강</category>
      <category>다짐</category>
      <category>도전</category>
      <category>목표</category>
      <category>성장</category>
      <category>회고</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/101</guid>
      <comments>https://gonslab.tistory.com/101#entry101comment</comments>
      <pubDate>Wed, 15 Jan 2025 15:12:00 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Custom Segmented Control Flexible Width (커스텀 세그먼트 컨트롤, TabView TabMenu, TabBar) 동적 너비 버전</title>
      <link>https://gonslab.tistory.com/98</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image_SegmentedFlexibleView2-ezgif.com-crop.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYU8xW/btsKzMI4gxa/LCjLlV4pAIRXfdc5xowTa1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYU8xW/btsKzMI4gxa/LCjLlV4pAIRXfdc5xowTa1/img.gif&quot; data-alt=&quot;Custom &amp;amp;nbsp;Segmented Control&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYU8xW/btsKzMI4gxa/LCjLlV4pAIRXfdc5xowTa1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cYU8xW/btsKzMI4gxa/LCjLlV4pAIRXfdc5xowTa1/img.gif&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;420&quot; height=&quot;420&quot; data-filename=&quot;image_SegmentedFlexibleView2-ezgif.com-crop.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Custom &amp;nbsp;Segmented Control&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1730943244006&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

/// 커스텀 세그먼트 뷰 (컨텐츠 크기만큼 동적 너비 버전)
struct SegmentedFlexibleView: View {
    
    let segments: [String]
    @Binding var currentPage: Int
    @Namespace private var name
    
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 0) {
                    ForEach(segments.indices, id: \.self) { index in
                        Button {
                            currentPage = index
                        } label: {
                            VStack {
                                Text(segments[index])
                                    .font(.footnote)
                                    .fontWeight(.medium)
                                    .foregroundStyle(currentPage == index ? .orange : Color(uiColor: .systemGray))
                                ZStack {
                                    Capsule()
                                        .fill(.clear)
                                        .frame(height: 4)
                                    if currentPage == index {
                                        Capsule()
                                            .fill(.orange)
                                            .frame(height: 4)
                                            .matchedGeometryEffect(id: &quot;Tab&quot;, in: name)
                                    }
                                }
                            }
                            .padding(.horizontal, 8) // 각 항목의 양쪽 여백 추가
                        }
                        .id(index)
                    }
                }
            }
            .onChange(of: currentPage) { _, newValue in
                withAnimation {
                    proxy.scrollTo(newValue, anchor: .center)
                }
            }
        }
        .animation(.spring(duration: 0.3), value: currentPage)
    }
}

#Preview {
    struct Preview: View {
        var segments: [Color] = [.red, .green, .blue, .brown, .purple, .pink, .yellow, .orange, .cyan, .teal, .gray, .black]
        
        @State var currentPage = 0
        
        var body: some View {
            VStack {
                SegmentedFlexibleView(segments: segments.map { &quot;\($0)&quot; },
                                      currentPage: $currentPage)
                
                TabView(selection: $currentPage) {
                    ForEach(segments.indices, id: \.self) { index in
                        segments[index]
                    }
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
                .scrollTargetBehavior(.paging)
            }
            .ignoresSafeArea(.all, edges: .bottom)
        }
    }
    return Preview()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UISegmentedControl&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Picker&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;탭뷰&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;탭뷰&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;탭메뉴&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>custom segment</category>
      <category>ios</category>
      <category>picker</category>
      <category>programmatically</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>TabBar</category>
      <category>tabmenu</category>
      <category>TabView</category>
      <category>UISegmentedControl</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/98</guid>
      <comments>https://gonslab.tistory.com/98#entry98comment</comments>
      <pubDate>Wed, 1 Jan 2025 00:01:38 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Custom Segmented Control Fixed Width (커스텀 세그먼트 컨트롤, TabView TabMenu, TabBar) 크기 고정 버전</title>
      <link>https://gonslab.tistory.com/97</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image_SegmentedFixedView2-ezgif.com-crop.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuUEyH/btsKymLPu6G/sbbtWnBg3MDhubr3rQvgH0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuUEyH/btsKymLPu6G/sbbtWnBg3MDhubr3rQvgH0/img.gif&quot; data-alt=&quot;Custom Segmented Control&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuUEyH/btsKymLPu6G/sbbtWnBg3MDhubr3rQvgH0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cuUEyH/btsKymLPu6G/sbbtWnBg3MDhubr3rQvgH0/img.gif&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;420&quot; height=&quot;420&quot; data-filename=&quot;image_SegmentedFixedView2-ezgif.com-crop.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Custom Segmented Control&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730942553536&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

/// 커스텀 세그먼트 뷰 (가로 꽉 채우는 고정 너비 버전)
struct SegmentedFixedView: View {
    
    let segments: [String]
    @Binding var currentPage: Int
    @Namespace private var name
    
    var body: some View {
        HStack(spacing: 0) {
            ForEach(segments.indices, id: \.self) { index in
                Button {
                    currentPage = index
                } label: {
                    VStack {
                        Text(segments[index])
                            .font(.footnote)
                            .fontWeight(.medium)
                            .foregroundStyle(currentPage == index ? .orange : Color(uiColor: .systemGray))
                        ZStack {
                            Capsule()
                                .fill(.clear)
                                .frame(height: 4)
                            if currentPage == index {
                                Capsule()
                                    .fill(.orange)
                                    .frame(height: 4)
                                    .matchedGeometryEffect(id: &quot;Tab&quot;, in: name)
                            }
                        }
                    }
                }
            }
        }
        .animation(.spring(duration: 0.3), value: currentPage)
    }
}

#Preview {
    struct Preview: View {
        var segments: [Color] = [.red, .green, .blue]
        
        @State var currentPage = 0
        
        var body: some View {
            VStack {
                SegmentedFixedView(segments: segments.map { &quot;\($0)&quot; },
                                   currentPage: $currentPage)
                
                TabView(selection: $currentPage) {
                    ForEach(segments.indices, id: \.self) { index in
                        segments[index]
                    }
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
                .scrollTargetBehavior(.paging)
            }
            .ignoresSafeArea(.all, edges: .bottom)
        }
    }
    return Preview()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@c64midi/android-style-segmented-control-in-swiftui-f2d9e8469bdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@c64midi/android-style-segmented-control-in-swiftui-f2d9e8469bdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UISegmentedControl&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Picker&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;탭뷰&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;탭뷰&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;탭메뉴&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>custom segment</category>
      <category>ios</category>
      <category>picker</category>
      <category>programmatically</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>TabBar</category>
      <category>tabmenu</category>
      <category>TabView</category>
      <category>UISegmentedControl</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/97</guid>
      <comments>https://gonslab.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 1 Jan 2025 00:00:00 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI AudioPlayer 오디오 플레이어 (Music 음악)</title>
      <link>https://gonslab.tistory.com/96</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/befEeT/btsJt5RRaSj/YRzSAXORQ2A0aRWGUU6pUK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/befEeT/btsJt5RRaSj/YRzSAXORQ2A0aRWGUU6pUK/img.gif&quot; data-alt=&quot;AudioPlayer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/befEeT/btsJt5RRaSj/YRzSAXORQ2A0aRWGUU6pUK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/befEeT/btsJt5RRaSj/YRzSAXORQ2A0aRWGUU6pUK/img.gif&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;380&quot; height=&quot;380&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AudioPlayer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1725701887378&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://ahmetkasimnazli.medium.com/creating-an-audio-player-with-avkit-in-swiftui-a17f3b704fad
// 오디오 플레이어
struct ContentView: View {
    @State var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            HStack {
                Button {
                    if viewModel.isPlaying {
                        viewModel.pause()
                    } else {
                        viewModel.play()
                    }
                } label: {
                    Image(systemName: viewModel.isPlaying ? &quot;pause.circle&quot; : &quot;play.circle&quot;)
                        .font(.largeTitle)
                }
                
                Slider(value: $viewModel.currentTime, in: 0...viewModel.totalTime) { editing in
                    if editing {
                        if viewModel.isPlaying {
                            viewModel.pause()
                        }
                    } else {
                        viewModel.player?.currentTime = viewModel.currentTime
                        viewModel.play()
                    }
                }
            }
            
            HStack {
                Text(&quot;\(formatTime(viewModel.currentTime))&quot;)
                Spacer()
                Text(&quot;\(formatTime(viewModel.totalTime))&quot;)
            }
        }
        .animation(.linear(duration: 0.1), value: viewModel.currentTime)
        .padding()
    }
    
    private func formatTime(_ time: TimeInterval) -&amp;gt; String {
        let seconds = Int(time) % 60
        let minutes = Int(time) / 60
        return String(format: &quot;%02d:%02d&quot;, minutes, seconds)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1725701906818&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation
import AVKit

/// 오디오 뷰모델
@Observable final class ViewModel {
    
    var player: AVAudioPlayer?
    var isPlaying = false
    var totalTime: TimeInterval = 0.0
    var currentTime: TimeInterval = 0.0
    var timer: Timer?
    
    init() {
        guard let url = Bundle.main.url(forResource: &quot;sample&quot;, withExtension: &quot;wav&quot;) else { return }
        setupAudio(withURL: url)
    }
    
    private func setupAudio(withURL url: URL) {
        do {
            // 무음모드에서도 재생되도록 설정
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
            try AVAudioSession.sharedInstance().setActive(true) // 오디오 세션 활성화. 앱이 백그라운드로 이동하거나 중단되었을 때, 다시 활성화해야함
            
            player = try AVAudioPlayer(contentsOf: url)
            player?.prepareToPlay()
            totalTime = player?.duration ?? 0.0
        } catch {
            print(&quot;Error loading audio: \(error)&quot;)
        }
    }
    
    func play() {
        guard let player else { return }
        isPlaying = true
        player.play()
        startTimer()
    }
    
    func pause() {
        guard let player else { return }
        isPlaying = false
        player.pause()
        stopTimer()
    }
    
    private func startTimer() {
        stopTimer()
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
            self?.updateProgress()
        }
    }
    
    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
    
    private func updateProgress() {
        guard let player else { return }
        
        currentTime = player.currentTime
        
        if !player.isPlaying {
            pause()
            currentTime = 0
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Audio Player&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Music Player&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;음악 재생기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;노래 플레이어&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;mttContainer&quot; class=&quot;notranslate&quot; style=&quot;transform: translate(858px, 355px);&quot; aria-expanded=&quot;true&quot;&gt;
&lt;div id=&quot;tippy-1&quot; style=&quot;z-index: 100000200; visibility: visible; position: absolute; inset: 0px auto auto 0px; margin: 0px; transform: translate3d(315px, 20px, 0px);&quot; data-tippy-root=&quot;&quot;&gt;
&lt;div class=&quot;tippy-box&quot; style=&quot;max-width: 350px; transition-duration: 300ms;&quot; tabindex=&quot;-1&quot; role=&quot;mtttooltip&quot; data-state=&quot;visible&quot; data-theme=&quot;custom&quot; data-animation=&quot;fade&quot; data-placement=&quot;bottom&quot;&gt;
&lt;div class=&quot;tippy-content&quot; style=&quot;transition-duration: 300ms;&quot; data-state=&quot;visible&quot;&gt;&lt;span&gt;// https://ahmetkasimnazli.medium.com/creating-an-audio-player-with-avkit-in-swiftui-a17f3b704fad // 오디오 플레이어 struct ContentView: View { @State var viewModel = ViewModel() var body: some View { VStack { HStack { Button { if viewModel.isPlaying { viewModel.pause() } else { viewModel.play() } } label: { Image(systemName: viewModel.isPlaying ? &quot;pause.circle&quot; : &quot;play.circle&quot; ) .font(.largeTitle) } Slider(값: $viewModel.currentTime, in: 0...viewModel.totalTime) { 편집 중 if 편집 { if viewModel.isPlaying { viewModel.pause() } } else { viewModel.player ?.currentTime = viewModel.currentTime viewModel.play() } } } HStack { Text(&quot;\(formatTime(viewModel.currentTime))&quot;) Spacer() Text(&quot;\(formatTime(viewModel.totalTime))&quot;) } } .animation(.linear(duration: 0.1), value: viewModel.currentTime) .padding() } private func formatTime(_ time: TimeInterval) -&amp;gt; String { let 초 = Int(time) % 60 let 분 = Int(time ) / 60 return String(형식: &quot;%02d:%02d&quot;, 분, 초) } }&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;tippy-arrow&quot; style=&quot;position: absolute; left: 0px; transform: translate3d(176px, 0px, 0px);&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>iOS SwiftUI</category>
      <category>AVAudioPlayer</category>
      <category>AVAudioSession</category>
      <category>AVFoundation</category>
      <category>AVKit</category>
      <category>avplayer</category>
      <category>Playback</category>
      <category>preparetoplay</category>
      <category>뮤직 플레이어</category>
      <category>오디오</category>
      <category>재생기</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/96</guid>
      <comments>https://gonslab.tistory.com/96#entry96comment</comments>
      <pubDate>Sun, 1 Dec 2024 00:00:38 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI 팝오버 모달 (Popover Modal Tooltip Dropdown)</title>
      <link>https://gonslab.tistory.com/95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yrROi/btsJtIbvyhw/dGdXw8jwY9ehRDrnyiY1b1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yrROi/btsJtIbvyhw/dGdXw8jwY9ehRDrnyiY1b1/img.gif&quot; data-alt=&quot;SwiftUI Popover&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yrROi/btsJtIbvyhw/dGdXw8jwY9ehRDrnyiY1b1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/yrROi/btsJtIbvyhw/dGdXw8jwY9ehRDrnyiY1b1/img.gif&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;300&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SwiftUI Popover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gonslab.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UIKit 버전 보러가기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725698727210&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

// 팝오버
struct ContentView: View {
    
    @State private var showPopover = false
    
    var body: some View {
        Button(&quot;Popover&quot;) {
            showPopover.toggle()
        }
        .popover(isPresented: $showPopover) {
            PopoverView()
                .presentationCompactAdaptation(.popover)
        }
    }
}

struct PopoverView: View {
    var body: some View {
        Text(&quot;팝오버&quot;)
    }
}

#Preview {
    ContentView()
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;팝오버&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;모달&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;툴팁&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;드롭다운&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;말풍선&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;채팅&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;chat&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Speech&amp;nbsp;bubble&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;셀렉트 박스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Select Box&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIKit&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>iOS SwiftUI</category>
      <category>arrowedge</category>
      <category>attachmentanchor</category>
      <category>ios</category>
      <category>popover</category>
      <category>presentationcompactadaptation</category>
      <category>speech bubble</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>말풍선</category>
      <category>팝오버</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/95</guid>
      <comments>https://gonslab.tistory.com/95#entry95comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:01:08 +0900</pubDate>
    </item>
    <item>
      <title>iOS UIKit 팝오버 모달 (Popover Modal Tooltip Dropdown)</title>
      <link>https://gonslab.tistory.com/94</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6jjzQ/btsJt791Fi4/VxfrqKfUlZrXPsqSFZKkS1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6jjzQ/btsJt791Fi4/VxfrqKfUlZrXPsqSFZKkS1/img.gif&quot; data-alt=&quot;UIKit Popover&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6jjzQ/btsJt791Fi4/VxfrqKfUlZrXPsqSFZKkS1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b6jjzQ/btsJt791Fi4/VxfrqKfUlZrXPsqSFZKkS1/img.gif&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;300&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UIKit Popover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gonslab.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SwiftUI 버전 보러가기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725698133511&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

// 팝오버
class ViewController: UIViewController {
    
    let button = UIButton(type: .system)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        button.setTitle(&quot;Popover&quot;, for: .normal)
        
        let action = UIAction(handler: showPopover)
        button.addAction(action, for: .touchUpInside)
        
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    func showPopover(action: UIAction) {
        let viewController = UIViewController()
        viewController.view.backgroundColor = .systemCyan
        
        viewController.preferredContentSize = .init(width: 200, height: 100)
        viewController.modalPresentationStyle = .popover
        
        let popoverPresentationController = viewController.popoverPresentationController
        popoverPresentationController?.permittedArrowDirections = [.down]
        popoverPresentationController?.sourceRect = button.bounds
        popoverPresentationController?.sourceView = button
        popoverPresentationController?.delegate = self // 필수
        present(viewController, animated: true)
    }
}

extension UIViewController: UIPopoverPresentationControllerDelegate {
    // Popover 스타일 표시하려면 필수
    public func adaptivePresentationStyle(for controller: UIPresentationController) -&amp;gt; UIModalPresentationStyle {
        .none
    }
}

#Preview {
    ViewController()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;팝오버&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;모달&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;툴팁&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;드롭다운&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;말풍선&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;채팅&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;chat&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Speech&amp;nbsp;bubble&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;셀렉트 박스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Select Box&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIKit&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>adaptivepresentationstyle</category>
      <category>modalPresentationStyle</category>
      <category>permittedarrowdirections</category>
      <category>popover</category>
      <category>popoverpresentationcontroller</category>
      <category>preferredcontentsize</category>
      <category>presentationcompactadaptation</category>
      <category>sourcerect</category>
      <category>sourceview</category>
      <category>uipopoverpresentationcontrollerdelegate</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/94</guid>
      <comments>https://gonslab.tistory.com/94#entry94comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:00:38 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Pie Chart (iOS 17 미만 버전) 파이 차트, 원형 그래프, Donut 도넛</title>
      <link>https://gonslab.tistory.com/93</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUBEVr/btsIrJWlC8e/CkwP9Oy3NoL4s8HjbhQRhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUBEVr/btsIrJWlC8e/CkwP9Oy3NoL4s8HjbhQRhK/img.png&quot; data-alt=&quot;Pie Chart&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUBEVr/btsIrJWlC8e/CkwP9Oy3NoL4s8HjbhQRhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUBEVr%2FbtsIrJWlC8e%2FCkwP9Oy3NoL4s8HjbhQRhK%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;280&quot; height=&quot;280&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pie Chart&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bajSuA/btsIrsm12jk/kTKIsgf3AtWe8CMDxzKJyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bajSuA/btsIrsm12jk/kTKIsgf3AtWe8CMDxzKJyk/img.png&quot; data-alt=&quot;Donut Chart&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bajSuA/btsIrsm12jk/kTKIsgf3AtWe8CMDxzKJyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbajSuA%2FbtsIrsm12jk%2FkTKIsgf3AtWe8CMDxzKJyk%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;280&quot; height=&quot;280&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Donut Chart&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720501116829&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://medium.com/@iOSchandra0/how-to-create-a-pie-chart-in-swiftui-c7f056d54c81
// iOS 17 미만을 위한 원형 차트
/// 꽉차있는 일반적인 파이 버전
struct PieChart: View {
    @State var slices: [(Double, Color)]
    
    var body: some View {
        Canvas { context, size in
            let total = slices.reduce(0) { $0 + $1.0 }
            context.translateBy(x: size.width * 0.5, y: size.height * 0.5)
            var pieContext = context
            pieContext.rotate(by: .degrees(-90))
            let radius = min(size.width, size.height) * 0.48
            var startAngle = Angle.zero
            for (value, color) in slices {
                let angle = Angle(degrees: 360 * (value / total))
                let endAngle = startAngle + angle
                let path = Path { p in
                    p.move(to: .zero)
                    p.addArc(center: .zero, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
                    p.closeSubpath()
                }
                pieContext.fill(path, with: .color(color))
                startAngle = endAngle
            }
        }
        .aspectRatio(1, contentMode: .fit)
    }
}

/// 중간에 비어있고 데이터 사이사이 갭 있는 도넛 버전
struct PieChart2: View {
    @State var slices: [(Double, Color)]
    
    var body: some View {
        Canvas { context, size in
            // Add these lines to display as Donut
            // Start Donut
            let donut = Path { p in
                p.addEllipse(in: CGRect(origin: .zero, size: size))
                p.addEllipse(in: CGRect(x: size.width * 0.25, y: size.height * 0.25, width: size.width * 0.5, height: size.height * 0.5))
            }
            context.clip(to: donut, style: .init(eoFill: true))
            //End Donut
            let total = slices.reduce(0) { $0 + $1.0 }
            context.translateBy(x: size.width * 0.5, y: size.height * 0.5)
            var pieContext = context
            pieContext.rotate(by: .degrees(-90))
            let radius = min(size.width, size.height) * 0.48
            let gapSize = Angle(degrees: 5) // size of the gap between slices in degrees
            
            var startAngle = Angle.zero
            for (value, color) in slices {
                let angle = Angle(degrees: 360 * (value / total))
                let endAngle = startAngle + angle
                let path = Path { p in
                    p.move(to: .zero)
                    p.addArc(center: .zero, radius: radius, startAngle: startAngle + gapSize / 2, endAngle: endAngle, clockwise: false)
                    p.closeSubpath()
                }
                pieContext.fill(path, with: .color(color))
                startAngle = endAngle
            }
        }
        .aspectRatio(1, contentMode: .fit)
    }
}

#Preview {
    let data: [(Double, Color)] = [
        (2, .red),
        (3, .orange),
        (4, .yellow),
        (1, .green),
        (5, .blue),
        (4, .indigo),
        (2, .purple)
    ]
    return VStack {
        PieChart(slices: data)
        PieChart2(slices: data)
    }
    .padding()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;출처&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@iOSchandra0/how-to-create-a-pie-chart-in-swiftui-c7f056d54c81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/@iOSchandra0/how-to-create-a-pie-chart-in-swiftui-c7f056d54c81&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>angle</category>
      <category>Canvas</category>
      <category>donut chart</category>
      <category>ios</category>
      <category>Path</category>
      <category>pie chart</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>원형 그래프</category>
      <category>파이 차트</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/93</guid>
      <comments>https://gonslab.tistory.com/93#entry93comment</comments>
      <pubDate>Tue, 1 Oct 2024 00:00:24 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Radar Chart 방사형 차트 (육각 graph, 오각 그래프)</title>
      <link>https://gonslab.tistory.com/92</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXUych/btsIrICXRx1/90pmfB3TdB1NJg6wTzXBOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXUych/btsIrICXRx1/90pmfB3TdB1NJg6wTzXBOK/img.png&quot; data-alt=&quot;Radar Chart&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXUych/btsIrICXRx1/90pmfB3TdB1NJg6wTzXBOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXUych%2FbtsIrICXRx1%2F90pmfB3TdB1NJg6wTzXBOK%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;280&quot; height=&quot;280&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Radar Chart&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZkiXQ/btsIsudfzQs/BFyKk2N7Ob3pWbTdIGYCV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZkiXQ/btsIsudfzQs/BFyKk2N7Ob3pWbTdIGYCV0/img.png&quot; data-alt=&quot;방사형 차트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZkiXQ/btsIsudfzQs/BFyKk2N7Ob3pWbTdIGYCV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZkiXQ%2FbtsIsudfzQs%2FBFyKk2N7Ob3pWbTdIGYCV0%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;280&quot; height=&quot;280&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;방사형 차트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720498465031&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://github.com/TheAppWizard/RadarChart
/// 방사형 차트
struct RadarChart: View {
    let data: [Double]
    let labels: [String]
    let maxValues: [Double]
    let shapeColor: Color
    let frameHeight: CGFloat?
    let frameWidth: CGFloat?
    
    init(data: [Double], labels: [String], maxValues: [Double], shapeColor: Color, frameHeight: CGFloat? = nil, frameWidth: CGFloat? = nil) {
        self.data = data
        self.labels = labels
        self.maxValues = maxValues
        self.shapeColor = shapeColor
        self.frameHeight = frameHeight
        self.frameWidth = frameWidth
    }
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                RadarChartShape(data: data, maxValues: maxValues, frameSize: geometry.size, shapeColor: shapeColor)
                RadarChartLabels(labels: labels, frameSize: geometry.size)
            }
        }
        .frame(width: frameWidth, height: frameHeight)
    }
}

struct RadarChartShape: View {
    let data: [Double]
    let maxValues: [Double]
    let frameSize: CGSize
    let shapeColor: Color
    
    var body: some View {
        ZStack {
            Path { path in
                let radius = min(frameSize.width, frameSize.height) / 2
                let center = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
                let angle = 2 * .pi / Double(data.count)
                
                for i in 0..&amp;lt;data.count {
                    let currentAngle = Double(i) * angle - .pi / 2
                    let x = center.x + CGFloat(radius * cos(currentAngle))
                    let y = center.y + CGFloat(radius * sin(currentAngle))
                    
                    if i == 0 {
                        path.move(to: CGPoint(x: x, y: y))
                    } else {
                        path.addLine(to: CGPoint(x: x, y: y))
                    }
                }
                path.closeSubpath()
            }
            .stroke(Color.primary.opacity(0.5), lineWidth: 2)
            
            
            
            // Lines from vertices to center
            let radius = min(frameSize.width, frameSize.height) / 2
            let center = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
            let angle = 2 * .pi / Double(data.count)
            ForEach(0..&amp;lt;data.count, id: \.self) { i in
                let currentAngle = Double(i) * angle - .pi / 2
                let x = center.x + CGFloat(radius * cos(currentAngle))
                let y = center.y + CGFloat(radius * sin(currentAngle))
                
                Path { path in
                    path.move(to: center)
                    path.addLine(to: CGPoint(x: x, y: y))
                }
                .stroke(Color.primary.opacity(0.5), lineWidth: 1)
            }
            
            
            ForEach(0..&amp;lt;data.count, id: \.self) { index in
                let radius = min(frameSize.width, frameSize.height) / 2
                let center = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
                let angle = 2 * .pi / Double(data.count)
                
                let currentAngle = Double(index) * angle - .pi / 2
                let currentRadius = (data[index] / maxValues[index]) * radius
                let x = center.x + CGFloat(currentRadius * cos(currentAngle))
                let y = center.y + CGFloat(currentRadius * sin(currentAngle))
                
                Circle()
                    .fill(shapeColor)
                    .frame(width: 10, height: 10)
                    .position(x: x, y: y)
            }
            
            Path { path in
                let radius = min(frameSize.width, frameSize.height) / 2
                let center = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
                let angle = 2 * .pi / Double(data.count)
                
                for i in 0..&amp;lt;data.count {
                    let currentAngle = Double(i) * angle - .pi / 2
                    let currentRadius = (data[i] / maxValues[i]) * radius
                    let x = center.x + CGFloat(currentRadius * cos(currentAngle))
                    let y = center.y + CGFloat(currentRadius * sin(currentAngle))
                    
                    if i == 0 {
                        path.move(to: CGPoint(x: x, y: y))
                    } else {
                        path.addLine(to: CGPoint(x: x, y: y))
                    }
                }
                path.closeSubpath()
            }
            .stroke(shapeColor.opacity(0.5), lineWidth: 1)
            //.fill(shapeColor.opacity(0.3)) // 레이더 색 채우기 is only available in iOS 17.0 or newer
        }
    }
}

struct RadarChartLabels: View {
    let labels: [String]
    let frameSize: CGSize
    
    var body: some View {
        let radius = min(frameSize.width, frameSize.height) / 2
        let angle = 2 * .pi / Double(labels.count)
        
        ForEach(0..&amp;lt;labels.count, id: \.self) { index in
            let currentAngle = Double(index) * angle - .pi / 2
            let x = frameSize.width / 2 + (radius + 20) * CGFloat(cos(currentAngle))
            let y = frameSize.height / 2 + (radius + 20) * CGFloat(sin(currentAngle))
            
            Text(labels[index])
                .foregroundColor(.primary)
                .font(.system(size: 20))
                .fontWeight(.bold)
                .position(x: x, y: y)
        }
    }
}

#Preview {
    struct Preview: View {
        @State private var firstValue: Double = 3
        @State private var secondValue: Double = 2
        @State private var thirdValue: Double = 2
        @State private var fourthValue: Double = 3
        @State private var fifthValue: Double = 2
        @State private var sixthValue: Double = 2.5
        
        let labels: [String] =
        [&quot;First&quot;, &quot;Second&quot;, &quot;Third&quot;, &quot;Fourth&quot;, &quot;Fifth&quot;, &quot;Sixth&quot;]
        let maxValues: [Double] = [3, 3, 3, 3, 3, 3]
        let shapeColor: Color = .orange
        
        var data: [Double] {
            [firstValue, secondValue, thirdValue, fourthValue, fifthValue, sixthValue]
        }
        
        var body: some View {
            RadarChart(data: data, labels: labels, maxValues: maxValues, shapeColor: shapeColor)
                .frame(height: 250)
        }
    }
    
    return Preview()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TheAppWizard/RadarChart&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/TheAppWizard/RadarChart&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;mttContainer&quot; class=&quot;notranslate&quot; style=&quot;transform: translate(555px, 4439px);&quot; aria-expanded=&quot;false&quot;&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>iOS SwiftUI</category>
      <category>Area</category>
      <category>Bar</category>
      <category>line</category>
      <category>Path</category>
      <category>point</category>
      <category>RADAR CHART</category>
      <category>Rectangle</category>
      <category>rule</category>
      <category>레이더 차트</category>
      <category>방사형 차트</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/92</guid>
      <comments>https://gonslab.tistory.com/92#entry92comment</comments>
      <pubDate>Sun, 1 Sep 2024 00:00:01 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI Custom Slider 커스텀 슬라이더 (제어센터 UI)</title>
      <link>https://gonslab.tistory.com/91</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/98A2n/btsIrLyvVDE/O7VQxFOqEIjhKwlTYEmRu1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/98A2n/btsIrLyvVDE/O7VQxFOqEIjhKwlTYEmRu1/img.gif&quot; data-alt=&quot;Custom Slider&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/98A2n/btsIrLyvVDE/O7VQxFOqEIjhKwlTYEmRu1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/98A2n/btsIrLyvVDE/O7VQxFOqEIjhKwlTYEmRu1/img.gif&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;200&quot; height=&quot;200&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Custom Slider&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720409666730&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://nuli.navercorp.com/community/article/1133220
/// 커스텀 슬라이더 (제어센터 UI)
struct CustomSlider: View {
    enum Orientation { case vertical, horizontal }
    @Binding var value: Double
    @State private var percentRate: Double = 0
    var label: String = &quot;&quot;
    var min: Double = 0
    var max: Double = 100
    var orientation: Orientation = .horizontal
    var trackColor: Color?
    var sliderColor: Color?
    /// 간격
    var step: Double = 1
    
    private var labelIsHidden: Bool {
        label.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }
    
    var body: some View {
        let layout = orientation == .horizontal ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
        layout {
            if orientation == .horizontal &amp;amp;&amp;amp; !labelIsHidden {
                Text(&quot;**\(label)**&quot;).dynamicTypeSize(.large) // ** markdown 볼드체, dynamicTypeSize 시스템 설정에 따라 글꼴 크기 자동 조절
            }
            GeometryReader { reader in
                ZStack (alignment: .bottomLeading) {
                    RoundedRectangle(cornerRadius: 12)
                        .foregroundStyle(trackColor ?? Color.primary)
                    
                    if orientation == .horizontal {
                        Rectangle().frame(width: reader.size.width * percentRate)
                            .foregroundStyle(sliderColor ?? Color.accentColor)
                    } else {
                        Rectangle().frame(height: reader.size.height * percentRate)
                            .foregroundStyle(sliderColor ?? Color.accentColor)
                    }
                }.gesture(DragGesture(minimumDistance: 0).onChanged { act in
                    if orientation == .horizontal {
                        let tr = act.location.x &amp;gt; reader.size.width ? reader.size.width : (
                            act.location.x &amp;lt; 0 ? 0 : act.location.x)
                        let rawValue = max * (tr / reader.size.width)
                        value = round(rawValue / step) * step // 값을 step 간격으로 반올림
                    } else if orientation == .vertical {
                        let tr = act.location.y &amp;gt; reader.size.height ? reader.size.height : (
                            act.location.y &amp;lt; 0 ? 0 : act.location.y)
                        let rawValue = max * (1.0 - tr / reader.size.height)
                        value = round(rawValue / step) * step // 값을 step 간격으로 반올림
                    }
                })
            }.clipShape(RoundedRectangle(cornerRadius: 12))
                .frame(width: orientation == .horizontal ? 120 : 40, height: orientation == .horizontal ? 40 : 120)
                .onChange(of: value) { _, new in
                    percentRate = new / max
                    let gen = UINotificationFeedbackGenerator()
                    if new == max || new == min {
                        gen.notificationOccurred(.success)
                    }
                }.onAppear { percentRate = value / max }
            if orientation == .vertical &amp;amp;&amp;amp; !labelIsHidden {
                Text(&quot;**\(label)**&quot;).dynamicTypeSize(.large) // ** markdown 볼드체, dynamicTypeSize 시스템 설정에 따라 글꼴 크기 자동 조절
            }
        }
    }
}

// https://www.reddit.com/r/SwiftUI/comments/17aruvw/comment/k5enzge/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button
// 프리뷰에서 @Binding 뷰 보기
#Preview {
    struct Preview: View {
        @State var value: Double = 0
        @State var value2: Double = 0
        
        var body: some View {
            VStack {
                CustomSlider(value: $value)
                CustomSlider(
                    value: $value2,
                    label: &quot;제어센터UI&quot;,
                    orientation: .vertical,
                    trackColor: .black.opacity(0.9),
                    sliderColor: .gray
                )
            }
        }
    }
    return Preview()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nuli.navercorp.com/community/article/1133220&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nuli.navercorp.com/community/article/1133220&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;mttContainer&quot; class=&quot;notranslate&quot; style=&quot;transform: translate(687px, 2176px);&quot; aria-expanded=&quot;false&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>indicator</category>
      <category>ios</category>
      <category>Progress</category>
      <category>slider</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>슬라이더</category>
      <category>인디케이터</category>
      <category>제어센터</category>
      <category>프로그래스</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/91</guid>
      <comments>https://gonslab.tistory.com/91#entry91comment</comments>
      <pubDate>Thu, 1 Aug 2024 00:00:34 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] iOS SwiftUI 글자수, 단어수, 줄수 세기 + 복사, 공유, 툴바, 키보드 닫기 등</title>
      <link>https://gonslab.tistory.com/90</link>
      <description>&lt;pre id=&quot;code_1719755287188&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 글자수 세기
struct ContentView: View {
    
    @State var text = &quot;&quot;
    @FocusState var isFocused: Bool
    
    /// 공백 제외
    var removeSpaceTextCount: Int {
        String(text.filter { $0 != &quot; &quot; }).count
    }
    /// 줄 개수
    var newLineCount: Int {
        text.reduce(text.isEmpty ? 0 : 1) { $1 == &quot;\n&quot; ? $0 + 1 : $0 }
    }
    /// 단어 개수
    var wordCount: Int {
        text.isEmpty ? 0 : text.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: .whitespacesAndNewlines).count
    }
    
    var body: some View {
        VStack {
            HStack {
                Button(&quot;전체삭제&quot;) {
                    text = &quot;&quot;
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                
                Button(&quot;전체복사&quot;) {
                    UIPasteboard.general.string = text
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                
                ShareLink(item: text) {
                    Text(&quot;공유&quot;)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
            }
            .frame(height: 30)
            .disabled(text.isEmpty)
            
            TextEditor(text: $text)
                .padding(8)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(.gray.opacity(0.5))
                )
                .focused($isFocused)
            
            HStack {
                VStack(alignment: .leading) {
                    Text(&quot;공백포함 : 총 \(text.count)자&quot;)
                    Text(&quot;공백제외 : 총 \(removeSpaceTextCount)자&quot;)
                }
                Spacer()
                VStack(alignment: .trailing) {
                    Text(&quot;줄 : \(newLineCount)&quot;)
                    Text(&quot;단어 : \(wordCount)&quot;)
                }
            }
        }
        .padding()
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                Spacer()
                
                Button {
                    isFocused = false
                } label: {
                    Image(systemName: &quot;keyboard.chevron.compact.down&quot;)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jun-30-2024 22-43-59.gif&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FVjJO/btsIhtMQtuZ/dHLdYTy0h7R9lqVpXgFsxk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FVjJO/btsIhtMQtuZ/dHLdYTy0h7R9lqVpXgFsxk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FVjJO/btsIhtMQtuZ/dHLdYTy0h7R9lqVpXgFsxk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/FVjJO/btsIhtMQtuZ/dHLdYTy0h7R9lqVpXgFsxk/img.gif&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;587&quot; data-filename=&quot;Jun-30-2024 22-43-59.gif&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>counting letters</category>
      <category>ios</category>
      <category>macos</category>
      <category>ShareLink</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>Til</category>
      <category>Today I learned</category>
      <category>wwdc</category>
      <category>Xcode</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/90</guid>
      <comments>https://gonslab.tistory.com/90#entry90comment</comments>
      <pubDate>Mon, 1 Jul 2024 00:00:54 +0900</pubDate>
    </item>
    <item>
      <title>macOS SwiftUI 맥앱 바로가기 아이콘 빼내기 (add mac app icon to dock)</title>
      <link>https://gonslab.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 는 빌드하면 아이폰에 앱이 생기는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS 는 데스크톱이나 응용프로그램에 앱이 안생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 빌드된 앱을 따로 빼줘야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;123.gif&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2pVn5/btsG51vU0fd/ttE3gepJKCa8f1VsvTzcCk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2pVn5/btsG51vU0fd/ttE3gepJKCa8f1VsvTzcCk/img.gif&quot; data-alt=&quot; macOS add app to dock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2pVn5/btsG51vU0fd/ttE3gepJKCa8f1VsvTzcCk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/2pVn5/btsG51vU0fd/ttE3gepJKCa8f1VsvTzcCk/img.gif&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;454&quot; height=&quot;358&quot; data-filename=&quot;123.gif&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; macOS add app to dock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행된 앱을 커맨드⌘ + 클릭하여 설치된 경로 폴더를 열고 옮겨주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dock 이든 응용프로그램이든 원하는 폴더로 이동할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>macOS</category>
      <category>app</category>
      <category>Display</category>
      <category>dock</category>
      <category>iCon</category>
      <category>ios</category>
      <category>macos</category>
      <category>objective-c</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>Xcode</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/89</guid>
      <comments>https://gonslab.tistory.com/89#entry89comment</comments>
      <pubDate>Sat, 1 Jun 2024 00:00:37 +0900</pubDate>
    </item>
    <item>
      <title>macOS SwiftUI 폴더 선택, 파일 이동 (move file)</title>
      <link>https://gonslab.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출발지에서 목적지로 파일이동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 출발지 폴더를 선택하고 정해진 하위 경로의 파일을 목적지로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 목적지 폴더가 없을 경우 생성, 목적지에 동일한 파일명이 있을 경우 제거 (덮어쓰기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714486199863&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    
    @State private var sourcePath: URL?
    @State private var destinationPath: URL?
    @State private var message: String?
    
    var body: some View {
        VStack {
            Button(&quot;출발지 폴더 선택&quot;) {
                selectFolder { url in
                    sourcePath = url
                }
            }
            Text(sourcePath?.absoluteString ?? &quot;&quot;)
            
            Button(&quot;목적지 폴더 선택&quot;) {
                selectFolder { url in
                    destinationPath = url
                }
            }
            Text(destinationPath?.absoluteString ?? &quot;&quot;)
            
            Button(&quot;파일 이동&quot;) {
                moveFiles()
            }
            Text(message ?? &quot;&quot;)
        }
    }
    
    /// 폴더 선택
    func selectFolder(completion: @escaping (URL) -&amp;gt; Void) {
        let openPanel = NSOpenPanel()
        openPanel.canChooseFiles = false
        openPanel.canChooseDirectories = true
        openPanel.begin { response in
            if response == .OK, let url = openPanel.urls.first {
                completion(url)
            }
        }
    }
    
    /// 파일 이동
    func moveFiles() {
        guard let sourcePath,
              let destinationPath else {
            message = &quot;Source or destination folder not selected.&quot;
            return
        }
        
        let fileName = &quot;img.png&quot;
        let sourceURL = sourcePath.appendingPathComponent(&quot;A&quot;).appendingPathComponent(fileName)
        let destinationFolderURL = destinationPath.appendingPathComponent(&quot;B&quot;).appendingPathComponent(&quot;C&quot;)
        let destinationFileURL = destinationFolderURL.appendingPathComponent(fileName)
        
        let fileManager = FileManager.default
        
        if fileManager.fileExists(atPath: destinationFolderURL.path) { // 목적지 폴더가 있을 경우
            if fileManager.fileExists(atPath: destinationFileURL.path) { // img.png 파일이 있다면 삭제
                do {
                    try fileManager.removeItem(at: destinationFileURL)
                } catch {
                    message = &quot;파일 삭제 실패: \(error.localizedDescription)&quot;
                }
            }
        } else { // 목적지 폴더가 없을 경우
            do { // 폴더 생성
                try fileManager.createDirectory(at: destinationFolderURL, withIntermediateDirectories: true)
            } catch {
                message = &quot;폴더 생성 실패: \(error.localizedDescription)&quot;
            }
        }
        
        do { // 복사 시작
            try fileManager.copyItem(at: sourceURL, to: destinationFileURL)
            message = &quot;복사 성공&quot;
        } catch {
            message = &quot;복사 실패: \(error.localizedDescription)&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 1개의 파일만 예시로 작성했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 다수의 각기 다른 경로 파일들을 옮기는데 유용하게 사용중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 맥앱을 활용하여 일일히 파일 옮기는 시간 단축&amp;darr; , 업무 효율 향상&amp;uarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>macOS</category>
      <category>copyitem</category>
      <category>CreateDirectory</category>
      <category>documentpicker</category>
      <category>FileExists</category>
      <category>fileexporter</category>
      <category>fileimporter</category>
      <category>FileManager</category>
      <category>folder</category>
      <category>nsopenpanel</category>
      <category>removeitem</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/88</guid>
      <comments>https://gonslab.tistory.com/88#entry88comment</comments>
      <pubDate>Wed, 1 May 2024 00:00:32 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI 드래그 앤 드롭 Drag And Drop (DND)</title>
      <link>https://gonslab.tistory.com/87</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;video.gif&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz5vBv/btsFM00xKzd/z4KS4yjCpUrfcF0xdHCKtk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz5vBv/btsFM00xKzd/z4KS4yjCpUrfcF0xdHCKtk/img.gif&quot; data-alt=&quot;Drag And Drop&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz5vBv/btsFM00xKzd/z4KS4yjCpUrfcF0xdHCKtk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cz5vBv/btsFM00xKzd/z4KS4yjCpUrfcF0xdHCKtk/img.gif&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;427&quot; data-filename=&quot;video.gif&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Drag And Drop&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710464955039&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// https://www.codecademy.com/resources/docs/swiftui/drag-and-drop
// https://hyeondxx.medium.com/swiftui-customlist-순서-바꾸기-58ff224b1ad8
/// 드래그 앤 드랍 (순서 변경)
struct DropViewDelegate: DropDelegate {
    
    let item: String
    @Binding var items: [String]
    @Binding var draggedItem: String?
    
    // 드래그 할 때 아이템 우측 상단에 표시되는 이미지 설정 (이 매서드 아예 안쓰면 기본 값 copy : + 플러스 이미지)
    func dropUpdated(info: DropInfo) -&amp;gt; DropProposal? {
        DropProposal(operation: .move) // move, cancel 은 이미지 안나옴. forbidden 는 ⛔︎ 이런 이미지 나옴
    }
    
    func performDrop(info: DropInfo) -&amp;gt; Bool {
        draggedItem = nil
        return true
    }
    
    func dropEntered(info: DropInfo) {
        guard let draggedItem,
              draggedItem != item,
              let to = items.firstIndex(of: item),
              let from = items.firstIndex(of: draggedItem) else { return }
        
        withAnimation {
            items.move(fromOffsets: IndexSet(integer: from), toOffset: to &amp;gt; from ? to + 1 : to)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710464997547&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    
    // 그냥 [Image] 배열로 하면 애니메이션이 작동하지 않고, String 같은 Hashable 데이터로 해야 애니메이션이 적용 됨.
    @State var items = [&quot;person&quot;, &quot;photo&quot;, &quot;dog&quot;, &quot;cat&quot;]
    
    @State var draggedItem: String?
    
    var body: some View {
        VStack(spacing: 20) {
            ForEach(items, id: \.self) { item in
                Image(systemName: item)
                    .resizable()
                    .frame(width: 100, height: 100)
                    .onDrag {
                        self.draggedItem = item
                        return NSItemProvider()
                    }
                    .onDrop(
                        of: [.image],
                        delegate: DropViewDelegate(
                            item: item,
                            items: $items,
                            draggedItem: $draggedItem
                        )
                    )
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;스유&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;스위프트유아이&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;옵씨&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>DND</category>
      <category>draganddrop</category>
      <category>dropDelegate</category>
      <category>DropInfo</category>
      <category>DropProposal</category>
      <category>MOVE</category>
      <category>NSItemProvider</category>
      <category>ondrag</category>
      <category>ondrop</category>
      <category>드래그앤드랍</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/87</guid>
      <comments>https://gonslab.tistory.com/87#entry87comment</comments>
      <pubDate>Mon, 1 Apr 2024 00:00:27 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI PageControl 페이지 컨트롤</title>
      <link>https://gonslab.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Feb-20-2024 16-42-50.gif&quot; data-origin-width=&quot;100&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFDh2v/btsFaaBnZID/dzPZkH40TwvQ80bs2OdAH1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFDh2v/btsFaaBnZID/dzPZkH40TwvQ80bs2OdAH1/img.gif&quot; data-alt=&quot;PageControl&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFDh2v/btsFaaBnZID/dzPZkH40TwvQ80bs2OdAH1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dFDh2v/btsFaaBnZID/dzPZkH40TwvQ80bs2OdAH1/img.gif&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;200&quot; height=&quot;200&quot; data-filename=&quot;Feb-20-2024 16-42-50.gif&quot; data-origin-width=&quot;100&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PageControl&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708415234179&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 커스텀 페이지 컨트롤 애니메이션 Custom PageControl + Animation
struct PageControl: View {
    
    var numberOfPages: Int
    @Binding var currentPage: Int
    
    var body: some View {
        HStack(spacing: 8) {
            ForEach(0..&amp;lt;numberOfPages, id: \.self) { pagingIndex in
                
                let isCurrentPage = currentPage == pagingIndex
                let height = 8.0
                let width = isCurrentPage ? height * 2 : height
                
                Capsule()
                    .fill(isCurrentPage ? .blue : .gray.opacity(0.5))
                    .frame(width: width, height: height)
            }
        }
        .animation(.linear, value: currentPage)
    }
}

#Preview {
    PageControl(numberOfPages: 3, currentPage: .constant(0))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708415249297&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    
    let array: [Color] = [.red, .green, .blue]
    
    @State var selection = 0
    
    var body: some View {
        
        VStack {
            TabView(selection: $selection) {
                ForEach(array.indices, id: \.self) {
                    array[$0]
                        .tag($0)
                }
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            .frame(width: 300, height: 300)
            
            PageControl(numberOfPages: array.count, currentPage: $selection)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;PageControl&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIPageControl&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;TabView&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;tabViewStyle&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;indexDisplayMode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;페이지컨트롤&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;인디케이터&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;indicator&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>indexDisplayMode</category>
      <category>indicator</category>
      <category>ios</category>
      <category>SWIFT</category>
      <category>TabView</category>
      <category>tabViewStyle</category>
      <category>UIPageControl</category>
      <category>Xcode</category>
      <category>인디케이터</category>
      <category>페이지컨트롤</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/86</guid>
      <comments>https://gonslab.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 1 Mar 2024 00:00:24 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI FlowLayout 컨텐츠 크기 만큼 아래로 배치 되는 레이아웃</title>
      <link>https://gonslab.tistory.com/85</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_screenshot.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebyGPc/btsDZJyTqxs/8A6SMKNIuqkaWnAdCKeE2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebyGPc/btsDZJyTqxs/8A6SMKNIuqkaWnAdCKeE2k/img.png&quot; data-alt=&quot;FlowLayout&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebyGPc/btsDZJyTqxs/8A6SMKNIuqkaWnAdCKeE2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebyGPc%2FbtsDZJyTqxs%2F8A6SMKNIuqkaWnAdCKeE2k%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;650&quot; data-filename=&quot;edited_screenshot.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FlowLayout&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706237357971&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

/// 컨텐츠 크기 만큼 아래로 배치 되는 레이아웃 iOS 16+
struct FlowLayout: Layout {
    var spacing: CGFloat? = nil
    var lineSpacing: CGFloat = 10.0 // 기본 줄 간격을 설정
    
    struct Cache {
        var sizes: [CGSize] = []
        var spacing: [CGFloat] = []
    }
    
    func makeCache(subviews: Subviews) -&amp;gt; Cache {
        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
        let spacing: [CGFloat] = subviews.indices.map { index in
            guard index != subviews.count - 1 else {
                return 0
            }
            
            return subviews[index].spacing.distance(
                to: subviews[index+1].spacing,
                along: .horizontal
            )
        }
        
        return Cache(sizes: sizes, spacing: spacing)
    }
    
    func sizeThatFits(
        proposal: ProposedViewSize,
        subviews: Subviews,
        cache: inout Cache
    ) -&amp;gt; CGSize {
        var totalHeight = 0.0
        var totalWidth = 0.0
        
        var lineWidth = 0.0
        var lineHeight = 0.0
        
        for index in subviews.indices {
            if lineWidth + cache.sizes[index].width &amp;gt; proposal.width ?? 0 {
                totalHeight += lineHeight + lineSpacing // 줄 간격 추가
                lineWidth = cache.sizes[index].width
                lineHeight = cache.sizes[index].height
            } else {
                lineWidth += cache.sizes[index].width + (spacing ?? cache.spacing[index])
                lineHeight = max(lineHeight, cache.sizes[index].height)
            }
            
            totalWidth = max(totalWidth, lineWidth)
        }
        
        totalHeight += lineHeight
        
        return .init(width: totalWidth, height: totalHeight)
    }
    
    func placeSubviews(
        in bounds: CGRect,
        proposal: ProposedViewSize,
        subviews: Subviews,
        cache: inout Cache
    ) {
        var lineX = bounds.minX
        var lineY = bounds.minY
        var lineHeight: CGFloat = 0
        
        for index in subviews.indices {
            if lineX + cache.sizes[index].width &amp;gt; (proposal.width ?? 0) {
                lineY += lineHeight + lineSpacing // 줄 간격 추가
                lineHeight = 0
                lineX = bounds.minX
            }
            
            let position = CGPoint(
                x: lineX + cache.sizes[index].width / 2,
                y: lineY + cache.sizes[index].height / 2
            )
            
            lineHeight = max(lineHeight, cache.sizes[index].height)
            lineX += cache.sizes[index].width + (spacing ?? cache.spacing[index])
            
            subviews[index].place(
                at: position,
                anchor: .center,
                proposal: ProposedViewSize(cache.sizes[index])
            )
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706237387777&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    
    let array = &quot;다중 레이아웃을 사용하면 다양한 방식으로 뷰를 구성할 수 있습니다. 한 가지 중요한 것은 콘크리트 레이아웃의 하위 항목 사이의 간격입니다. 이번 주에 우리는 뷰 사이의 특정 간격을 지정할 수 있는 사용자 정의 레이아웃을 구축하는 방법과 SwiftUI에서 플랫폼 중심의 사전 정의된 간격 규칙을 준수하는 방법을 배울 것입니다.&quot;.split(separator: &quot; &quot;)
    
    var body: some View {
        VStack {
            FlowLayout(spacing: 10, lineSpacing: 10) {
                ForEach(array.indices, id: \.self) {
                    Text(array[$0])
                        .font($0.isMultiple(of: 2) ? .title : .title2)
                        .border(Color.red)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swiftwithmajid.com/2022/11/16/building-custom-layout-in-swiftui-basics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://swiftwithmajid.com/2022/11/16/building-custom-layout-in-swiftui-basics&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swiftwithmajid.com/2022/12/06/building-custom-layout-in-swiftui-spacing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://swiftwithmajid.com/2022/12/06/building-custom-layout-in-swiftui-spacing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>automaticDimension</category>
      <category>collectionview</category>
      <category>dynimic</category>
      <category>EstimatedSize</category>
      <category>flowlayout</category>
      <category>layout</category>
      <category>Size</category>
      <category>동적</category>
      <category>레이아웃</category>
      <category>태그</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/85</guid>
      <comments>https://gonslab.tistory.com/85#entry85comment</comments>
      <pubDate>Thu, 1 Feb 2024 00:00:52 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 유튜브 리젝 (YouTube Reject)</title>
      <link>https://gonslab.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 2.3.6 - Performance - Accurate Metadata&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The rating you have selected, 4+, is inconsistent with the content of your app. Since your app allows filtered access to YouTube, where content with mature or suggestive themes can be accessed, it should be rated 12+.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Next Steps&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To resolve this issue, please update your Rating selections in App Store Connect.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Log in to App Store Connect&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Click on &quot;My Apps&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Select your app&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Scroll down to select a Rating on the App Details page&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Click the Edit button next to &quot;Rating&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Revise the Rating selections to yield 12+&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Click &quot;Done&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Once you've completed all changes, click the &quot;Save&quot; button at the top of the App Version Information page.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: Apps must be rated accordingly for the highest level of content that the user is able to access in the app.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Please see attached screenshot for details.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;거절 사유:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.3.6 Performance: Accurate Metadata&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 내에 있는 유튜브 컨텐츠가 아동용이 아닐 때 앱 연령 제한이 4+ 세 이면 리젝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12+ 세 로 올리면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>AppStore</category>
      <category>guideline 2.3.6 - performance - accurate metadata</category>
      <category>ios</category>
      <category>Reject</category>
      <category>Review</category>
      <category>youtube</category>
      <category>리뷰</category>
      <category>리젝</category>
      <category>심사</category>
      <category>유튜브</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/84</guid>
      <comments>https://gonslab.tistory.com/84#entry84comment</comments>
      <pubDate>Thu, 4 Jan 2024 15:54:00 +0900</pubDate>
    </item>
    <item>
      <title>iOS SwiftUI TabView 의 tabItem 누를 때 탭 전환 대신, 네비게이션 이동</title>
      <link>https://gonslab.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;탭뷰의 아이템 터치 시 탭 전환 말고 Navigation 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0SpZx/btsBVzdbJEX/Fggbw90roke0uBCtaUxpO1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0SpZx/btsBVzdbJEX/Fggbw90roke0uBCtaUxpO1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0SpZx/btsBVzdbJEX/Fggbw90roke0uBCtaUxpO1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/0SpZx/btsBVzdbJEX/Fggbw90roke0uBCtaUxpO1/img.gif&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;334&quot; height=&quot;666&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702387800566&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// iOS 16 이상 버전
struct ContentView: View {
    
    @State private var isDetailViewPresented = false
    @State private var selectedTabIndex = 0
    
    var body: some View {
        
        NavigationStack {
            TabView(selection: $selectedTabIndex) {
                
                Text(&quot;Tab 0 Content&quot;)
                    .tabItem {
                        Image(systemName: &quot;0.circle.fill&quot;)
                        Text(&quot;Tab 0&quot;)
                    }
                    .tag(0)
                Text(&quot;Tab 1 Content&quot;)
                    .tabItem {
                        Image(systemName: &quot;1.circle.fill&quot;)
                        Text(&quot;Tab 1&quot;)
                    }
                    .tag(1)
                Text(&quot;Tab 2 Content&quot;)
                    .tabItem {
                        Image(systemName: &quot;2.circle.fill&quot;)
                        Text(&quot;Tab 2&quot;)
                    }
                    .tag(2)
            }
            .onChange(of: selectedTabIndex) { oldValue, newValue in
                if newValue == 2 {
                    self.selectedTabIndex = oldValue
                    isDetailViewPresented = true
                }
            }
            .navigationDestination(isPresented: $isDetailViewPresented) {
                Text(&quot;navigationDestination&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702387844001&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// iOS 16 미만 버전
struct ContentView2: View {
    
    @State private var isDetailViewPresented = false
    @State private var selectedTabIndex = 0
    
    var body: some View {
        
        NavigationView {
            TabView(selection: $selectedTabIndex) {
                
                Text(&quot;Tab 0 Content&quot;)
                    .tabItem {
                        Image(systemName: &quot;0.circle.fill&quot;)
                        Text(&quot;Tab 0&quot;)
                    }
                    .tag(0)
                Text(&quot;Tab 1 Content&quot;)
                    .tabItem {
                        Image(systemName: &quot;1.circle.fill&quot;)
                        Text(&quot;Tab 1&quot;)
                    }
                    .tag(1)
                Text(&quot;Tab 2 Content&quot;)
                    .tabItem {
                        Image(systemName: &quot;2.circle.fill&quot;)
                        Text(&quot;Tab 2&quot;)
                    }
                    .tag(2)
            }
            .onChange(of: selectedTabIndex) { [selectedTabIndex] newTab in
                if newTab == 2 {
                    self.selectedTabIndex = selectedTabIndex
                    isDetailViewPresented = true
                }
            }
            .background(
                NavigationLink(
                    destination: Text(&quot;NavigationLink&quot;),
                    isActive: $isDetailViewPresented
                ) {
                    EmptyView()
                }
                .hidden()
            )
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;스유&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS SwiftUI</category>
      <category>didSelect</category>
      <category>ios</category>
      <category>navigation</category>
      <category>onchange</category>
      <category>selection</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>tabbarcontroller</category>
      <category>tabItem</category>
      <category>TabView</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/83</guid>
      <comments>https://gonslab.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 1 Jan 2024 00:00:15 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 심사 스크린샷 리젝 (Review Screenshot Reject)</title>
      <link>https://gonslab.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bug Fix Submissions&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;The issues we've identified below are eligible to be resolved on your next update. If this submission includes bug fixes and you'd like to have it approved at this time, reply to this message and let us know. You do not need to resubmit your app for us to proceed.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Alternatively, if you'd like to resolve these issues now, please review the details, make the appropriate changes, and resubmit.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fafafa; color: #111111; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Guideline 2.3.3 - Performance - Accurate Metadata&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;We noticed that your screenshots do not sufficiently show your app in use. Specifically, your iPad Pro (2nd Gen) and iPad Pro (6th Gen) screenshots show an iPhone device frame.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;To help users understand your app&amp;rsquo;s functionality and value, your screenshots should highlight your app's core concept. For example, a gaming app should feature screenshots that capture actual gameplay within the app.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Next Steps&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Please revise your screenshots to ensure that they accurately reflect the app in use on the supported devices.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Keep in mind the following requirements: &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- Marketing or promotional materials that do not reflect the UI of the app are not appropriate for screenshots.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- The majority of the screenshots should highlight your app's main features and functionality.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- Confirm that your app looks and behaves identically in all languages and on all supported devices.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- Make sure that the screenshots show your app in use on the correct device. For example, iPhone screenshots should be taken on iPhone, not on iPad. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Resources&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- To learn more about creating great screenshots for the App Store, see &lt;/span&gt;&lt;a style=&quot;background-color: #fafafa; color: #0070c9; text-align: start;&quot; href=&quot;https://developer.apple.com/app-store/product-page/&quot;&gt;Creating your product page&lt;/a&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- To learn more about uploading screenshots, see &lt;/span&gt;&lt;a style=&quot;background-color: #fafafa; color: #0070c9; text-align: start;&quot; href=&quot;https://developer.apple.com/help/app-store-connect/manage-app-information/upload-app-previews-and-screenshots/&quot;&gt;App Store Connect Help&lt;/a&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Please see attached screenshots for details. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이패드 스크린샷에 아이패드 이미지 없이 아이폰 프레임만 사용하면 리젝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>AppStore</category>
      <category>connect</category>
      <category>guidelines</category>
      <category>ios</category>
      <category>Reject</category>
      <category>Review</category>
      <category>SWIFT</category>
      <category>Xcode</category>
      <category>리젝</category>
      <category>심사</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/82</guid>
      <comments>https://gonslab.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 1 Dec 2023 00:00:30 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift Google ML Kit - 머신러닝 번역 (Machine Learning Translation)</title>
      <link>https://gonslab.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Google ML Kit 을 소개하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 머신러닝으로 크게 Vision API 와 Natural Language API 가 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼굴 인식, 텍스트 인식, 언어 식별 등 다양한 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 다국어 지원 앱을 개발하는데 여러 언어의 로컬라이징 파일을 관리하기 번거롭기도 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번에 번역할 수 있는 방법을 찾다가 구글 ML Kit 번역을 학습해보게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 공식 사이트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/ml-kit&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/ml-kit&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698327040026&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ML Kit &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;모바일 개발자를 위한 Google의 기기별 머신러닝 키트입니다.&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/ml-kit&quot; data-og-url=&quot;https://developers.google.com/ml-kit?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h0F6M/hyUluLSYoY/dvbc8NdjATIHYW2XKVPRe0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/lp8dF/hyUlte8hxZ/fqeLeQkLcOpcwDMen5KV80/img.png?width=738&amp;amp;height=618&amp;amp;face=0_0_738_618,https://scrap.kakaocdn.net/dn/XmnXc/hyUkiZiuBW/xO40CR9bCr7EnV3mlUncqk/img.png?width=970&amp;amp;height=413&amp;amp;face=0_0_970_413&quot;&gt;&lt;a href=&quot;https://developers.google.com/ml-kit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/ml-kit&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h0F6M/hyUluLSYoY/dvbc8NdjATIHYW2XKVPRe0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/lp8dF/hyUlte8hxZ/fqeLeQkLcOpcwDMen5KV80/img.png?width=738&amp;amp;height=618&amp;amp;face=0_0_738_618,https://scrap.kakaocdn.net/dn/XmnXc/hyUkiZiuBW/xO40CR9bCr7EnV3mlUncqk/img.png?width=970&amp;amp;height=413&amp;amp;face=0_0_970_413');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ML Kit &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;모바일 개발자를 위한 Google의 기기별 머신러닝 키트입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용은 무료입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-26 오후 10.52.15.png&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WTtNB/btszbUzxlKu/PrTZrIucsti3xavMMfhlX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WTtNB/btszbUzxlKu/PrTZrIucsti3xavMMfhlX0/img.png&quot; data-alt=&quot;Google ML Kit 무료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WTtNB/btszbUzxlKu/PrTZrIucsti3xavMMfhlX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWTtNB%2FbtszbUzxlKu%2FPrTZrIucsti3xavMMfhlX0%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;500&quot; height=&quot;229&quot; data-filename=&quot;스크린샷 2023-10-26 오후 10.52.15.png&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Google ML Kit 무료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CocoaPods 만 지원합니다. SPM 은 지원하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-26 오후 10.58.32.png&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZW5He/btszdg9QF14/idbIuZo0dG8Dd9sOxPSP10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZW5He/btszdg9QF14/idbIuZo0dG8Dd9sOxPSP10/img.png&quot; data-alt=&quot;CocoaPods 로만 제공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZW5He/btszdg9QF14/idbIuZo0dG8Dd9sOxPSP10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZW5He%2Fbtszdg9QF14%2FidbIuZo0dG8Dd9sOxPSP10%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;700&quot; height=&quot;169&quot; data-filename=&quot;스크린샷 2023-10-26 오후 10.58.32.png&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CocoaPods 로만 제공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코코아팟으로 현재 최신 버전을 설치하고&lt;/p&gt;
&lt;pre id=&quot;code_1698329643694&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pod 'GoogleMLKit/Translate', '3.2.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임포트 해주세요.&lt;/p&gt;
&lt;pre id=&quot;code_1698330530727&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import MLKitTranslate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출발어와 도착어를 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698330506109&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let options = TranslatorOptions(sourceLanguage: .korean, targetLanguage: .english)
let koreanEnglishTranslator = Translator.translator(options: options)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건을 설정해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698331066034&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let conditions = ModelDownloadConditions(
    allowsCellularAccess: false, // 셀룰러 허용 여부
    allowsBackgroundDownloading: true // 백그라운드 다운 허용 여부
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 모델을 다운로드 하고&lt;/p&gt;
&lt;pre id=&quot;code_1698331393856&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;koreanEnglishTranslator.downloadModelIfNeeded(with: conditions) { error in
    guard error == nil else { return }
    print(&quot;언어 모델 다운로드 완료&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698332070763&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;koreanEnglishTranslator.translate(&quot;안녕&quot;) { translatedText, error in
    guard error == nil, let translatedText else { return }
    print(translatedText) // Hi
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkT4b1/btszdgvjjah/wekaOhUsubiKprxvNuJ7o1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkT4b1/btszdgvjjah/wekaOhUsubiKprxvNuJ7o1/img.gif&quot; data-alt=&quot;Google ML Kit&amp;amp;amp;nbsp;Translation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkT4b1/btszdgvjjah/wekaOhUsubiKprxvNuJ7o1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dkT4b1/btszdgvjjah/wekaOhUsubiKprxvNuJ7o1/img.gif&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;600&quot; height=&quot;693&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Google ML Kit&amp;amp;nbsp;Translation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 언어별 번역 모델 관리, 다운로드 프로그레스 등 몇가지 기능이 더 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 활용하면 좋은 SDK 인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 또 좋은 정보로 글 쓰겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Library</category>
      <category>Google</category>
      <category>ios</category>
      <category>machine learning</category>
      <category>ML Kit</category>
      <category>SWIFT</category>
      <category>translation</category>
      <category>Vision</category>
      <category>구글</category>
      <category>머신러닝</category>
      <category>번역</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/81</guid>
      <comments>https://gonslab.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 1 Nov 2023 00:00:57 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 네이버 그린닷 UI 구현 학습 (Circle CollectionView ,  Rotating Wheel , Circular Picker , Spin Menu)</title>
      <link>https://gonslab.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;네이버 앱에 보면 가운데 버튼 눌렀을 때 나오는 회전 메뉴가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그린닷이라고 하는데요 미스고 라는 부동산 앱에도 같은 UI가 있네요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;휠을 돌리는게 신기하기도 하고 공부도 할겸 저도 한번 만들어 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jun-07-2023 21-14-30.gif&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c16pdD/btsiX1VxcUr/Jxy9l9nSZLAbrQcw2xs4Fk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c16pdD/btsiX1VxcUr/Jxy9l9nSZLAbrQcw2xs4Fk/img.gif&quot; data-alt=&quot;네이버 그린닷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c16pdD/btsiX1VxcUr/Jxy9l9nSZLAbrQcw2xs4Fk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c16pdD/btsiX1VxcUr/Jxy9l9nSZLAbrQcw2xs4Fk/img.gif&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;650&quot; height=&quot;700&quot; data-filename=&quot;Jun-07-2023 21-14-30.gif&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네이버 그린닷&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;처음에는 컬렉션뷰로 하는건줄 알고 Circle CollectionView 이런식의 키워드로 접근했는데 안나오더라고요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 Rotating Wheel , Circular Picker , Spin Menu 등으로 검색하다가 괜찮아보이는걸 찾았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a title=&quot;SpinWheelControl&quot; href=&quot;https://github.com/joshdhenry/SpinWheelControl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/joshdhenry/SpinWheelControl&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686151462859&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joshdhenry/SpinWheelControl: An inertial spinning wheel UI control that allows selection of an item. Cocoapod written i&quot; data-og-description=&quot;An inertial spinning wheel UI control that allows selection of an item. Cocoapod written in Swift. - GitHub - joshdhenry/SpinWheelControl: An inertial spinning wheel UI control that allows selectio...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joshdhenry/SpinWheelControl&quot; data-og-url=&quot;https://github.com/joshdhenry/SpinWheelControl&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YvFXa/hySVHla0ls/tsjEvvK7kKLoCdyi1Q7ZpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/joshdhenry/SpinWheelControl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joshdhenry/SpinWheelControl&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YvFXa/hySVHla0ls/tsjEvvK7kKLoCdyi1Q7ZpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joshdhenry/SpinWheelControl: An inertial spinning wheel UI control that allows selection of an item. Cocoapod written i&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An inertial spinning wheel UI control that allows selection of an item. Cocoapod written in Swift. - GitHub - joshdhenry/SpinWheelControl: An inertial spinning wheel UI control that allows selectio...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 라이브러리가 제일 비슷한 것 같아서 이걸 베이스로 하기로 결정하고 수정해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;큰 틀은 UIControl 로 되어 있고, 애니메이션을 일반적인 방식이 아니라 CADisplayLink 로 하는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이게 디스플레이의 주사율에 맞춰서, 특정 함수를 호출시킬 수 있는 타이머 개체입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;처음에는 어려웠지만 하다보니 되더라고요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jun-08-2023 00-40-20.gif&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TB86l/btsi6RiQ80I/IezsM0klRHap4jLK4mfDO0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TB86l/btsi6RiQ80I/IezsM0klRHap4jLK4mfDO0/img.gif&quot; data-alt=&quot;회전 메뉴 UI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TB86l/btsi6RiQ80I/IezsM0klRHap4jLK4mfDO0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/TB86l/btsi6RiQ80I/IezsM0klRHap4jLK4mfDO0/img.gif&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;350&quot; height=&quot;735&quot; data-filename=&quot;Jun-08-2023 00-40-20.gif&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;회전 메뉴 UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;작업한 내용은 크게 이렇습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 회전 시 각 부채꼴 도형에 똑바로 서있는 뷰 추가 (난이도 상)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 이전, 다음 버튼으로 한칸 이동 동작 추가 (연타 대응 완료)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 스핀 또는 버튼으로 회전 후 최종으로 멈춘 이벤트 전달 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 각 부채꼴 조각 터치 이벤트 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 중앙 버튼 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;UIControl Tracking&amp;nbsp; , CADisplayLink , CAShapeLayer , atan2 , sqrt , abs 등&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;새로운 것도 많이 알게 되어서 도움이 많이 됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;끝~!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Naver&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Library</category>
      <category>CADisplayLink</category>
      <category>CAShapeLayer</category>
      <category>ios</category>
      <category>Naver</category>
      <category>SWIFT</category>
      <category>TRACKING</category>
      <category>UIControl</category>
      <category>그린닷</category>
      <category>네이버</category>
      <category>애니메이션</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/80</guid>
      <comments>https://gonslab.tistory.com/80#entry80comment</comments>
      <pubDate>Wed, 7 Jun 2023 23:34:28 +0900</pubDate>
    </item>
    <item>
      <title>게시판 리젝 Guideline 1.2 - Safety - User Generated Content</title>
      <link>https://gonslab.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;게시판 기능을 추가해서 업데이트를 올렸는데 리젝이 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명게시판도 있고, 신고 기능도 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Guideline 1.2 - Safety - User Generated Content&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Your app enables users to post content anonymously but does not have the proper precautions in place.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next Steps&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To resolve this issue, please revise your app to implement all of the following precautions:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Age rating must reflect 17+&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- A mechanism for users to block abusive users&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- To clarify, the user should be able to block another user without having to flag the content 3 times&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Please see attached screenshots for details.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거절 사유:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.2.0 Safety: User Generated Content&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리젝 내용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서&amp;nbsp;사용자가&amp;nbsp;익명으로&amp;nbsp;콘텐츠를&amp;nbsp;게시할&amp;nbsp;수&amp;nbsp;있지만&amp;nbsp;적절한&amp;nbsp;예방&amp;nbsp;조치가&amp;nbsp;마련되어&amp;nbsp;있지&amp;nbsp;않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면 다음 예방 조치를 모두 구현하도록 앱을 수정하세요.&lt;br /&gt;-&amp;nbsp;연령&amp;nbsp;등급은&amp;nbsp;17세&amp;nbsp;이상을&amp;nbsp;반영해야&amp;nbsp;합니다.&lt;br /&gt;- 사용자가 악의적인 사용자를 차단하는 메커니즘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;명확히&amp;nbsp;하기&amp;nbsp;위해&amp;nbsp;사용자는&amp;nbsp;콘텐츠를&amp;nbsp;3번&amp;nbsp;신고하지&amp;nbsp;않고도&amp;nbsp;다른&amp;nbsp;사용자를&amp;nbsp;차단할&amp;nbsp;수&amp;nbsp;있어야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조치 내용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원래 4세 이상이었던 앱의 연령 등급을 17세 이상으로 변경했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 글 작성란에 &quot;내용을 입력하세요.&quot; 라는 플레이스홀더를 &quot;부적절하거나&lt;span&gt; &lt;/span&gt;불쾌감을&lt;span&gt; &lt;/span&gt;줄&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;컨텐츠를 게시할 경우 제재를 받을 수 있습니다.&quot; 로 변경했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1회만 신고를 해도 사용자를 차단할 수 있도록 게시판 운영 정책을 변경하고 신고 및 처리 과정에 대한 메커니즘 설명과 영상을 첨부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게시글, 댓글 신고 화면에 &quot;누적 신고 횟수가 3회 이상인 사용자는 게시판 사용이 제한된다&quot;는 문구가 있었는데 제거했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게시글, 댓글 신고 완료 후 팝업에 &quot;신고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;내용은&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;24&lt;/span&gt;시간&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이내&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;조치됨&quot; 이라는 문구를 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앱에서 문의 방법 4가지 (이메일, 전화, 홈페이지, 메신저 채널) 를 이용하는 영상을 첨부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서비스 이용 약관 중 게시판에 대한 부분에서 &quot;불쾌한&lt;span&gt; &lt;/span&gt;콘텐츠&lt;span&gt; &lt;/span&gt;또는&lt;span&gt; &lt;/span&gt;남용하는&lt;span&gt; &lt;/span&gt;사용자에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;관용이&lt;span&gt; &lt;/span&gt;없다&quot; 는 내용 확인 스크린샷을 첨부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재심사 후 통과됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판 기능을 추가할 때는 위 사항을 미리미리 준비하면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Reject&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Board&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>Board</category>
      <category>ios</category>
      <category>Reject</category>
      <category>SWIFT</category>
      <category>Xcode</category>
      <category>게시판</category>
      <category>리젝</category>
      <category>신고</category>
      <category>안전성(Safety)</category>
      <category>익명</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/79</guid>
      <comments>https://gonslab.tistory.com/79#entry79comment</comments>
      <pubDate>Sun, 4 Jun 2023 12:18:04 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 배열 개수 자르기 Array Slice</title>
      <link>https://gonslab.tistory.com/78</link>
      <description>&lt;pre id=&quot;code_1681958468877&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let numbers = [1, 2, 3, 4, 5]

//앞에서 2개 가져오기
print(numbers.prefix(2))    // Prints &quot;[1, 2]&quot;
//최대 개수 넘어도 상관없음
print(numbers.prefix(10))   // Prints &quot;[1, 2, 3, 4, 5]&quot;

//뒤에서 2개 가져오기
print(numbers.suffix(2))    // Prints &quot;[4, 5]&quot;
//최대 개수 넘어도 상관없음
print(numbers.suffix(10))   // Prints &quot;[1, 2, 3, 4, 5]&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>array</category>
      <category>ios</category>
      <category>PREFIX</category>
      <category>slice</category>
      <category>suffix</category>
      <category>SWIFT</category>
      <category>Xcode</category>
      <category>나누기</category>
      <category>배열</category>
      <category>자르기</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/78</guid>
      <comments>https://gonslab.tistory.com/78#entry78comment</comments>
      <pubDate>Sat, 3 Jun 2023 16:05:14 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 클립보드 복사 UIPasteboard 문자열, 이미지</title>
      <link>https://gonslab.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;문자열만 복사&lt;/p&gt;
&lt;pre id=&quot;code_1677594837531&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UIPasteboard.general.string = &quot;복사할 문자열&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지만 복사&lt;/p&gt;
&lt;pre id=&quot;code_1677594857292&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UIPasteboard.general.image = UIImage(systemName: &quot;photo&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열과 이미지 같이 복사&lt;/p&gt;
&lt;pre id=&quot;code_1677594922349&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UniformTypeIdentifiers

if let imageData = UIImage(systemName: &quot;photo&quot;)?.pngData() {
    let pasteboard = [
        [UTType.utf8PlainText.identifier : &quot;복사할 문자열&quot;],
        [UTType.png.identifier : imageData],
    ]
    UIPasteboard.general.setItems(pasteboard)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #ffffff;&quot;&gt;iOS Swift Xcode SwiftUI Objective-C Combine RxSwift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Copy&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Paste&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #ffffff;&quot;&gt;Clipboard&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;UIPasteboard&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #ffffff;&quot;&gt;사진 Image 텍스트 Text 함께 클립보드 복사 붙여넣기&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>clipboard</category>
      <category>copy</category>
      <category>ios</category>
      <category>paste</category>
      <category>SWIFT</category>
      <category>UIPasteboard</category>
      <category>UniformTypeIdentifiers</category>
      <category>복사</category>
      <category>붙여넣기</category>
      <category>클립보드</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/77</guid>
      <comments>https://gonslab.tistory.com/77#entry77comment</comments>
      <pubDate>Wed, 1 Mar 2023 00:00:21 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 리젝 Guideline 5.3.2 - Legal - Gaming, Gambling, and Lotteries</title>
      <link>https://gonslab.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Guideline 5.3.2 - Legal - Gaming, Gambling, and Lotteries&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Your app includes a contest or sweepstakes but it does not:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- Indicate that Apple is not involved in any way with the contest or sweepstakes.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Next Steps&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;It is necessary to:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Include official rules of the contest or sweepstakes in the app and ensure the rules are available to users at all times.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Include an explicit statement in the contest or sweepstakes rules specifying that Apple is not a sponsor.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Please see attached screenshot for details.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;거절&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;사유&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;5.3.2 Legal: Gambling, Gaming, and Lotteries&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;거절&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;첨부&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;파일&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AZwPw/btrVfrVScUa/NQ2cmA5McAwuG4jf9SF3R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AZwPw/btrVfrVScUa/NQ2cmA5McAwuG4jf9SF3R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AZwPw/btrVfrVScUa/NQ2cmA5McAwuG4jf9SF3R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAZwPw%2FbtrVfrVScUa%2FNQ2cmA5McAwuG4jf9SF3R1%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;300&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에어팟을 주는 이벤트였는데 리젝이 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 이미지 하단에 Apple 은 이 이벤트와 무관하다는 문구를 삽입하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 설명에 대한 PDF 문서를 회신한 후 통과됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경품&lt;span&gt; &lt;/span&gt;이벤트를&lt;span&gt; &lt;/span&gt;진행할&lt;span&gt; &lt;/span&gt;때는&lt;span&gt; &lt;/span&gt;위&lt;span&gt; &lt;/span&gt;사항을&lt;span&gt; &lt;/span&gt;미리&lt;span&gt; &lt;/span&gt;준비해서&lt;span&gt; &lt;/span&gt;일정에&lt;span&gt; &lt;/span&gt;차질이&lt;span&gt; &lt;/span&gt;없도록&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;좋을&lt;span&gt; &lt;/span&gt;것&lt;span&gt; &lt;/span&gt;같아요&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Objective-C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Combine&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;RxSwift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Reject&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Event&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>AppStore</category>
      <category>connect</category>
      <category>event</category>
      <category>HIG</category>
      <category>ios</category>
      <category>Reject</category>
      <category>SWIFT</category>
      <category>리젝</category>
      <category>심사</category>
      <category>이벤트</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/76</guid>
      <comments>https://gonslab.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 1 Feb 2023 00:00:10 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 사용자 정의 연산자 Custom Operator</title>
      <link>https://gonslab.tistory.com/75</link>
      <description>&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;postfix operator ~~

extension String {
    static postfix func ~~ (string: Self) -&amp;gt; Character {
        return string.last ?? Character(&quot;&quot;)
    }
}

&quot;Gons&quot;~~		// s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열의 마지막 글자를 ~~ 물결 2개로 가져옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 원하는 동작을 하는 연산자를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Custom&amp;nbsp;Operator&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;postfix&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;prefix&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;infix&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;ternary&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>custom operator</category>
      <category>infix</category>
      <category>ios</category>
      <category>postfix</category>
      <category>PREFIX</category>
      <category>SWIFT</category>
      <category>ternary</category>
      <category>Xcode</category>
      <category>부호</category>
      <category>커스텀 연산자</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/75</guid>
      <comments>https://gonslab.tistory.com/75#entry75comment</comments>
      <pubDate>Sun, 1 Jan 2023 00:00:38 +0900</pubDate>
    </item>
    <item>
      <title>원티드 프리온보딩 iOS 챌린지 1차 (Wanted Free Onboarding iOS Challenge)</title>
      <link>https://gonslab.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;br /&gt;원티드에서 iOS 프리온보딩을 진행한다고 합니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.wanted.co.kr/events/pre_challenge_ios_1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.wanted.co.kr/events/pre_challenge_ios_1&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;프리온보딩 iOS 챌린지 1차 | 원티드&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;AI 채용, 연봉 정보, 이력서, 커리어 콘텐츠까지 커리어 성장에 필요한 모든 것, 원티드에서 만나보세요.&quot; data-og-host=&quot;www.wanted.co.kr&quot; data-og-source-url=&quot;https://www.wanted.co.kr/events/pre_challenge_ios_1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wMUj8/hyQBhvBGY0/KjhJ5AN0SwuOKlnVCEkqO0/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/baGZWg/hyQBeTb5Ot/cUVUtUJjeinwxG6Zc0E0x1/img.jpg?width=1340&amp;amp;height=840&amp;amp;face=0_0_1340_840,https://scrap.kakaocdn.net/dn/cCvBzz/hyQBhCnp83/6H147Hk0qVrk3HoiF6DZwk/img.jpg?width=1340&amp;amp;height=840&amp;amp;face=0_0_1340_840&quot; data-og-url=&quot;https://www.wanted.co.kr/events/pre_challenge_ios_1&quot;&gt;&lt;a href=&quot;https://www.wanted.co.kr/events/pre_challenge_ios_1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.wanted.co.kr/events/pre_challenge_ios_1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wMUj8/hyQBhvBGY0/KjhJ5AN0SwuOKlnVCEkqO0/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/baGZWg/hyQBeTb5Ot/cUVUtUJjeinwxG6Zc0E0x1/img.jpg?width=1340&amp;amp;height=840&amp;amp;face=0_0_1340_840,https://scrap.kakaocdn.net/dn/cCvBzz/hyQBhCnp83/6H147Hk0qVrk3HoiF6DZwk/img.jpg?width=1340&amp;amp;height=840&amp;amp;face=0_0_1340_840');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프리온보딩 iOS 챌린지 1차 | 원티드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AI 채용, 연봉 정보, 이력서, 커리어 콘텐츠까지 커리어 성장에 필요한 모든 것, 원티드에서 만나보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.wanted.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;챌린지 일정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;11.10 (목) - 11.23 (수)참가 신청 및 사전 과제 수행&lt;/li&gt;
&lt;li&gt;11.28 (월) - 12.9 (금)기술 역량 향상 (2주)- 강의 : 월요일 &amp;amp; 목요일 19시 30분 (총 12시간, 온라인)&lt;/li&gt;
&lt;li&gt;12.12 (월) - 1.6 (금)취업 챌린지 진행 (4주)- 이력서 지원부터 ~ 합격까지 (개별 취업 활동)&lt;/li&gt;
&lt;li&gt;1.9 (월)챌린지 종료 및 시상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&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;내 생각과 근거를 갖춘 코드를 작성하는 생각의 방향을 찾는 거예요.&lt;/li&gt;
&lt;li&gt;피드백을 통해 놓치는 부분을 채워서 한 발짝 더 성장하는 거예요.&lt;/li&gt;
&lt;li&gt;취업에 성공해서 iOS 개발자로서 멋지게 커리어를 시작하는 거예요.&lt;/li&gt;
&lt;li&gt;이번 챌린지를 통해 아마추어적인 코드를 프로의 코드처럼 리팩토링 하는 경험을 하게 될 거예요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;기술과제 합격을 위한 네 가지 리팩토링
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리팩토링 01 - 객체 지향 프로그래밍을 올바르게 이해하고 적용합니다.&lt;/li&gt;
&lt;li&gt;리팩토링 02 - Swift 언어의 특성을 고려해 리팩토링을 수행합니다.&lt;/li&gt;
&lt;li&gt;리팩토링 03 - 성능향상을 위해 코드를 개선합니다.&lt;/li&gt;
&lt;li&gt;리팩토링 04 - 코드의 안정성 확보를 위해 테스트를 적용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&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;학습 커리큘럼은 사전 과제를 기반으로 내용이 구성되어 있습니다.&lt;/li&gt;
&lt;li&gt;강의 시간에 사전 과제의 코드를 리팩토링하며 발전시켜갑니다.&lt;/li&gt;
&lt;li&gt;효과적인 기술 역량 향상을 위해 사전 과제 수행 후 챌린지에 참가하는 것을 권장드립니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yagomacademy.notion.site/iOS-ba2d0c0bb0b949c896cc28567706e969&quot;&gt;링크) 프리온보딩 iOS 챌린지 11월 - 사전과제&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1668509104375&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;원티드 프리온보딩 챌린지 iOS과정 사전과제&quot; data-og-description=&quot;- 사전과제는 자신의 GitHub의 공개 저장소에 작성하여 링크를 전달주세요. - 링크는 챌린지 시작 전 등록 설문을 통해 전달하실 수 있습니다. (추후 안내 예정) - 사전 과제는 챌린지를 준비하며 &quot; data-og-host=&quot;yagomacademy.notion.site&quot; data-og-source-url=&quot;https://yagomacademy.notion.site/iOS-ba2d0c0bb0b949c896cc28567706e969&quot; data-og-url=&quot;https://yagomacademy.notion.site/ba2d0c0bb0b949c896cc28567706e969&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nCC80/hyQzbw19vC/oKVhE0oYTEZGkocZBQaL8K/img.png?width=1446&amp;amp;height=640&amp;amp;face=0_0_1446_640,https://scrap.kakaocdn.net/dn/bZsSmx/hyQA4JOsI3/qCKne3lCVKYRX8E1Nv3YFK/img.png?width=1446&amp;amp;height=640&amp;amp;face=0_0_1446_640&quot;&gt;&lt;a href=&quot;https://yagomacademy.notion.site/iOS-ba2d0c0bb0b949c896cc28567706e969&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yagomacademy.notion.site/iOS-ba2d0c0bb0b949c896cc28567706e969&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nCC80/hyQzbw19vC/oKVhE0oYTEZGkocZBQaL8K/img.png?width=1446&amp;amp;height=640&amp;amp;face=0_0_1446_640,https://scrap.kakaocdn.net/dn/bZsSmx/hyQA4JOsI3/qCKne3lCVKYRX8E1Nv3YFK/img.png?width=1446&amp;amp;height=640&amp;amp;face=0_0_1446_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;원티드 프리온보딩 챌린지 iOS과정 사전과제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;- 사전과제는 자신의 GitHub의 공개 저장소에 작성하여 링크를 전달주세요. - 링크는 챌린지 시작 전 등록 설문을 통해 전달하실 수 있습니다. (추후 안내 예정) - 사전 과제는 챌린지를 준비하며&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yagomacademy.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;커리큘럼&lt;br /&gt;11월 챌린지 메이커야곰아카데미 대표 야곰 | 카카오, 빗썸 등 다수 강의 진행&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Week 1-1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 기본기&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기초 코딩 습관, 객체지향 프로그래밍, 타입 설계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[OT] 강사 소개, 코스 커리큘럼 및 학습 목표 소개&lt;/li&gt;
&lt;li&gt;코딩의 필수 기초습관 향상&lt;/li&gt;
&lt;li&gt;객체지향 프로그래밍을 제대로 이해하고 코드에 적용&lt;/li&gt;
&lt;li&gt;[아하!모먼트]라이브러리, 디자인패턴, 아키텍쳐의 현명한 활용&lt;/li&gt;
&lt;li&gt;[아하!모먼트]라이브 이력서 검토 I&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Week 1-2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안전한 코드&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널, 함수형 프로그래밍, 고차함수, 오류처리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift 언어의 특성을 반영한 프로그래밍&lt;/li&gt;
&lt;li&gt;고차함수를 통한 안전한 프로그래밍&lt;/li&gt;
&lt;li&gt;상황에 따른 적절한 오류처리&lt;/li&gt;
&lt;li&gt;[아하!모먼트]면접에선 울고 나와야 한다.&lt;/li&gt;
&lt;li&gt;[아하!모먼트]라이브 이력서 검토 II&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Week 2-1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능을 고려한 코드&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;P.O.P, Swift Performance&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift 언어의 특성을 반영한 프로그래밍&lt;/li&gt;
&lt;li&gt;Protocol Oriented Programming 개념 습득 및 활용&lt;/li&gt;
&lt;li&gt;Swift 언어의 성능향상을 고려한 코드작성&lt;/li&gt;
&lt;li&gt;[아하!모먼트]내가 찾는 그것이 있는 곳 - 공식문서, WWDC (좋은 세션, 문서 소개하기)&lt;/li&gt;
&lt;li&gt;[아하!모먼트]라이브 이력서 검토 III&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Week 2-2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 가능한 코드&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unit Test, Testing, TDD&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unit Test의 활용&lt;/li&gt;
&lt;li&gt;Unit Test를 활용한 TDD&lt;/li&gt;
&lt;li&gt;테스트 가능한 코드 작성&lt;/li&gt;
&lt;li&gt;[아하!모먼트]나도 회사를 면접봐야 한다&lt;/li&gt;
&lt;li&gt;[아하!모먼트]라이브 이력서 검토 IV&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;온라인이고 퇴근 시간 이후라 가능 할 것 같아요&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;사전과제도 쉽고 지원금도 있다고 하니 한번쯤 해보는 것도 좋을 것 같은데요 ㅎㅎ&lt;br /&gt;저도 간단하게 해서 깃허브에 올렸습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a href=&quot;https://github.com/tjdrhs90/MyCreditManager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/tjdrhs90/MyCreditManager&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1668604455334&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - tjdrhs90/MyCreditManager: 원티드 프리온보딩 챌린지 iOS&quot; data-og-description=&quot;원티드 프리온보딩 챌린지 iOS. Contribute to tjdrhs90/MyCreditManager development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tjdrhs90/MyCreditManager&quot; data-og-url=&quot;https://github.com/tjdrhs90/MyCreditManager&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/j0RFP/hyQA6239zS/g65YTFtKg9hBeAkNoBe62K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/tjdrhs90/MyCreditManager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/tjdrhs90/MyCreditManager&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/j0RFP/hyQA6239zS/g65YTFtKg9hBeAkNoBe62K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - tjdrhs90/MyCreditManager: 원티드 프리온보딩 챌린지 iOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;원티드 프리온보딩 챌린지 iOS. Contribute to tjdrhs90/MyCreditManager development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;되면 재밌을 것 같네요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;끝&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;MacOS&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Command Line Tool&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;readLine&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>challenge</category>
      <category>Command Line Tool</category>
      <category>Free Onboarding</category>
      <category>ios</category>
      <category>SWIFT</category>
      <category>Wanted</category>
      <category>Xcode</category>
      <category>원티드</category>
      <category>챌린지</category>
      <category>프리온보딩</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/74</guid>
      <comments>https://gonslab.tistory.com/74#entry74comment</comments>
      <pubDate>Wed, 16 Nov 2022 00:00:32 +0900</pubDate>
    </item>
    <item>
      <title>리젝 회원 탈퇴 기능 (Reject Membership Withdrawal Delete Account)</title>
      <link>https://gonslab.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Guideline 5.1.1(v) - Data Collection and Storage&lt;/b&gt;&lt;/h3&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We noticed that your app supports account creation but does not appear to include an option to initiate account deletion.&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apps that support account creation must also offer account deletion to give App Store users more control of the data they've shared while using your app.&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Next Steps&lt;/b&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If your app already supports account deletion, reply to this message and let us know how to locate this feature. If your app does not support account deletion, revise your app to include an option to initiate account deletion.&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If you are unable to offer account deletion or need to provide additional customer service flows to facilitate and confirm account deletion, either because your app operates in a highly-regulated industry or for some other reason, reply to this message in App Store Connect and provide additional information or documentation. If you have questions regarding your legal obligations, check with your legal counsel.&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keep these requirements in mind when updating your app to support account deletion:&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Only offering to temporarily deactivate or disable an account is insufficient.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- If users need to visit a website to finish deleting their account, include a link directly to the page on your website where they can complete the process.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- You may include confirmation steps to prevent users from accidentally deleting their account. However, only apps in highly-regulated industries may require users to use customer service resources, such as making a phone call or sending an email, to complete account deletion.&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Resources&lt;/b&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Review frequently asked questions and learn more about the &lt;a href=&quot;https://developer.apple.com/support/offering-account-deletion-in-your-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;account deletion requirements&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Apps that offer Sign in with Apple should use the &lt;a href=&quot;https://developer.apple.com/documentation/sign_in_with_apple/revoke_tokens&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sign in with Apple REST API&lt;/a&gt; to revoke user tokens.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr class=&quot;Box-sc-18eybku-0 HR-sc-jf2ll2-0 iNRbiO&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 앱 1.0.0&lt;/li&gt;
&lt;li&gt;앱 버전&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거절 사유:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.1.1 Legal: Privacy - Data Collection and Storage&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원탈퇴 기능이 있는데 못 찾겠다고 리젝됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탈퇴가 없다면 추가하고 있다면 하는 방법을 알려달라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원탈퇴 하는 법을 스크린샷 찍어서 회신했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출시 일정에 차질이 없도록 미리미리&amp;nbsp;처음부터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 심사 정보에 탈퇴하는법 스크린샷을 추가 해놓으면 좋을 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;계정삭제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;계정탈퇴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>Delete Account</category>
      <category>ios</category>
      <category>Membership Withdrawal</category>
      <category>Reject</category>
      <category>SWIFT</category>
      <category>계정삭제</category>
      <category>계정탈퇴</category>
      <category>리젝</category>
      <category>심사</category>
      <category>회원탈퇴</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/73</guid>
      <comments>https://gonslab.tistory.com/73#entry73comment</comments>
      <pubDate>Thu, 10 Nov 2022 00:00:44 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift URLSession (Alamofire 없이 http 통신)</title>
      <link>https://gonslab.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제 블로그 통계를 보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://gonslab.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Alamofire 정리글&lt;/a&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 조회수가 가장 많아서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;오늘은 &lt;b&gt;URLSession&lt;/b&gt;&lt;span&gt;&lt;span&gt; 으로&lt;/span&gt;&lt;/span&gt; 알라모파이어 없이 http 통신을 해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기본&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Foundation 에 포함되어 있어서 따로 라이브러리 설치 안해도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. GET&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 간단하게 GET 먼저 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jsonplaceholder.typicode.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jsonplaceholder.typicode.com&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사이트에 데이터를 가져올겁니다.&lt;/p&gt;
&lt;figure id=&quot;og_1664238758086&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JSONPlaceholder - Free Fake REST API&quot; data-og-description=&quot;{JSON} Placeholder Free fake API for testing and prototyping. Powered by JSON Server + LowDB. Tested with XV. As of Oct 2021, serving ~1.7 billion requests each month.&quot; data-og-host=&quot;jsonplaceholder.typicode.com&quot; data-og-source-url=&quot;https://jsonplaceholder.typicode.com&quot; data-og-url=&quot;https://jsonplaceholder.typicode.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://jsonplaceholder.typicode.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jsonplaceholder.typicode.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JSONPlaceholder - Free Fake REST API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{JSON} Placeholder Free fake API for testing and prototyping. Powered by JSON Server + LowDB. Tested with XV. As of Oct 2021, serving ~1.7 billion requests each month.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jsonplaceholder.typicode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성하고 실행하면&lt;/p&gt;
&lt;pre id=&quot;code_1664238896041&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func get() {
        //도메인 넣어서 URL 컴포넌트 생성
        var components = URLComponents(string: &quot;https://jsonplaceholder.typicode.com&quot;)
        //도메인 뒤에 API 주소 삽입
        components?.path = &quot;/comments&quot;
        //파라미터 추가할거 있으면 작성
        let parameters = [URLQueryItem(name: &quot;postId&quot;, value: &quot;1&quot;),
                          URLQueryItem(name: &quot;id&quot;, value: &quot;2&quot;)]
        components?.percentEncodedQueryItems = parameters
        //URL 생성
        guard let url = components?.url else { return }
        print(url)
        //리퀘스트 생성
        var request: URLRequest = URLRequest(url: url)
        //통신 방법 지정
        request.httpMethod = &quot;GET&quot;
        //태스크 생성
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            //여기서 에러 체크 및 받은 데이터 가공하여 사용
            guard let data,
                  let str = String(data: data, encoding:.utf8) else { return }
            print(str)
        }
        //실행
        task.resume()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 잘 가져옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmHwEN/btrM642LgyB/TpfzdfMTox33F13p4HYdV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmHwEN/btrM642LgyB/TpfzdfMTox33F13p4HYdV1/img.png&quot; data-alt=&quot;디버그 영역&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmHwEN/btrM642LgyB/TpfzdfMTox33F13p4HYdV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmHwEN%2FbtrM642LgyB%2FTpfzdfMTox33F13p4HYdV1%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;759&quot; height=&quot;359&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디버그 영역&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jsonplaceholder.typicode.com/comments?postId=1&amp;amp;id=2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jsonplaceholder.typicode.com/comments?postId=1&amp;amp;id=2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만든 이 URL을 그대로 사파리에 쳐보면 똑같은 데이터를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFPwQZ/btrM9RnADPX/J6KEY7dNynsEtVD3ge46PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFPwQZ/btrM9RnADPX/J6KEY7dNynsEtVD3ge46PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFPwQZ/btrM9RnADPX/J6KEY7dNynsEtVD3ge46PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFPwQZ%2FbtrM9RnADPX%2FJ6KEY7dNynsEtVD3ge46PK%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;1126&quot; height=&quot;461&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. POST&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 POST 를 해볼건데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버로 데이터를 보내야하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ptsv2.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ptsv2.com/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사이트에 임시 서버를 만들어서 데이터를 보내보겠습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1664239840876&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;PTS - V2&quot; data-og-description=&quot;Welcome to Henry's Post Test Server V2! This is a service for developers testing clients that POST and GET things over HTTP. To begin, use the search below to find an unclaimed toilet. (Toilets are where your dumps go) *All this form does is redirect you t&quot; data-og-host=&quot;ptsv2.com&quot; data-og-source-url=&quot;https://ptsv2.com/&quot; data-og-url=&quot;https://ptsv2.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://ptsv2.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ptsv2.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;PTS - V2&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to Henry's Post Test Server V2! This is a service for developers testing clients that POST and GET things over HTTP. To begin, use the search below to find an unclaimed toilet. (Toilets are where your dumps go) *All this form does is redirect you t&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ptsv2.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트에 접속해서 빨간색 표시한 버튼 눌러서 서버 하나 만들어주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctKs9u/btrM6bBpzqD/Z7IghCF7iXD5Ir9fKB5de0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctKs9u/btrM6bBpzqD/Z7IghCF7iXD5Ir9fKB5de0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctKs9u/btrM6bBpzqD/Z7IghCF7iXD5Ir9fKB5de0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctKs9u%2FbtrM6bBpzqD%2FZ7IghCF7iXD5Ir9fKB5de0%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;2386&quot; height=&quot;1524&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 만들어졌습니다. 빨간색 박스 표시한 주소를 복사해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 보기도 눌러보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kP9I4/btrM4QjU1Uk/rE1Zx3OCk5DhvVpZC4j6lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kP9I4/btrM4QjU1Uk/rE1Zx3OCk5DhvVpZC4j6lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kP9I4/btrM4QjU1Uk/rE1Zx3OCk5DhvVpZC4j6lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkP9I4%2FbtrM4QjU1Uk%2FrE1Zx3OCk5DhvVpZC4j6lK%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;2386&quot; height=&quot;1524&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신 성공 했을 때 이런 메시지가 나온다고 합니다. 바꿀 수도 있고요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mZWGN/btrM9Qa94nj/LHPkexdAztaKCUpi9L1tYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mZWGN/btrM9Qa94nj/LHPkexdAztaKCUpi9L1tYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mZWGN/btrM9Qa94nj/LHPkexdAztaKCUpi9L1tYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmZWGN%2FbtrM9Qa94nj%2FLHPkexdAztaKCUpi9L1tYK%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;2386&quot; height=&quot;1524&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 서버로 보낼 데이터 모델 하나 만들고요&lt;/p&gt;
&lt;pre id=&quot;code_1664240403310&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct User: Codable {
    var name: String
    var job: String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 복사해둔 서버 주소로 아래와 같이 작성하고 실행하면&lt;/p&gt;
&lt;pre id=&quot;code_1664240572063&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func post() {
        var components = URLComponents(string: &quot;https://ptsv2.com&quot;)
        //아까 복사해둔 서버 주소
        components?.path = &quot;/t/swnuy-1664239957/post&quot;
        
        guard let url = components?.url else { return }
        
        var request: URLRequest = URLRequest(url: url)
        
        request.httpMethod = &quot;POST&quot;
        //헤더 지정
        request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)
        //타임아웃 시간 지정. (5초 이상 걸리면 중지)
        request.timeoutInterval = 5
        //Codable 모델 생성
        let bodyModel = User(name: &quot;Gons&quot;, job: &quot;iOS&quot;)
        //Codable 모델을 JSON 인코딩하여 데이터로 만든 후 http 바디에 추가
        request.httpBody = try? JSONEncoder().encode(bodyModel)
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data,
                  let str = String(data: data, encoding:.utf8) else { return }
            print(str)
        }
        
        task.resume()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 컨피그에 적혀있던 내용이 잘 프린트 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p1Puj/btrM65tSCvU/75gA6kyFWkF6MdH3ccqT2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p1Puj/btrM65tSCvU/75gA6kyFWkF6MdH3ccqT2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p1Puj/btrM65tSCvU/75gA6kyFWkF6MdH3ccqT2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp1Puj%2FbtrM65tSCvU%2F75gA6kyFWkF6MdH3ccqT2k%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;649&quot; height=&quot;167&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트에서 새로고침을 해보면 Dumps 에 목록이 하나 생겼습니다. &lt;span&gt;우리가 보낸겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;2382&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q7nvi/btrM65gjNsQ/gcKol2GJ5UKZ2Xz93rK3AK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q7nvi/btrM65gjNsQ/gcKol2GJ5UKZ2Xz93rK3AK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q7nvi/btrM65gjNsQ/gcKol2GJ5UKZ2Xz93rK3AK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ7nvi%2FbtrM65gjNsQ%2FgcKol2GJ5UKZ2Xz93rK3AK%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;2382&quot; height=&quot;962&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;2382&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view 를 눌러서 상세하게 보면 우리가 보낸 데이터가 바디에 잘 들어가 있는것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;2056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pz2z4/btrM7j6tJ3U/x9k7UPnkhb6graLtjsbSI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pz2z4/btrM7j6tJ3U/x9k7UPnkhb6graLtjsbSI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pz2z4/btrM7j6tJ3U/x9k7UPnkhb6graLtjsbSI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpz2z4%2FbtrM7j6tJ3U%2Fx9k7UPnkhb6graLtjsbSI1%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;2386&quot; height=&quot;2056&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;2056&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URLSession&lt;/b&gt; 으로&amp;nbsp;이렇게 간단하게 http 통신을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못 작성한 내용이 있거나 궁금하신 점 있으면 있으면 댓글로 알려주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>alamofire</category>
      <category>Codable</category>
      <category>HTTP</category>
      <category>URLComponents</category>
      <category>URLQueryItem</category>
      <category>URLRequest</category>
      <category>URLSession</category>
      <category>네트워크</category>
      <category>서버</category>
      <category>통신</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/72</guid>
      <comments>https://gonslab.tistory.com/72#entry72comment</comments>
      <pubDate>Sat, 1 Oct 2022 00:00:52 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift UISheetPresentationController.Detent (하프 모달 Half screen size custom modal drag bottom sheet present)</title>
      <link>https://gonslab.tistory.com/71</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v3aCC/btrJXSwSPMc/sJi0QmB7gix0Op0NjvqMEk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v3aCC/btrJXSwSPMc/sJi0QmB7gix0Op0NjvqMEk/img.gif&quot; data-alt=&quot;UISheetPresentationController.Detent&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v3aCC/btrJXSwSPMc/sJi0QmB7gix0Op0NjvqMEk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/v3aCC/btrJXSwSPMc/sJi0QmB7gix0Op0NjvqMEk/img.gif&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;608&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UISheetPresentationController.Detent&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 &lt;b&gt;Gons&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반만 나오는 하프 모달 바텀 시트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드래그해서 크기를 변경할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iOS 15&lt;/b&gt; &lt;span&gt;부터&lt;/span&gt; &lt;span&gt;지원합니다&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qTpF3/btrJZItL6JD/i7aCdn3RNXkP6aFzyJENP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qTpF3/btrJZItL6JD/i7aCdn3RNXkP6aFzyJENP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qTpF3/btrJZItL6JD/i7aCdn3RNXkP6aFzyJENP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqTpF3%2FbtrJZItL6JD%2Fi7aCdn3RNXkP6aFzyJENP0%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;995&quot; height=&quot;61&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드&lt;/p&gt;
&lt;pre id=&quot;code_1660783967001&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let btn = UIButton(type: .system)
        view.addSubview(btn)
        btn.frame = .init(x: 100, y: 100, width: 100, height: 100)
        btn.setTitle(&quot;presentModal&quot;, for: .normal)
        btn.addTarget(self, action: #selector(presentModalBtnTap), for: .touchUpInside)
    }
    
    @objc private func presentModalBtnTap() {
        
        let vc = UIViewController()
        vc.view.backgroundColor = .systemYellow
        
        vc.modalPresentationStyle = .pageSheet
        
        if let sheet = vc.sheetPresentationController {
            
            //지원할 크기 지정
            sheet.detents = [.medium(), .large()]
            //크기 변하는거 감지
            sheet.delegate = self
           
            //시트 상단에 그래버 표시 (기본 값은 false)
            sheet.prefersGrabberVisible = true
            
            //처음 크기 지정 (기본 값은 가장 작은 크기)
            //sheet.selectedDetentIdentifier = .large
            
            //뒤 배경 흐리게 제거 (기본 값은 모든 크기에서 배경 흐리게 됨)
            //sheet.largestUndimmedDetentIdentifier = .medium
        }
        
        present(vc, animated: true, completion: nil)
    }
    
}

extension ViewController: UISheetPresentationControllerDelegate {
    func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
        //크기 변경 됐을 경우
        print(sheetPresentationController.selectedDetentIdentifier == .large ? &quot;large&quot; : &quot;medium&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;UIKit&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SwiftUI&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;modalPresentationStyle&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;sheetPresentationController&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;detents&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;커스텀 프레젠트&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>bottom sheet</category>
      <category>custom modal</category>
      <category>Detent</category>
      <category>drag size</category>
      <category>Half screen</category>
      <category>ios</category>
      <category>SWIFT</category>
      <category>UIKit</category>
      <category>UIModalPresentationStyle</category>
      <category>UISheetPresentationController</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/71</guid>
      <comments>https://gonslab.tistory.com/71#entry71comment</comments>
      <pubDate>Thu, 1 Sep 2022 00:00:32 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift Email Masking (이메일 마스킹 * 표시 ID 아이디 가리기)</title>
      <link>https://gonslab.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞 2자리, 뒤 2자리 만 보이게&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//이메일 마스킹 ( test12@test.com -&amp;gt; te**12@test.com )
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;func maskEmail() -&amp;gt; String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let components = self.components(separatedBy: &quot;@&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard let first = components.first,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let last = components.last else { return self }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let mask = first.enumerated().map {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//첫번째 자리, 두번째 자리, 마지막 앞자리, 마지막 자리만 보이게
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return [0, 1, first.count - 2, first.count - 1].contains($0.offset) ? $0.element : &quot;*&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return String(mask) + &quot;@&quot; + last
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;앞 4자리 만 보이게&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//이메일 마스킹2 ( test12@test.com -&amp;gt; test**@test.com )
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var maskEmail2: String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let components = self.components(separatedBy: &quot;@&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard let first = components.first,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let last = components.last else { return self }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let mask = first.enumerated().map { index, char in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//앞 4자리만 보이게
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Array(0..&amp;lt;4).contains(index) ? char : &quot;*&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return String(mask) + &quot;@&quot; + last
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;전체 코드&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override func viewDidLoad() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.viewDidLoad()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let email = &quot;test12@test.com&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(email)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(email.maskEmail())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(email.maskEmail2)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
}


extension String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//이메일 마스킹 ( test12@test.com -&amp;gt; te**12@test.com )
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;func maskEmail() -&amp;gt; String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let components = self.components(separatedBy: &quot;@&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard let first = components.first,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let last = components.last else { return self }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let mask = first.enumerated().map {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//첫번째 자리, 두번째 자리, 마지막 앞자리, 마지막 자리만 보이게
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return [0, 1, first.count - 2, first.count - 1].contains($0.offset) ? $0.element : &quot;*&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return String(mask) + &quot;@&quot; + last
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//이메일 마스킹2 ( test12@test.com -&amp;gt; test**@test.com )
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var maskEmail2: String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let components = self.components(separatedBy: &quot;@&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;guard let first = components.first,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let last = components.last else { return self }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let mask = first.enumerated().map { index, char in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//앞 4자리만 보이게
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Array(0..&amp;lt;4).contains(index) ? char : &quot;*&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return String(mask) + &quot;@&quot; + last
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5hF6z/btrCwhE81dG/vlvSI3GAHtz3zcTCVwjXvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5hF6z/btrCwhE81dG/vlvSI3GAHtz3zcTCVwjXvk/img.png&quot; data-alt=&quot;결과 Debug area&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5hF6z/btrCwhE81dG/vlvSI3GAHtz3zcTCVwjXvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5hF6z%2FbtrCwhE81dG%2FvlvSI3GAHtz3zcTCVwjXvk%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;200&quot; height=&quot;200&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과 Debug area&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;민감한 정보 숨김&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;개인정보 가리기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Privacy Data&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;ID PW 가림&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Password hidden&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;비밀번호 패스워드 별표시&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>*표시</category>
      <category>email</category>
      <category>format</category>
      <category>mask</category>
      <category>masking</category>
      <category>가리기</category>
      <category>마스크</category>
      <category>마스킹</category>
      <category>별표시</category>
      <category>이메일</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/70</guid>
      <comments>https://gonslab.tistory.com/70#entry70comment</comments>
      <pubDate>Wed, 1 Jun 2022 00:00:33 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 웹뷰 html 바디 가져오기 (get webview html body tag)</title>
      <link>https://gonslab.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;웹뷰에서 화면의 값을 가져와야할 일이 생겼습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;html 바디의 pre 태그 안에 json 데이터가 들어있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651637442851&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;pre&amp;gt;
        {&quot;status&quot;:true,&quot;message&quot;:&quot;success&quot;,&quot;data&quot;:{&quot;name&quot;:&quot;Gons&quot;,&quot;url&quot;:&quot;https://gonslab.tistory.com/&quot;}}
    &amp;lt;/pre&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;lt;pre id=&quot;data&quot;&amp;gt;&amp;nbsp;이런식으로 아이디를 넣어주면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;document.getElementById(&amp;ldquo;data&quot;)&amp;nbsp;이렇게 손쉽게 찾을 수 있는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;타사에서 넘겨받은 데이터라 수정할 수가 없어서 태그명으로 접근했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651643118547&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;webView.evaluateJavaScript(&quot;document.getElementsByTagName('pre')[0].innerHTML&quot;) { html, error in
    
    guard let html = html as? String else {
        print(error as Any)
        return
    }
    print(html)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이렇게 태그 경로대로 작성하면 Json 데이터만 가져올 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;{&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&quot;status&quot;:&amp;nbsp;&quot;true&quot;,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&quot;message&quot;:&amp;nbsp;&quot;success&quot;,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&quot;data&quot;: {&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;name&quot;:&amp;nbsp;&quot;Gons&quot;,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;url&quot;:&amp;nbsp;&quot;&lt;a href=&quot;https://gonslab.tistory.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gonslab.tistory.com/&lt;/a&gt;&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이제 이 문자열을 모델로 가공해서 사용하시면 됩니다 ㅎㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>body</category>
      <category>CSS</category>
      <category>evaluate</category>
      <category>HTML</category>
      <category>JavaScript</category>
      <category>JS</category>
      <category>JSON</category>
      <category>parse</category>
      <category>tag</category>
      <category>WebView</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/69</guid>
      <comments>https://gonslab.tistory.com/69#entry69comment</comments>
      <pubDate>Wed, 4 May 2022 20:52:42 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 날짜 차이 구하기 (요일, 시간 사이 간격 변환 계산) Calc Date</title>
      <link>https://gonslab.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날짜 차이 구하기&lt;/p&gt;
&lt;pre id=&quot;code_1645584940917&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        let myDateComponents = DateComponents(year: 2022, month: 3, day: 01)
        let startDate = Calendar.current.date(from: myDateComponents)!
        
        let offsetComps = Calendar.current.dateComponents([.year,.month,.day], from: startDate, to: Date())
        
        if case let (y?, m?, d?) = (offsetComps.year, offsetComps.month, offsetComps.day) {
          print(&quot;\(y)년 \(m)월 \(d)일 만큼 차이남&quot;)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;TimeIntervalSinceNow&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;TimeInterval&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Locale&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Date&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;DateFormatter&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Calendar&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;DateComponents&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;DateCompare&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Difference&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>Calendar</category>
      <category>date</category>
      <category>DateCompare</category>
      <category>DateComponents</category>
      <category>dateFormatter</category>
      <category>TimeInterval</category>
      <category>날짜</category>
      <category>시간</category>
      <category>요일</category>
      <category>차이</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/68</guid>
      <comments>https://gonslab.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 2 Mar 2022 09:13:02 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift AWS S3 사용법 (Amazon Simple Storage Service)</title>
      <link>https://gonslab.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 AWS S3 사용법을 메모해놓겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서 아마존 웹서비스 스토리지를 사용해야할 일이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식사이트를 보니 Amplify 라는 SDK 로 하는 것 같은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서는 앰플리파이를 사용하지 않고 S3 만 사용하라는 오더도 있었어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amplify 없이 스토리지를 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1642390471067&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pod 'AWSS3'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 설치해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.00.39.png&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2JVYx/btrqRTavZx4/2UggngKIRo8PaYpGO9teK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2JVYx/btrqRTavZx4/2UggngKIRo8PaYpGO9teK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2JVYx/btrqRTavZx4/2UggngKIRo8PaYpGO9teK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2JVYx%2FbtrqRTavZx4%2F2UggngKIRo8PaYpGO9teK0%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;609&quot; height=&quot;437&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.00.39.png&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 하나 넣어주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.02.56.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r48Yu/btrqX0mcA63/GlvUlCOqCO1SS5yP10edtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r48Yu/btrqX0mcA63/GlvUlCOqCO1SS5yP10edtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r48Yu/btrqX0mcA63/GlvUlCOqCO1SS5yP10edtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr48Yu%2FbtrqX0mcA63%2FGlvUlCOqCO1SS5yP10edtk%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;434&quot; height=&quot;424&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.02.56.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 2개와 이미지뷰를 넣어주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1642391402075&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

import AWSS3

class ViewController: UIViewController {

    @IBOutlet weak var imgView: UIImageView!
    
    let bucketName = &quot;myapp-bucketname&quot;
    let accessKey = &quot;SSGASLNTTCLXGJNPRLNE&quot;
    let secretKey = &quot;60ayImDHNVF+HmgP5TCrOzvLIspqBFmpbKShOfxA&quot;
    let utilityKey = &quot;utility-key&quot;
    var fileKey = &quot;profile/image/&quot;
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let credentialsProvider = AWSStaticCredentialsProvider(accessKey: accessKey, secretKey: secretKey)

        let configuration = AWSServiceConfiguration(region:.APNortheast2, credentialsProvider:credentialsProvider)
        AWSServiceManager.default().defaultServiceConfiguration = configuration

        let tuConf = AWSS3TransferUtilityConfiguration()
        tuConf.isAccelerateModeEnabled = false

        AWSS3TransferUtility.register(
            with: configuration!,
            transferUtilityConfiguration: tuConf,
            forKey: utilityKey
        )
        
        let dateFormat = DateFormatter()
        dateFormat.dateFormat = &quot;yyyyMMdd/&quot;
        fileKey += dateFormat.string(from: Date())
        fileKey += String(Int64(Date().timeIntervalSince1970)) + &quot;_&quot;
        fileKey += UUID().uuidString + &quot;.png&quot;
        
    }

    func upload() {
        

        guard let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: utilityKey)
        else
        {
            return
        }

        let expression = AWSS3TransferUtilityUploadExpression()
        expression.setValue(&quot;public-read&quot;, forRequestHeader: &quot;x-amz-acl&quot;) //URL로 이미지 읽을 수 있도록 권한 설정 (이 헤더 없으면 못읽음)
        expression.progressBlock = {(task, progress) in
            print(&quot;progress \(progress.fractionCompleted)&quot;)
        }
        
        var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
        completionHandler = { [weak self] (task, error) -&amp;gt; Void in
            guard let self = self else { return }
            print(&quot;task finished&quot;)
            
            let url = AWSS3.default().configuration.endpoint.url
            let publicURL = url?.appendingPathComponent(self.bucketName).appendingPathComponent(self.fileKey)
            if let absoluteString = publicURL?.absoluteString {
                print(&quot;image url &amp;darr;&amp;darr;&quot;)
                print(absoluteString)
            }
            
            if let query = task.request?.url?.query,
               var removeQueryUrlString = task.request?.url?.absoluteString.replacingOccurrences(of: query, with: &quot;&quot;) {
                removeQueryUrlString.removeLast() // 맨 뒤 물음표 삭제
                print(&quot;업로드 리퀘스트에서 쿼리만 제거한 url &amp;darr;&amp;darr;&quot;) //이 주소도 파일 열림
                print(removeQueryUrlString)
            }
        }



        guard let data = UIImage(named: &quot;img&quot;)?.pngData()
        else
        {
            return
        }

        transferUtility.uploadData(data as Data, bucket: bucketName, key: fileKey, contentType: &quot;image/png&quot;, expression: expression,
        completionHandler: completionHandler).continueWith
        {
            (task) -&amp;gt; AnyObject? in
            if let error = task.error {
                print(&quot;Error: \(error.localizedDescription)&quot;)

            }

            if let _ = task.result {
                print (&quot;upload successful.&quot;)
            }

            return nil
        }
    }
    
    func download() {
        
        
        guard let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: utilityKey)
        else
        {
            return
        }

        let expression = AWSS3TransferUtilityDownloadExpression()
        expression.progressBlock = {(task, progress) in
            print(&quot;progress \(progress.fractionCompleted)&quot;)
        }
        
        var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock?
        completionHandler = { (task, url, data, err) -&amp;gt; Void in
            print(&quot;task finished&quot;)
            
            DispatchQueue.main.async { [weak self] in
                
                if let d = data {
                    print(&quot;img data 있음&quot;)
                    self?.imgView.image = UIImage(data: d)
                }
            }
        }
        
        
        transferUtility.downloadData(fromBucket: bucketName, key: fileKey, expression: expression, completionHandler: completionHandler).continueWith
        {
            (task) -&amp;gt; AnyObject? in
            if let error = task.error {
                print(&quot;Error: \(error.localizedDescription)&quot;)
            }
            
            if let _ = task.result {
                print (&quot;download successful.&quot;)
            }
            return nil
        }
        
    }
    
    @IBAction func upload(_ sender: UIButton) {
        upload()
    }
    
    
    @IBAction func download(_ sender: UIButton) {
        download()
        
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;img 라는 이미지를 S3 에 업로드하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷네임과 파일키로 이미지를 다운받아서 이미지뷰에 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.06.04.png&quot; data-origin-width=&quot;1073&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbkpvX/btrqXwTfKJC/3XaE5EzBDIQDM3eNQjsd01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbkpvX/btrqXwTfKJC/3XaE5EzBDIQDM3eNQjsd01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbkpvX/btrqXwTfKJC/3XaE5EzBDIQDM3eNQjsd01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbkpvX%2FbtrqXwTfKJC%2F3XaE5EzBDIQDM3eNQjsd01%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;1073&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.06.04.png&quot; data-origin-width=&quot;1073&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 시에 이렇게 로그가 나오고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 https 주소 2가지 형식 모두 이미지가 잘 열립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.06.23.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qp9d1/btrqXwr8LAn/i8z54mRnNKroXvbBRPxklk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qp9d1/btrqXwr8LAn/i8z54mRnNKroXvbBRPxklk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qp9d1/btrqXwr8LAn/i8z54mRnNKroXvbBRPxklk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqp9d1%2FbtrqXwr8LAn%2Fi8z54mRnNKroXvbBRPxklk%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;454&quot; height=&quot;125&quot; data-filename=&quot;스크린샷 2022-01-17 오후 1.06.23.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드 시에는 이렇게 로그가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액세스키와 시크릿키, 버킷명과 폴더경로만 본인꺼로 바꾸셔서 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Library</category>
      <category>Amazon</category>
      <category>amplify</category>
      <category>AWS</category>
      <category>AWSS3</category>
      <category>ios</category>
      <category>S3</category>
      <category>Simple Storage Service</category>
      <category>SWIFT</category>
      <category>Xcode</category>
      <category>아마존</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/66</guid>
      <comments>https://gonslab.tistory.com/66#entry66comment</comments>
      <pubDate>Mon, 17 Jan 2022 12:58:30 +0900</pubDate>
    </item>
    <item>
      <title>2021년 회고와 2022년 다짐</title>
      <link>https://gonslab.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;안녕하세요 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Gons&lt;/b&gt;&lt;/span&gt; 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;정말 오랜만에 글을 쓰는 것 같네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그동안 많은 일들이 있었고 나름 바빠서 이제야 글을 씁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;제가 좋아하는&amp;nbsp; Zedd님, NamS님, 소들이님 등&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;많은 iOS 블로거 분들이 한 해를 정리하는 글을 적는거 보고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;저도 따라해볼까합니다 ㅎㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;#이직&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;iOS 개발로 들어간 첫 회사를 그만 두게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;SI 회사에서 사수 없이 신입으로 이리 부딪히고 저리 부딪히며 나름 열심히 했었는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;회사 경영악화로 일감이 떨어져서 허송세월 보내는 날이 많아졌고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;더 이상 성장할 수 없음을 느끼자마자 이직을 결심했어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;회사를 다니면서 여기저기 지원을 했는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;여러군데 면접을 보면서 느낀점은 질문이 거의 비슷비슷하다라는 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코딩테스트도 몇개 보고 재밌다고 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러다가 나름 대기업이라고 하는 곳에 들어가게 됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;자사 앱들을 운영하는 일이었고 RxSwift 를 사용했고 iOS인원이 3명이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Objective-C로 만들어진 앱들도 있어서 배워야했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;확실히 유지보수 하는일이라 별로 할게 없고 인원도 여러명이라 나눠서 할 양도 안됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 얼마 안있다가 팀장님께 제가 새로운 앱을 만드는데에 흥미가 있는 것 같다고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존 앱을 유지보수하는 일에 재미를 못붙이는 것 같다고 말씀드리고 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 바로 또 다른 곳 면접봐서 이번에는 스타트업에 들어왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;새로운 자사 앱을 만드는 회사고요. 원하던 일이라 만족하며 다니고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;#목표&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2022년에는 좀 더 부지런히 살아보고 싶습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그동안 방치되었던 블로그도 다시 쓰고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그동안 방치되었던 개인앱들도 다시 업데이트하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;SwiftUI 도 조금씩 공부해보고 싶고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;운동도 좀 하고 싶고 &lt;/span&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그렇습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;올해에는 좀 정신적으로나 육체적으로 건강한 삶을 살아보겠습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이런 일기 같은 글을 쓰려니 힘드네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;불필요한 말들만 적힌 것 같기도 하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;아무튼 앞으로도 화이팅해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>combine</category>
      <category>ios</category>
      <category>ipados</category>
      <category>MVVM</category>
      <category>rxswift</category>
      <category>StoryBoard</category>
      <category>SWIFT</category>
      <category>swiftUI</category>
      <category>watchos</category>
      <category>Xcode</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/67</guid>
      <comments>https://gonslab.tistory.com/67#entry67comment</comments>
      <pubDate>Thu, 13 Jan 2022 22:32:31 +0900</pubDate>
    </item>
    <item>
      <title>Reject 큐알코드 스캔 리젝 사유 (QRcode, 바코드, barcode scan)</title>
      <link>https://gonslab.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2021년 6월 2일 오전 12:39&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple의 메시지&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;2.&amp;nbsp;1 Performance: App Completeness&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 2.1 - Information Needed&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We have started the review of your app, but we are not able to continue because we need a demo QR code to fully assess your app features.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Specifically, please provide a demo QR codes in order to review the functionality.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next Steps&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To help us proceed with the review of your app, please provide the demo details in the App Review Information section for your app in App Store Connect or reply to this message with the requested demo information. Please ensure that the information you provide includes any data necessary to demonstrate the functionality of your app features.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To provide QR codes or images information:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Log in to App Store Connect&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Click on &quot;My Apps&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Select your app&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Click on the app version on the left side of the screen&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Scroll down to &quot;App Review Information&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Provide information in &quot;Notes&quot; as appropriate&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Click &quot;Save&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Once you've completed all changes, click the &quot;Save&quot; button at the top of the Version Information page.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Please see attached screenshot for details.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Since your App Store Connect status is Metadata Rejected, we do NOT require a new binary. To revise the metadata, visit App Store Connect to select your app and revise the desired metadata values. Once you&amp;rsquo;ve completed all changes, reply to this message in Resolution Center and we will continue the review.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QRcode 스캔 하는 앱을 업로드했는데 메타데이터 거부로 반려 당했는데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QRcode 데모 이미지가 필요하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에 일반적인 모든 큐알코드를 스캔하는 다른 앱을 올렸을 때는 샘플 이미지가 필요하지 않았는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 올린 앱은 특정 QR코드만 인식하는 앱이라 (예 : 자사 명함 스캔하는 앱, 로또 QR코드 스캔 앱)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 앱은 심사 정보에 스캔할 수 있는 이미지를 추가해서 올리면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>AVCaptureSession</category>
      <category>AVCaptureVideoPreviewLayer</category>
      <category>Barcode</category>
      <category>Qrcode</category>
      <category>QR코드</category>
      <category>Reject</category>
      <category>리젝</category>
      <category>메타데이터거부</category>
      <category>바코드scan</category>
      <category>큐알code</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/65</guid>
      <comments>https://gonslab.tistory.com/65#entry65comment</comments>
      <pubDate>Wed, 2 Jun 2021 10:25:34 +0900</pubDate>
    </item>
    <item>
      <title>Reject 인앱 결제 리젝 사유 2 (IAP - in app purchase)</title>
      <link>https://gonslab.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Apple의&amp;nbsp;메시지&lt;br /&gt;&lt;b&gt;Guideline&amp;nbsp;2.3.10&amp;nbsp;-&amp;nbsp;Performance&amp;nbsp;-&amp;nbsp;Accurate&amp;nbsp;Metadata&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;We noticed that your app includes the following references to Apple's processes or policies in the 아이템 이름, which are not relevant to the app's content and functionality:&lt;br /&gt;&lt;br /&gt;- 세금공제 목적으로 애플에서 30%를 가져갑니다.&lt;br /&gt;&lt;br /&gt;Next&amp;nbsp;Steps&lt;br /&gt;&lt;br /&gt;Please revise or remove this content from your app's 아이템이름.&lt;br /&gt;&lt;br /&gt;Please&amp;nbsp;see&amp;nbsp;attached&amp;nbsp;screenshot&amp;nbsp;for&amp;nbsp;details.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인앱결제 상품을 등록했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 정보를 설명하는 화면에서 아래 문구가 있다고 반려 당했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;세금공제 목적으로 애플에서 30%를 가져갑니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문구를 삭제한 뒤 통과가 됐습니다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 Apple 프로세스나 정책적인 부분은 적지 않는게 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>IAP</category>
      <category>purchase</category>
      <category>Reject</category>
      <category>SKPayment</category>
      <category>SKPaymentTransactionObserver</category>
      <category>SKProductsRequestDelegate</category>
      <category>StoreKit</category>
      <category>결재</category>
      <category>리젝</category>
      <category>인앱결제</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/64</guid>
      <comments>https://gonslab.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 24 May 2021 09:28:37 +0900</pubDate>
    </item>
    <item>
      <title>Reject 인앱 결제 리젝 사유 1 (IAP - in app purchase)</title>
      <link>https://gonslab.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Apple의&amp;nbsp;메시지&lt;br /&gt;2.&amp;nbsp;1&amp;nbsp;Performance:&amp;nbsp;App&amp;nbsp;Completeness&lt;br /&gt;&lt;b&gt;Guideline&amp;nbsp;2.1&amp;nbsp;-&amp;nbsp;Information&amp;nbsp;Needed&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;We&amp;nbsp;have&amp;nbsp;started&amp;nbsp;the&amp;nbsp;review&amp;nbsp;of&amp;nbsp;your&amp;nbsp;app,&amp;nbsp;but&amp;nbsp;we&amp;nbsp;are&amp;nbsp;not&amp;nbsp;able&amp;nbsp;to&amp;nbsp;continue&amp;nbsp;because&amp;nbsp;we&amp;nbsp;cannot&amp;nbsp;locate&amp;nbsp;the&amp;nbsp;in-app&amp;nbsp;purchases&amp;nbsp;within&amp;nbsp;your&amp;nbsp;app.&lt;br /&gt;&lt;br /&gt;Specifically,&amp;nbsp;we&amp;nbsp;were&amp;nbsp;unable&amp;nbsp;to&amp;nbsp;locate&amp;nbsp;following&amp;nbsp;in-app&amp;nbsp;purchase&amp;nbsp;item:&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;아이템 이름 -&amp;nbsp;for&amp;nbsp;$.99&lt;br /&gt;&lt;br /&gt;Next&amp;nbsp;Steps&lt;br /&gt;&lt;br /&gt;To&amp;nbsp;help&amp;nbsp;us&amp;nbsp;proceed&amp;nbsp;with&amp;nbsp;the&amp;nbsp;review&amp;nbsp;of&amp;nbsp;your&amp;nbsp;app,&amp;nbsp;please&amp;nbsp;reply&amp;nbsp;to&amp;nbsp;this&amp;nbsp;message&amp;nbsp;providing&amp;nbsp;the&amp;nbsp;steps&amp;nbsp;for&amp;nbsp;locating&amp;nbsp;the&amp;nbsp;in-app&amp;nbsp;purchases&amp;nbsp;in&amp;nbsp;your&amp;nbsp;app.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Please&amp;nbsp;see&amp;nbsp;attached&amp;nbsp;screenshot&amp;nbsp;for&amp;nbsp;details.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인앱결제 상품 2개를 등록했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 상품을 결제 해야 2번 상품이 보이도록 짰는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 상품을 찾을 수 없다고 반려 당했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사 정보에 &quot;2번 상품은 1번 상품을 결제해야 보입니다.&quot; 라고 적어주니 통과됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루 날리지 마시고, 미리미리 적어줍시다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Swift&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>IAP</category>
      <category>purchase</category>
      <category>Reject</category>
      <category>SKPayment</category>
      <category>SKPaymentTransactionObserver</category>
      <category>SKProductsRequestDelegate</category>
      <category>StoreKit</category>
      <category>결재</category>
      <category>리젝</category>
      <category>인앱결제</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/63</guid>
      <comments>https://gonslab.tistory.com/63#entry63comment</comments>
      <pubDate>Fri, 21 May 2021 09:50:18 +0900</pubDate>
    </item>
    <item>
      <title>WARNING ITMS-90076 (앱 이전 키체인 손실 경고)</title>
      <link>https://gonslab.tistory.com/62</link>
      <description>&lt;p&gt;아카이브 할 때 팝업창 내용&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;App Store Connect Operation Warning&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;WARNING &lt;b&gt;ITMS-90076&lt;/b&gt;: &quot;Potential Loss of Keychain Access. The previous version of software has an application-identifier value of ['이전팀ID.com.gons.앱이름'] and the new version of software being submitted has an application-identifier of ['이후&lt;span style=&quot;color: #333333;&quot;&gt;팀ID.com.gons.앱이름&lt;/span&gt;']. This will result in a loss of keychain access.&quot;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;메일로 온 내용&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Dear Developer,&lt;/p&gt;
&lt;p&gt;We identified one or more issues with a recent delivery for your app, &quot;앱이름&quot; 1.0.1 (2). Your delivery was successful, but you may wish to correct the following issues in your next delivery:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;ITMS-90076: Potential Loss of Keychain Access&lt;/b&gt; - The previous version of software has an application-identifier value of ['&lt;span style=&quot;color: #333333;&quot;&gt;이전팀ID.com.gons.앱이름&lt;/span&gt;'] and the new version of software being submitted has an application-identifier of ['&lt;span style=&quot;color: #333333;&quot;&gt;이후&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;팀ID.com.gons.앱이름&lt;/span&gt;']. This will result in a loss of keychain access.&lt;/p&gt;
&lt;p&gt;After you&amp;rsquo;ve corrected the issues, you can upload a new binary to App Store Connect.&lt;/p&gt;
&lt;p&gt;Best regards,&lt;/p&gt;
&lt;p&gt;The App Store Team&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앱을 다른 개발자 계정으로 이전 하여서 전에 쓰던 키체인이 손실된다는 경고.&lt;/p&gt;
&lt;p&gt;경고만 나오고 그냥 잘 업로드 됐음.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS Swift Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS etc.</category>
      <category>App Transfer</category>
      <category>iOS 앱 이전</category>
      <category>ITMS-90076</category>
      <category>keychain</category>
      <category>SWIFT</category>
      <category>소유권 변경</category>
      <category>소유권 이전</category>
      <category>앱 넘기기</category>
      <category>앱 옮기기</category>
      <category>키체인</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/62</guid>
      <comments>https://gonslab.tistory.com/62#entry62comment</comments>
      <pubDate>Tue, 4 May 2021 09:24:54 +0900</pubDate>
    </item>
    <item>
      <title>iOS Swift 앱 안끄고 언어 변경 ( 앱 내 언어 설정 )</title>
      <link>https://gonslab.tistory.com/61</link>
      <description>&lt;p&gt;안녕하세요 Gons 입니다.&lt;/p&gt;
&lt;p&gt;오늘은 앱을 종료하지 않고 언어변경을 해보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 다국어 설정이 완료된 상태여야합니다.&lt;/p&gt;
&lt;p&gt;다국어 설정이 안되신 분들은&lt;/p&gt;
&lt;p&gt;이전 포스팅을 확인해주세요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;a href=&quot;https://gonslab.tistory.com/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다국어 설정 - 스토리보드&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;a href=&quot;https://gonslab.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다국어 설정 - 코드&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저는 이렇게 준비해놨습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;스크린샷 2021-04-01 오전 10.46.13.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;722&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu9Tuq/btq1zsu0nTk/dffhqG769exsTS59Klfkb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu9Tuq/btq1zsu0nTk/dffhqG769exsTS59Klfkb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu9Tuq/btq1zsu0nTk/dffhqG769exsTS59Klfkb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu9Tuq%2Fbtq1zsu0nTk%2FdffhqG769exsTS59Klfkb1%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-04-01 오전 10.46.13.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;722&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;스크린샷 2021-04-01 오전 10.46.16.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;722&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce598G/btq1AFgdlMC/Ncv6qQOP2UFKAEOmwHsg2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce598G/btq1AFgdlMC/Ncv6qQOP2UFKAEOmwHsg2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce598G/btq1AFgdlMC/Ncv6qQOP2UFKAEOmwHsg2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce598G%2Fbtq1AFgdlMC%2FNcv6qQOP2UFKAEOmwHsg2K%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-04-01 오전 10.46.16.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;722&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;그룹 1.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;612&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RuRaF/btq1BygRe98/K9EDSh5KzBNXxXa7SxmUk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RuRaF/btq1BygRe98/K9EDSh5KzBNXxXa7SxmUk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RuRaF/btq1BygRe98/K9EDSh5KzBNXxXa7SxmUk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRuRaF%2Fbtq1BygRe98%2FK9EDSh5KzBNXxXa7SxmUk0%2Fimg.png&quot; data-filename=&quot;그룹 1.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;612&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1617242319839&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @IBOutlet var myLabel: UILabel!
    
    @IBOutlet var myLabel2: UILabel!
    
    @IBOutlet var myLabel3: UILabel!
    
    @IBOutlet var myLabel4: UILabel!
    
    @IBOutlet var myLabel5: UILabel!
    
    @IBOutlet var buttonKorean: UIButton!
    
    @IBOutlet var buttonEnglish: UIButton!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작하겠습니다.&lt;/p&gt;
&lt;p&gt;기본적으로 앱 내에서 언어를 설정하면&lt;/p&gt;
&lt;p&gt;폰 설정에 있는 언어를 따라가지 않습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;보통 NSLocalizedString 을 사용하는데 이 방법은 앱 내에서 언어를 변경해도 앱을 껐다 켜야만 언어가 바뀝니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1617242374934&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    override func viewDidLoad() {
        super.viewDidLoad()
        
        setLanguage()
    }
    
    func setLanguage() {
        //기본 방법
        myLabel.text = NSLocalizedString(&quot;Hello&quot;, comment: &quot;&quot;)
        //...
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 다국어 설정 파일에 접근해서 localizedString 으로 언어를 변경해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1617242751719&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func setLanguage() {
        
        //설정된 언어 코드 가져오기
        let language = UserDefaults.standard.array(forKey: &quot;AppleLanguages&quot;)?.first as! String // 초기에 &quot;ko-KR&quot; , &quot;en-KR&quot; 등으로 저장되어있음
        let index = language.index(language.startIndex, offsetBy: 2)
        let languageCode = String(language[..&amp;lt;index]) //&quot;ko&quot; , &quot;en&quot; 등
        
        //설정된 언어 파일 가져오기
        let path = Bundle.main.path(forResource: languageCode, ofType: &quot;lproj&quot;)
        let bundle = Bundle(path: path!)
        
        myLabel.text = bundle?.localizedString(forKey: &quot;Hello&quot;, value: nil, table: nil)
        myLabel2.text = bundle?.localizedString(forKey: &quot;Welcome&quot;, value: nil, table: nil)
        myLabel3.text = bundle?.localizedString(forKey: &quot;Thank&quot;, value: nil, table: nil)
        myLabel4.text = bundle?.localizedString(forKey: &quot;Happy&quot;, value: nil, table: nil)
        myLabel5.text = bundle?.localizedString(forKey: &quot;Sorry&quot;, value: nil, table: nil)
        buttonKorean.setTitle(bundle?.localizedString(forKey: &quot;buttonKorean&quot;, value: nil, table: nil), for: .normal)
        buttonEnglish.setTitle(bundle?.localizedString(forKey: &quot;buttonEnglish&quot;, value: nil, table: nil), for: .normal)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;UserDefaults &lt;/span&gt;&quot;AppleLanguages&quot; 에 설정된 언어가 저장되어 있고 앞으로 이 값을 통해 언어를 변경해주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1617242918465&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @IBAction func koreanClicked(_ sender: UIButton) {
        //한국어로 변경
        UserDefaults.standard.set([&quot;ko&quot;], forKey: &quot;AppleLanguages&quot;)
        UserDefaults.standard.synchronize()
        
        //보통 메인화면으로 이동시켜줌
        setLanguage()
    }
    @IBAction func englishClicked(_ sender: UIButton) {
        //영어로 변경
        UserDefaults.standard.set([&quot;en&quot;], forKey: &quot;AppleLanguages&quot;)
        UserDefaults.standard.synchronize()
        
        //보통 메인화면으로 이동시켜줌
        setLanguage()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;Apr-01-2021 11-12-31.gif&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLQL2D/btq1vgCf1UT/4NZ6GQ4BMrbKri3t9q1IjK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLQL2D/btq1vgCf1UT/4NZ6GQ4BMrbKri3t9q1IjK/img.gif&quot; data-alt=&quot;ios swift in app programmatically change language without restart.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLQL2D/btq1vgCf1UT/4NZ6GQ4BMrbKri3t9q1IjK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cLQL2D/btq1vgCf1UT/4NZ6GQ4BMrbKri3t9q1IjK/img.gif&quot; data-filename=&quot;Apr-01-2021 11-12-31.gif&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;ios swift in app programmatically change language without restart.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;잘 변경되네요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;끝입니다.&lt;/p&gt;
&lt;p&gt;저는 다음에 더 필요한 정보로 글을 쓰겠습니다.&lt;/p&gt;
&lt;p&gt;읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;iOS Swift Xcode&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS Swift + UIKit</category>
      <category>change language without restart</category>
      <category>in app localizable</category>
      <category>localizing</category>
      <category>programmatically localizations</category>
      <category>국제화</category>
      <category>다국어</category>
      <category>로컬라이징</category>
      <category>앱 실행중</category>
      <category>앱 안에서</category>
      <category>종료안하고</category>
      <author>GonsLab</author>
      <guid isPermaLink="true">https://gonslab.tistory.com/61</guid>
      <comments>https://gonslab.tistory.com/61#entry61comment</comments>
      <pubDate>Mon, 5 Apr 2021 10:55:49 +0900</pubDate>
    </item>
  </channel>
</rss>