<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hey_hen.log</title>
        <link>https://velog.io/</link>
        <description>iOS Dev, Coffee in my bloodstream</description>
        <lastBuildDate>Mon, 24 Jan 2022 14:48:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hey_hen.log</title>
            <url>https://images.velog.io/images/hey_hen/profile/8d4e2385-2b15-4ee9-8f36-d34262430c5b/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hey_hen.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hey_hen" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[서브클래싱, 커스텀UI에 Rx적용하기]]></title>
            <link>https://velog.io/@hey_hen/%EC%84%9C%EB%B8%8C%ED%81%B4%EB%9E%98%EC%8B%B1-%EC%BB%A4%EC%8A%A4%ED%85%80UI%EC%97%90-Rx%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hey_hen/%EC%84%9C%EB%B8%8C%ED%81%B4%EB%9E%98%EC%8B%B1-%EC%BB%A4%EC%8A%A4%ED%85%80UI%EC%97%90-Rx%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Jan 2022 14:48:53 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>특별한 건 아니고, <code>UIControl</code>을 상속해서 만든 <code>CustomTextField</code>가 있다. 굳이 <code>UITextField</code>를 상속해서 만들지 않았던 이유는, CustomTextField를 UIView처럼 사용해야 하기 때문이었다. 만약 UITextField를 사용했다면, 사이즈나, 애니메이션 등에서 불편한게 많기 때문이라고 생각.</p>
<h1 id="구현">구현</h1>
<p>어쨌든 그래서 UIControl을 사용해서 프로퍼티로 UITextField를 주입했는데
잘 알겠지만 TextField를 rx로 바인딩 할때</p>
<pre><code class="language-swift">let textField = UITextlField()
textField.rx.text //ControlProperty&lt;String&gt;</code></pre>
<p>이런식으로 사용하는데
<img src="https://images.velog.io/images/hey_hen/post/fd90d175-bc55-46cb-b579-e7f7c20fd1d1/image.png" alt="">
지금 형식은 View에서는 CustomTextField의 text속성에만 접근이 가능하고 UITextField에는 접근이 불가능하다. text속성에 rx 바인딩을 하고 싶어서 찾아봤는데, 생각보다 구글에 만족할 만한 답변이 없어 이것저것 시도해 보다가, 너무 심플하게 해결 됐다.</p>
<pre><code class="language-swift">  public lazy var rxText: ControlProperty&lt;String?&gt; = {
    self.textField.rx.text
  }()</code></pre>
<p> CustomTextField내부에서 textField.rx.text를 바인딩 하는 프로퍼티가 있으면 된다.</p>
<pre><code class="language-swift">let input = PhoneAuthViewModel.Input(
      textInput: textField.rxText.orEmpty.asObservable(),
      button: button.rx.controlEvent(.touchUpInside).asObservable())</code></pre>
<p>view에서는 위 처럼 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Design Pattern - Coordinator Part1]]></title>
            <link>https://velog.io/@hey_hen/Design-Pattern-Coordinator</link>
            <guid>https://velog.io/@hey_hen/Design-Pattern-Coordinator</guid>
            <pubDate>Fri, 21 Jan 2022 16:09:23 GMT</pubDate>
            <description><![CDATA[<h1 id="coordinator-pattern">Coordinator Pattern</h1>
<p>Coordinator Pattern은 Structure Design Pattern으로 View Controller간의 로직 흐름을 조직하기 위한 디자인 패턴이다.</p>
<p>간단하게 얘기하자면, 뷰간 화면 전환 Coordinator로 한번에 관리하겠다는 뜻이다. </p>
<p>5개의 컴포넌트로 이루어져 있다.</p>
<ul>
<li>코디네이터 프로토콜: View Present, Dismiss 메소드를 정의</li>
<li>코디네이터 인스턴스: 코디네이터 프로로콜을 채용한 인스턴스, 뷰 컨트롤러를 어떻게 만들 것인지 알고있다.</li>
<li>라우터 프로토콜: View Present, Dismiss 메소드를 정의</li>
<li>라우터 인스턴스: 라우터 프로토콜을 채용한 인스턴스, 코디네이터와 다른 점이라면, 어디서 무엇을 보여줄 것인지가 아니라, 어떻게 보여줄 것인지를 정의한다.</li>
<li>뷰 컨트롤러 인스턴스 들: 뷰 컨트롤러는 서로, 어디서 어떻게 표시될지는 모른다.</li>
</ul>
<p><img src="https://images.velog.io/images/hey_hen/post/ed5b2142-22a2-4027-ad08-b123c10c5261/image.png" alt=""></p>
<blockquote>
<p>코디네이터 패턴 클래스 다이어그램 srouce: <em>raywenderlich</em></p>
</blockquote>
<p>사용목적은 다음과 같다.</p>
<ul>
<li>뷰 컨트롤러간의 종속성을 때어내고 싶을 때</li>
<li>뷰 컨트롤러의 재사용성을 높이고 싶을 때</li>
<li>런타임에 뷰 컨트롤러 시퀀스가 결정될 때</li>
</ul>
<h1 id="router">Router</h1>
<p>Router는 Coordinator Pattern에서 어떻게 뷰의 Present, Dismiss의 방법을 정의한다.</p>
<pre><code class="language-swift">import UIKit

public protocol Router: AnyObject {
  func present(_ viewController: UIViewController, animated: Bool)
  func present(_ viewController: UIViewController, animated: Bool, onDismissed: (() -&gt; Void)?)
  func dismiss(animated: Bool)
}

extension Router {
  public func present(_ viewController: UIViewController, animated: Bool) {
    present(viewController, animated: animated, onDismissed: nil)
  }
}</code></pre>
<p>프로토콜 에서는 present와 dismiss 메소드를 정의한다.</p>
<h2 id="navigationrouter">NavigationRouter</h2>
<p>UINavigationController를 사용할 때 Router가 어떻게 동작하는지 만든다</p>
<pre><code class="language-swift">import UIKit

public class NavigationRouter: NSObject {
  private let navigationController: UINavigationController
  private let routerRootController: UIViewController?
  private var onDismissForViewController: [UIViewController: (()-&gt;Void)] = [:]

  public init(navigationController: UINavigationController) {
    self.navigationController = navigationController
    self.routerRootController = navigationController.viewControllers.first
    super.init()
    self.navigationController.delegate = self
  }
}</code></pre>
<p><code>NavigationRouter</code>는 <code>UINavigationController</code>와 <code>RootViewController</code>, 그리고 View가 Dismiss됐을 때 동작을 저장하는 Dictionary로 <code>[UIViewController: (() -&gt; Void)?)]</code>
정의한다.</p>
<p>이제 <code>NavigationRouter</code>는 Router 프로토콜을 채용한다.</p>
<pre><code class="language-swift">extension NavigationRouter: Router {
  public func present(_ viewController: UIViewController, animated: Bool, onDismissed: (() -&gt; Void)?) {
    onDismissForViewController[viewController] = onDismissed
    navigationController.pushViewController(viewController, animated: animated)
  }

  public func dismiss(animated: Bool) {
    guard let routerRootController = routerRootController else {
      navigationController.popToRootViewController(animated: animated)
      return
    }
    performOnDismissed(for: routerRootController)
    navigationController.popToViewController(routerRootController, animated: animated)
  }

  private func performOnDismissed(for viewController: UIViewController) {
    guard let onDismiss = onDismissForViewController[viewController] else {
      return
    }
    onDismiss()
    onDismissForViewController[viewController] = nil
  }
}</code></pre>
<p><code>present</code>시에는 present할 viewController를 파라메터로 가진다. 전달받은 viewController는 navigationController에 push되는데, 이때 onDismissForViewController에 onDismissed 클로져와 함께 등록된다.</p>
<p><code>dismiss(animated:)</code>메소드는 뷰가 dismiss될 때를 정의하는 것으로,
<code>routerRootViewController</code>가 없으면 가장 아래 뷰 컨트롤러까지 pop한다.</p>
<p>있을 경우는 평범하게 해당 뷰 컨트롤러까지 pop하는데 이 전에 <code>performOnDismissed</code>를 호출한다. 해당 메서드는 뷰를 present할때 정의해 두었던 dismiss 클로져를 실행하고, 딕셔너리에서 정리하기 위함</p>
<h2 id="coordinator">Coordinator</h2>
<p>코디네이터 프로토콜은 각 뷰 컨트롤러간의 계층 구조를 나타내기 위함이다.
코디네이터 프로토콜은 아래와 같다</p>
<pre><code class="language-swift">public protocol Coordinator: AnyObject {
  var children: [Coordinator] { get set }
  var router: Router { get }

  func present(animated: Bool, onDismissed: (()-&gt;Void)?)
  func dismiss(animated: Bool)

  func presentChild(_ child: Coordinator, animated: Bool)
  func presentChild(_ child: Coordinator, animated: Bool, onDismissed: (()-&gt;Void)?)
}</code></pre>
<p>Coordinator 프로토콜을 준수하는 객체를 children으로 가지고
router를 가진다.</p>
<pre><code class="language-swift">extension Coordinator {
  public func dismiss(animated: Bool) {
    router.dismiss(animated: animated)
  }

  public func presentChild(_ child: Coordinator, animated: Bool) {
    presentChild(child, animated: animated, onDismissed: nil)
  }

  public func presentChild(_ child: Coordinator, animated: Bool, onDismissed: (()-&gt;Void)?) {
    children.append(child)
    child.present(animated: animated, onDismissed: {[weak self, weak child] in
      guard let self = self, let child = child else { return }
      self.removeChild(child)
      onDismissed?()
    })
  }

  public func removeChild(_ child: Coordinator) {
    guard let index = children.firstIndex(where: { $0 === child }) else { return }
    children.remove(at: index)
  }
}</code></pre>
<p>child를 present 할 때는 children에 해당 child View Controller를 등록한다. onDismissed 클로져에는 실행 시에 스스로를 제거하는 클로져이다.</p>
<h1 id="simple-example">Simple Example</h1>
<p>간단한 실제 예제로 Coordinator 패턴을 좀더 들여다보자.
<img src="https://images.velog.io/images/hey_hen/post/fae802f7-8665-4f7b-b9b0-42e805faa5a4/image.png" alt=""></p>
<p>간단하게 각기다른 3개의 뷰 컨트롤러가 있다고 가정하자.
Coordinator 패턴이 아닌 일반적인 방법으로 계층을 보면</p>
<ol>
<li>ViewController1에서 ViewController2를 인스턴스화 하고 push한다(네비게이션 기준, present일 수도 있다.)</li>
<li>ViewController2에서 ViewController3를 인스턴스화 하고 push한다.</li>
</ol>
<p>이고 각 뷰컨트롤러 내용에는 화면 전환에 대한 코드가 존재할 것이다.</p>
<pre><code class="language-swift">//View Controller1
func buttonAction() {
    navigationController?.pushViewController(ViewController2, animated: true)
    //or
    present(ViewController2, animated: true)

}

//View Controller2
func buttonAction() {
    navigationController?.pushViewController(ViewController3,     animated: true)
    //or
    present(ViewController3, animated: true)
}


//View Controller3
func buttonAction() {
    navigationController?.popViewController(animated: true)
    //or
    dismiss(animated: true))
}
</code></pre>
<p>Coordinator 패턴에서는 이 부분이 뷰 컨트롤러에서 빠지고, 두 컴포넌트, Coordinator와 Router로 분리되어 작성된다.</p>
<p>그러면 뷰컨트롤러에서는 Coordinator에게 나 <em>버튼 눌렸으니 화면 이동해줘!</em> 라고 핸들러나 델리게이트로 알려주기만 하면 된다. 어떻게 전환할지(push, present)는 Router에서,
어디로 이동할지(ViewController1 -&gt; ViewController2)는 Coordinator에서 관리하게 된다.</p>
<p><img src="https://images.velog.io/images/hey_hen/post/acdb98f7-c510-46c0-a25c-9917d8ce8e91/image.png" alt=""></p>
<p>Concrete Coordinator에서는 ViewController의 계층을 알고있고, View Controller에서 델리게이트 또는 핸들러 등으로 화면 전환이 필요하다고 요청하면, Router를 통해 어떻게 전환할 것인지 결정한다.</p>
<h1 id="장점">장점</h1>
<p>Coordinator Pattern의 장점은 위 처럼 하여, 여기저기 흩어져 있는 뷰 계층을 Coordinator에서 한눈에 파악이 가능하고, 뷰 전환에 관련된 코드가 뷰 컨트롤러에서 빠지므로, 뷰가 좀더 정말 보여주는 거에만 집중할 수 있다.
다만 너무 작은 시스템에서는 오히려, 오버킬이 될 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftGen 사용기 (Homebrew)]]></title>
            <link>https://velog.io/@hey_hen/SwfitGen-%EC%82%AC%EC%9A%A9%EA%B8%B0-Homebrew</link>
            <guid>https://velog.io/@hey_hen/SwfitGen-%EC%82%AC%EC%9A%A9%EA%B8%B0-Homebrew</guid>
            <pubDate>Wed, 19 Jan 2022 18:09:09 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>SwiftGen을 간단하게 소개하자면</p>
<p>이해하기 쉽게 시나리오로 설명을 해보려고 한다.</p>
<ul>
<li>Assets 카탈로그에 Icons, Image, Color등을 정의하고 이를 프로젝트에서 사용하기 위해서는 각각의 생성자 (Color로 예를들자면 <code>UIColor(named:)</code> 를 통해 Asset에 지정한 문자열 상수를 넣어 불러와야 한다.<pre><code class="language-swift">let customColor = UIColor(named: &quot;customRed&quot;)!</code></pre>
해당 방법은 문자열을 하드코딩 한다는 점에서, 실수가 발생할 여지가 있고, 또 런타임에 에러가 결정되기 때문에 컴파일 타임에 해당 실수를 잡아내기 어렵다.</li>
</ul>
<p>그래서 보완책으로 아래 처럼 사용해볼 수 있었다.</p>
<pre><code class="language-swift">enum Constans: String {
  let customRed = &quot;customRed&quot;
}

extension UIColor {
  static var customRed: UIColor {
    UIColor(named: Constans.customRed.rawValue) ?? UIColor()
  }
}

let customRed: UIColor = .customRed</code></pre>
<p>추가한 에셋이 많을 경우는 매크로 코드를 짜서 일괄로 생성하는 방법도 썼었다.</p>
<p>이 부분을 간편하게 해주도록 나온 것이 바로 SwiftGen이다.
SwiftGen은 위 과정을 템플릿을 통해 일괄적으로 작업할 수 있게 해주고, 
하드코딩에 의한 실수, 또 컴파일 타임에 에러를 잡을 수 있도록 도와준다.
어떻게 사용하는지 알아보자.</p>
<h1 id="설치">설치</h1>
<p>설치 전에 나의 작업 환경은 다음과 같다.</p>
<ol>
<li>m1 macbook pro 16g</li>
<li>Xcode 13.2</li>
<li>Monterey 12.1</li>
</ol>
<p><a href="https://github.com/SwiftGen/SwiftGen#installation">설치 방법</a>
github에 잘 소개 되어있기도 한데 여러 방법마다 장단이 있는 것 같다.</p>
<ol>
<li>Download the ZIP</li>
<li>CocoaPods</li>
<li>Homebrew</li>
<li>Mint
Zip과 CocoaPods는 프로젝트 별로 따로 적용하는데 이용하기 편하고
Homebrew와 Mint는 시스템 전체적으로 사용할 수 있기 때문에,프로젝트 마다 일일이 설정하기 싫은 경우 선택할 수 있다.</li>
</ol>
<p>나는 Homebrew를 선택했고, Homebrew로 설치했지만, 프로젝트마다 다르게 swiftgen.yml을 다르게 적용해보려고 한다.</p>
<h2 id="homebrew-설치">Homebrew 설치</h2>
<p>terminal에서 brew로 swiftgen을 설치한다.</p>
<pre><code>brew update
brew install swiftgen</code></pre><p>이 경우 hoem directory에 기본적으로 swiftgen.yml이 위치하게 된다.</p>
<h2 id="swiftgen-기본-사용법">swiftgen 기본 사용법</h2>
<p>swiftgen의 기본 사용 방법은 swiftgen.yml을 통해 템플릿을 작성하고
swiftgen으로 템플릿을 파싱하는 식이다. </p>
<p>swiftgen.yml에서는 번들의 <code>Assets.xcassets</code>를 파싱해서
<code>Asset+Generated.swift</code>(예시 이름)를 생성한다.</p>
<p><code>Asset+Generated.swift</code>에는 열거형으로 Asset에 정의한 것들을 접근할 수 있게 한다.
예를들어</p>
<pre><code class="language-swift">let customRed: UIColor = Asset.Color.customRed.color</code></pre>
<p>위와같이 쓸 수 있다. 타입 어노테이션은 참고하라고 써둔 것이고 필수가 아니다.</p>
<p>일단은 저게 어떻게 돌아가는지 간단하게 테스트 해보자.</p>
<p>터미널에서 다음과 같이 입력한다.</p>
<pre><code>cd ~/Desktop
swiftgen config init</code></pre><blockquote>
<p>만약 여기서 에러 났으면 brew로 swiftgen이 설치가 안된거다.</p>
</blockquote>
<p>우선 사용하기 편하게 데스크톱 디렉토리로 이동 한 뒤 작업한다.
위 명령어는 swiftgen.yml을 생성하는 명령어이다.
Dekstop 디렉토리로 가면 swiftgen.yml이 생성되어 있을 것이다.
아마 <code>swiftgen config init</code> 명령어를 입력하면 swiftgen.yml 파일이 자동으로 편집기로 열릴 것이다.</p>
<p><code>swiftgen.yml</code>내용을 보면 주석으로 사용법이 자세하게 적혀있는게 나중에 디테일 하게 사용하고 싶다면 꼭 읽어보자</p>
<p>우리는 우선 asset에만 집중해보자.</p>
<ul>
<li>프로젝트에서 방금 만든 <code>swiftgen.yml</code>을 가져오자.
<img src="https://images.velog.io/images/hey_hen/post/fd92d3d3-d6c8-47ba-996f-3ae2d727744c/image.png" alt=""></li>
</ul>
<p>릴리즈 하고 나면 swiftgen이 실행 될 일이 없기 때문에, 따로 가져올 필요가 없지만, 설명에 편의를 위해 가져왔다. 프로젝트에 따로 추가하지 않아도 되고 필요한 디렉토리 위치시켜 두고 위치만 지정하면 된다.</p>
<p>yml내용을 보면 </p>
<pre><code># xcassets:
#   inputs:
#     - Main.xcassets
#     - ProFeatures.xcassets
#   outputs:
#     - templateName: swift5
#       params:
#         forceProvidesNamespaces: true
#       output: XCAssets+Generated.swift</code></pre><p>이 부분이 Asset을 파싱 후 컨버팅 해주는 부분으로, 조리해서 사용하면 된다.</p>
<p>사용해보려고 하는 부분은 Color Asset을 별도로 만들어 볼려고 한다.</p>
<p><code>Asset Catalog</code>를 하나 새로 만든다.
<img src="https://images.velog.io/images/hey_hen/post/ae19f0f6-61ec-4282-9f16-37a68119d755/image.png" alt=""></p>
<p>이름은 Color 로
<img src="https://images.velog.io/images/hey_hen/post/4a0fb82f-93a1-4f45-86d1-d81ec21e49c6/image.png" alt=""></p>
<p>Color Asset안에 커스텀 컬러 몇개 저장 후
<img src="https://images.velog.io/images/hey_hen/post/1bd3527e-e0e2-479d-a8e1-b7352a4e4d1c/image.png" alt=""></p>
<p>swiftgen.yml에 다음과 같이 내용을 추가한다.</p>
<pre><code>xcassets:
  inputs:
    - Color.xcassets
  outputs:
    - templateName: swift5
      params:
        enumName: Colors
      output: Color+Generated.swift</code></pre><p>내용을 찬찬히 뜯어보면</p>
<ul>
<li>xcassets: xcassets 형태를 파싱하기 위한 명령어로 문서보면 xcassets말고도 string, ib 등등이 있다.</li>
<li>inputs: 입력할 파일로 우리가 생성한 Asset Catalog를 전달</li>
<li>outputs: 출력할 영역<ul>
<li>templateName: swift5를 쓰자, 특별히 swift4를 쓸일이 있으면 해당 내용을 전달하면 된다. 자세한 내용은 문서</li>
<li>params: 파일을 출력할 때 몇가지 옵션을 줄 수 있다.</li>
<li>enumName: 따로 설정하지 않으면 기본 열거형의 이름이 Asset로 정해진다. </li>
<li>output: 출력할 파일 이름</li>
</ul>
</li>
</ul>
<p>작성이 끝났으면 terminal로 해당 yml이 있는 디렉토리로 찾아간다.
그리고 아래 명령어를 입력
<code>swiftgen config run</code>
성공적으로 명령어가 동작했다면
<code>File written: Color+Generated.swift</code>
이라고 표시된다.
지금은 아무것도 보이지 않을텐데 해당 디렉토리로 가 보면
<code>Color+Generated.swift</code>파일이 생성 되어 있을 것이다.
이를 프로젝트에 추가한다.</p>
<pre><code class="language-swift">struct ContentView: View {
  var body: some View {
    Text(&quot;Hello, world!&quot;)
      .padding()
      .foregroundColor(Colors.customRed.color)
  }
}
extension View {
  func foregroundColor(_ uicolor: UIColor) -&gt; some View {
    self.foregroundColor(Color(uicolor))
  }
}</code></pre>
<ul>
<li>extension은 foregroundColor에서 UIColor로 바로 생성할 수 있도록 작성 된것</li>
</ul>
<p>실제로 컬러는
<code>Color.customRed.color</code>로 접근할 수 있다. 형태는 <code>Color</code>는 yml에서 작성한 enumName이다.</p>
<p>여기까지가 기본적인 작업 플로우로, 정리해보면</p>
<ul>
<li>swiftgen.yml 을 생성</li>
<li>swiftgen.yml 템플릿 내용을 작성</li>
<li>terminal에서 <code>swiftgen config run</code></li>
<li>생성된 swift파일 프로젝트에 추가
이다.</li>
</ul>
<p>여기서 좀더 생각해보면, Asset Catalog에 새 Asset이 추가될 때 마다 terminal 키고 <code>swiftgen config run</code> 하면 너무 귀찮은데? 라는 생각이 들 수 있다. 이부분을 자동화 시켜줄 수 있는것이 바로 <code>Run Script</code>이다.</p>
<h2 id="runscript">RunScript</h2>
<p>프로젝트 -&gt; 타겟 -&gt; 빌드 페이즈(Build Phases)로 이동한 뒤
Run Script를 하나 추가하자.
<img src="https://images.velog.io/images/hey_hen/post/46f9b54d-175f-4509-94a8-55d801e2f5e4/image.png" alt="">
run script는 빌드 할 때 자동으로 터미널 켜서 스크립트 안에 적힌 내용을 실행시켜 주는 기능이라고 생각하면 된다.</p>
<p><img src="https://images.velog.io/images/hey_hen/post/63a4173e-3fc0-460e-adb4-d3dcbcf499d7/image.png" alt="">
요 Shell 안에 내용을 작성할 것인데</p>
<p>여기서 앞서 우리가 터미널에서 했던
<code>swiftgen config run</code> 을 작성해주면
빌드 될 때 마다 파싱해서 <code>Generated.swift</code>를 생성해 줄 것이다.</p>
<p><img src="https://images.velog.io/images/hey_hen/post/c8f77a66-cc7d-4b93-97ed-4580ee290c5f/image.png" alt="">
위 내용을 넣고 빌드해보면~
<img src="https://images.velog.io/images/hey_hen/post/0ce61a07-7a6e-42f1-8892-23cca8174173/image.png" alt="">
에러가 난다.
별 내용은 아니고 terminal에서 swiftgen 실행시키려고 하는데 없어서 그러는 거다.
brew에 설치된 swiftgen을 실행시켜야 해서 패스를 지정해 준다.</p>
<pre><code>PATH=/opt/homebrew/bin:$PATH
swiftgen config run</code></pre><p><img src="https://images.velog.io/images/hey_hen/post/bbe96a70-2478-4b84-873d-ba999bd51c35/image.png" alt=""></p>
<p>여기서 실행 시켜보면 또 에러가 난다.
<img src="https://images.velog.io/images/hey_hen/post/cf6fba00-b58a-4045-b1e3-686354c65246/image.png" alt=""></p>
<p>이번에는 swiftgen.yml을 찾을 수가 없다고 나온다. 엥?
그래서 해당 명령어를 실행하기 전에 위치한 디렉터리가 어딘지 보자</p>
<pre><code>PATH=/opt/homebrew/bin:$PATH
pwd
ls
swiftgen config run</code></pre><p>스크립트에 위 내용으로 변경후 발생하는 에러를 보면
<img src="https://images.velog.io/images/hey_hen/post/5b9cf937-73c0-41c9-848b-825762c5e5ce/image.png" alt="">
프로젝트 폴더에는 가있는데 타겟 폴더에 안들어가 있다.</p>
<p>타겟 폴더 안으로 들어가면 되겠다.</p>
<pre><code>PATH=/opt/homebrew/bin:$PATH
cd SwiftGenPractice
swiftgen config run</code></pre><p>이제 빌드해보면 에러가 발생하지 않고 정상 빌드 될 것이다!</p>
<h3 id="인라인">인라인?</h3>
<p>이 경우는 swfitgen.yml을 찾고 내용대로 파싱해서 파일을 생성하는 방식인데 사실 예제처럼 파싱할 내용이 많지 않으면 yml없이 인라인으로 작성할 수도 있다.</p>
<pre><code>swiftgen xcassets Resources/Images.xcassets --templateName swift5 --output &quot;Constants/Assets+Generated.swift&quot;</code></pre><h1 id="정리">정리</h1>
<ol>
<li>swiftgen은 자동화 도구로 사용만 잘하면 굉장히 유용하다.</li>
<li>굳이굳이 homebrew로 설치하고, 프로젝트 마다 다르게 사용하려면 위 처럼 고생한다. 잘 동작은 하는데 이게 맞는 방법인지는 모르겠다.</li>
<li>템플릿을 커스텀하거나, 파싱하는데 더 다양한 옵션을 제공하므로 <a href="https://github.com/SwiftGen/SwiftGen">문서</a>를 꼭 확인하자.
<a href="https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/SwiftGenPractice">전체코드</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift, Line Height]]></title>
            <link>https://velog.io/@hey_hen/Swift-Line-Height</link>
            <guid>https://velog.io/@hey_hen/Swift-Line-Height</guid>
            <pubDate>Wed, 19 Jan 2022 06:48:10 GMT</pubDate>
            <description><![CDATA[<p>Figma로 디자인을 확인하면 폰트쪽에 LineHeight를 정의 해 두는데, 한번도 적용해보지 않은 개념이라, 방법을 찾아보았다.</p>
<p><img src="https://images.velog.io/images/hey_hen/post/5f096b96-0963-478c-9b35-e022f1d37260/image.png" alt="">
위와 같은 상태인데, 디자인이 저렇게 나왔으면, 그냥 UILabel을 간격에 맞게 2개 써도 되지 않나? 라고 생각이 될 수도 있다.</p>
<p>그런데 만약에 몇줄이 되어야 하는 상황일지 모른다면?
또 UILabel이 아니라 채팅 서비스에서 몇줄이나 되는 채팅 UITextView 라면? </p>
<p><code>attributedText</code>로 적용 가능한 속성 중에 <code>paragraphStyle</code>에서 Line Height를 일괄로 적용이 가능하다.</p>
<pre><code class="language-swift">let style = NSMutableParagraphStyle()
let fontSize: CGFloat = 20
let lineheight = fontSize * 1.6  //font size * multiple
style.minimumLineHeight = lineheight
style.maximumLineHeight = lineheight

label.attributedText = NSAttributedString(
  string: greeting,
  attributes: [
    .paragraphStyle: style
  ])
label.font = .systemFont(ofSize: fontSize)</code></pre>
<p><img src="https://images.velog.io/images/hey_hen/post/766ff0be-4ad5-4dcb-84b1-2f5854b549cc/image.png" alt=""></p>
<p>여기서 한가지 고려해봐야 할 부분은, 바운드에 대한 Inset값인데, UILabel 배경색을 입혀보면
<img src="https://images.velog.io/images/hey_hen/post/ed5c9469-f8d1-4ed3-9ec1-d2926cdfe47a/image.png" alt="">
텍스트가 아래로 정렬이 된 것을 볼 수 있다.
만약 위로 정렬하고 싶다면</p>
<p>attributes 속성의 baselineOffset 을 이용할 수 있다.</p>
<pre><code class="language-swift">.baselineOffset: (lineheight - fontSize) / 4,</code></pre>
<p><img src="https://images.velog.io/images/hey_hen/post/1e820944-05d0-4a9f-87eb-29f03449a18e/image.png" alt=""></p>
<p>똑같은 방식으로 <code>UITextView</code>에도 적용된다.</p>
<p><a href="https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/UIKit/LineHeight.playground">전체코드</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Firebase Auth 전화번호 회원가입]]></title>
            <link>https://velog.io/@hey_hen/Firebase-Auth-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</link>
            <guid>https://velog.io/@hey_hen/Firebase-Auth-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</guid>
            <pubDate>Mon, 17 Jan 2022 16:45:52 GMT</pubDate>
            <description><![CDATA[<p>Firebase Auth에서 제공하는 전화번호 인증해보기</p>
<h1 id="1-사전작업">1. 사전작업</h1>
<p>우선 Firebase Auth 전화번호 인증은, Slient Push Notification을 이용해 토큰을 보내기 때문에 Push Notification을 사용하는 것과 동일하다.</p>
<p>따라서 아래의 준비가 필요하다.</p>
<ul>
<li>유료 애플 개발자 계정</li>
<li>Xcode Project Setup</li>
<li>Apple Developer Member Center Identifier</li>
</ul>
<h2 id="유료-애플-개발자-계정">유료 애플 개발자 계정</h2>
<ul>
<li>앞서 말했지만, 유료 애플 개발자 계정을 필요로 한다.</li>
</ul>
<h2 id="xcode-project-setup">Xcode Project Setup</h2>
<ul>
<li>Push Notification을 받는 것이기 때문에 Xcode 설정이 필요하다.</li>
<li>Project - Target - Signing &amp; Capabilites - Push Notifications
<img src="https://images.velog.io/images/hey_hen/post/9c7b75e8-b2e0-4c44-8c07-06719b98e952/1.png" alt=""></li>
</ul>
<h2 id="apple-developer-member-center-identifier">Apple Developer Member Center Identifier</h2>
<p>인증토큰은 Firebase project에 등록해야할 토큰으로 아래 과정을 통해서 만들 수 있다.</p>
<p>Developer Apple의 Member Center로 이동 후, Keys탭으로 간다.</p>
<p><img src="https://images.velog.io/images/hey_hen/post/4c12b406-c0fb-4366-a206-9e2ec4530c57/2.png" alt=""></p>
<p><img src="https://images.velog.io/images/hey_hen/post/650b10d9-e97c-4a22-85de-35115254761b/3.png" alt=""></p>
<p>적당히 알아볼 만한 이름을 작성하고, APNs를 체크 한 후 Continue - Register</p>
<p>그다음 Download가 나오는데</p>
<p>경고문을 찬찬히 읽어보면, 보안을 위해 다운로드 후에는 서버에서 해당 키를 삭제한다고 한다. 그러니까 다운받은 그것이 유일본이기 때문에 잃어버리지 않도록 안전한 곳에 보관해야 한다.</p>
<p>키를 다운받아 보면 AuthKey_ABCDEFGH~ 이런식으로 되어있는데 언더바 뒤의 알파벳,숫자 조합이 바로 키 아이디가 된다.</p>
<p>이제 디바이스 토큰, 인증 토큰(키), 그리고 하나 더 필요한 것은 TeamID인데 이것은 화면 우측 상단 계정에 이름 아래에 있다.</p>
<p><img src="https://images.velog.io/images/hey_hen/post/005f89f5-d3c1-4953-bb0a-ef957c226621/4.png" alt=""></p>
<p>위 토큰을 이제 Firebase에 넣어주면 된다.</p>
<p>해당 내용은 파이어베이스 전화번호 인증 문서 참고</p>
<p><a href="https://firebase.google.com/docs/auth/ios/phone-auth?authuser=0">Firebase 인증 문서</a></p>
<h1 id="2-프로젝트">2. 프로젝트</h1>
<p>파이어베이스에 프로젝트에 앱을 등록했으면, 이제 프로젝트에 작성할 일만 남았다.</p>
<p>우선 본인은 SwiftUI로 했는데 SwiftUI 처음 진입하면 AppDelegate가 없다.</p>
<p>다음과 같이 작성해준다. (프로젝트명App.swift)</p>
<pre><code class="language-swift">import SwiftUI
import Firebase

class AppDelegate: NSObject, UIApplicationDelegate {

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&gt; Bool {

    FirebaseApp.configure()
    return true
  }

  func application(_ application: UIApplication,
                   didReceiveRemoteNotification notification: [AnyHashable : Any],
                   fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -&gt; Void) {

  }
}

@main
struct FirebasePhoneAuthPracticeApp: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
</code></pre>
<p>노티피케이션의 기본적인 플로우는 아래와 같다.</p>
<ol>
<li><code>application(_:didFinishLaunchingWithOptions:)</code> 에서 <code>registerForRemoteNotifications</code>를 통해 APN으로 디바이스 토큰을 전송한다</li>
<li>APN은 앱에 디바이스 토큰을 반환하고 이때 <code>application(_:didRegisterForRemoteNotificationsWithDeviceToken:)</code>  호출하거나 토큰 등록에 실패했을 시 <code>application(_:didFailToRegisterForRemoteNotificationsWithError:)</code> 를 호출하여 알린다.</li>
<li>디바이스는 토큰을 binary 또는 hexadecimal 포맷으로 provider(서버)로 토큰을 보낸다. provider가 이제 토큰을 추적하기 시작한다.</li>
<li>provider가 notification 요청을 다수의 토큰과 함께 APNs로 보낸다.</li>
<li>마지막으로 APNs가 notification을 유효한 토큰이 제공된 각 디바이스에 알림을 보낸다.</li>
</ol>
<p>그런데 Firebase 전화번호 Auth는 Slient Push Notification이기 때문에 <code>application(_:didFinishLaunchingWithOptions:)</code> 는 FirebaseApp초기화 때문에 필요하고 나머지는 필요하지 않다.</p>
<p>따라서 Slient Push를 이용하기 위한 사전 작업이 따로 필요하다.</p>
<h2 id="background-modes">Background Modes</h2>
<p>Slient Push Notification은 사용자에게 보이지 않는 기능이고 백그라운드에서 동작한다. 따라서 Sining &amp; Capabilites에 Background Modes를 추가하고 거기서 Remote Notifications을 체크 한다.
<img src="https://images.velog.io/images/hey_hen/post/e202d16a-d68d-4ad6-bdf5-2611d8bd81bc/5.png" alt=""></p>
<h2 id="app-delegate">App Delegate</h2>
<pre><code class="language-swift">func application(
  _ application: UIApplication,
  didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  fetchCompletionHandler completionHandler:
  @escaping (UIBackgroundFetchResult) -&gt; Void
) {
  ...
}</code></pre>
<p> <code>application(_:didReceiveRemoteNotification:fetchCompletionHandler)</code> 델리게이트 메서드가 있어야 한다. 해당 코드는 이미 작성했으므로 Slient Push Notification을 사용할 준비가 되었다.</p>
<p>또 Slient Push Notification은 유저에게 알람이 발생하지 않으므로 알람에 대한 동의를 받을 필요가 없다.</p>
<h1 id="인증하기">인증하기</h1>
<p>인증하는 작업 플로우는 다음과 같다.</p>
<ol>
<li>전화번호를 검증하고 요청한다.</li>
<li>1번 과정이 성공적으로 진행되면 해당 번호로 인증번호가 온다.</li>
<li>유저가 작성한 인증번호가, 실제로 서버에서 보낸 인증번호가 일치하는 작업을 한다.</li>
<li>3번이 성공적일 경우 로그인 루틴을 실행한다.</li>
</ol>
<h2 id="1-전화번호-검증">1. 전화번호 검증</h2>
<pre><code class="language-swift">func requestVerifyCode() {
    buttonDisabled = true
    let validPhoneNumber = &quot;+82\(phoneNumber)&quot;
    PhoneAuthProvider.provider().verifyPhoneNumber(validPhoneNumber, uiDelegate: nil) { verification, error in
      if let error = error {
        print(&quot;error: \(error)&quot;)
        buttonDisabled = false
      } else {
        self.verificationId = verification
        withAnimation {
          watingVeryfiId = true
        }
      }
    }
  }</code></pre>
<p>텍스트필드에 입력된 전화번호를 검증하고 Firebase서버에 요청하는 코드로</p>
<p>verifyPhoneNumber(_:uiDelegate:completion:) 메서드를 통해 요청한다.</p>
<p>첫번째 인자에 유저가 입력한 전화번호가 들어가면 된다. 입력은 통상적으로 입력하는 010~ 패턴이면 되는데 앞에 국가코드를 포함해야 한다.</p>
<p>전화번호 검증이 성공했을 때 <strong>인증코드</strong>가 전화번호로 날아오게 된다.</p>
<h2 id="2-인증번호-검증">2. 인증번호 검증</h2>
<pre><code class="language-swift">func verifyLogin() {
    isVerifying = true
    let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationId ?? &quot;&quot;, verificationCode: userVerify)
    Auth.auth().signIn(with: credential) { (success, error) in
      if let error = error {
        print(&quot;error: \(error.localizedDescription)&quot;)

      } else {
        print(&quot;success&quot;)
        showMainView = true
      }
      isVerifying = false
    }
  }</code></pre>
<p>이제 유저가 받은 인증코드를 다른 텍스트필드에 입력하고, 1에서 받은 인증코드와 비교하는 메서드를 작성한다.</p>
<p><code>credential(withVerificationID:verificationCode)</code> 메소드에 첫번째 인자는 verifyPhoneNumber메서드로 받은 인증코드, 그리고 두번째 인자에는 유저가 입력한 인증코드가 된다. 두 개가 일치하게 되면 인증이 성공하는 것이다.</p>
<p>기본 코드만 작성 한 것으로 토큰 관리나, 로그인 상태 관리 등은 꼭 별도로 해야한다!
<img src="https://images.velog.io/images/hey_hen/post/aa9b177b-9569-47eb-a82b-9f38d45970ce/FirebaseAuth.gif" alt=""></p>
<p><a href="https://github.com/urijan44/TIL-Store/tree/master/Firebase/FirebasePhoneAuthPractice">전체 코드 보기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[iOS 15.0 UIButton]]></title>
            <link>https://velog.io/@hey_hen/iOS-15.0-UIButton</link>
            <guid>https://velog.io/@hey_hen/iOS-15.0-UIButton</guid>
            <pubDate>Sun, 16 Jan 2022 14:32:22 GMT</pubDate>
            <description><![CDATA[<style>
img {
  width:50%;
  display: block;
  margin-left: auto;
  margin-right: auto;
}
</style>

<p>WWDC2021가 발표한지는 한참 지났지만, 정리해보는 UIButton,
iOS15.0의 버튼이 특별히 달라지는 것은 아니지만, 기존에 서브클래싱 등으로 해결해야 했던것을 <code>Configuration</code>을 통해 제공해주는 것이 핵심이다.</p>
<h1 id="basic-style">Basic Style</h1>
<p>iOS15.0 UIButton은 기본 4가지 스타일을 제공한다.</p>
<ul>
<li>Plain</li>
<li>Gray</li>
<li>Tinted</li>
<li>Filed</li>
</ul>
<img src="https://images.velog.io/images/hey_hen/post/e2284c7a-b654-48f0-950b-bf34b663e3a7/image.png">

<p>전통적인 UIButton은 다음과 같을 것이다.</p>
<pre><code class="language-swift">let button = UIButton(type: .system)
button.setTitle(&quot;Sign In&quot;, for: [])</code></pre>
<img src="https://images.velog.io/images/hey_hen/post/e7f546e3-18e6-4c4d-8b29-38422b174490/image.png">

<p>여기에 Basic Style을 적용하는 방법은 아래와 같다.</p>
<pre><code class="language-swift">let button = UIButton(type: .system)
button.setTitle(&quot;Sign In&quot;, for: [])
button.configuration = .filled()</code></pre>
<img src="https://images.velog.io/images/hey_hen/post/29d81bbd-c09a-4938-ac73-a7b383b75c2a/image.png">

<p>Basic Style을 적용해서 <code>BackgroundColor</code> Tint와<code>CornerRadius</code>가 있는 버튼을 빠르게 얻을 수 있다. 
바닐라 스타일을 사용하는 사람들에게는 편리한 기능으로 보인다.</p>
<h2 id="uibuttonconfiguration">UIButton.Configuration</h2>
<p>UIButton 인스턴스를 생성 한 후에, 프로퍼티들을 바꾸는 것이 아니라, UIButton.Configuration을 미리 만들고, UIButton 인스턴스를 만들 때 configuration을 생성자에 전달할 수 있다.
이 방법은 굳이 UIButton을 서브클래싱 하는 것보다 더 나은 재사용성을 누릴 수 있다.</p>
<p>위 예제에서는 button을 생성 한 뒤에, 타이틀을 입력했지만 configuration을 사용하면 아래와 같이 할 수 있다.</p>
<pre><code class="language-swift">var config = UIButton.Configuration.tinted()
config.title = &quot;Hello, Swift!&quot;
config.image = UIImage(systemName: &quot;swift&quot;)

let button = UIButton(
  configuration: config,
  primaryAction: UIAction(handler: { _ in
    print(&quot;Hello, Swift!&quot;)
  }))</code></pre>
<img src="https://images.velog.io/images/hey_hen/post/6f47304b-cf66-4094-a7dd-55c0af699b46/image.png">

<p>configuration에는 다양한 옵션이 있기 때문에 입맛대로 사용하기도 편하다.
예를들어서 이미지 위치가 왼쪽이 아니라 오른쪽에 위치했으면 좋겠다고 한다면</p>
<pre><code class="language-swift">config.imagePlacement = .trailing</code></pre>
<p>이 한출 추가하는 것으로 이미지는 오른쪽으로 이동한다.
<img src= "https://images.velog.io/images/hey_hen/post/2dcf9871-b7ab-44bf-8c8c-c588de9b4008/image.png">
이 외에도 타이틀과 이미지의 패딩, 타이틀의 정렬 기준 등등 다양하기 때문에 문서를 보고 사용해 보길 권장</p>
<h3 id="configurationupdatehandler">ConfigurationUpdateHandler</h3>
<p>Button을 사용하다보면, 항상 똑같은 모습을 보여주는 정적인 버튼 말고도, 상태에 따라 변하는 동적인 버튼을 많이 사용한다.
기존에는 버튼 상태를 나타내는 모델에서 모델이 변경되면 버튼을 업데이트 하도록 구성할 수 있었다.</p>
<pre><code class="language-swift">   button.configurationUpdateHandler =  { [unowned self] button in
      var config = button.configuration
      config?.title = &quot;오늘의 할일&quot;
      config?.image = self.done
      ? UIImage(systemName: &quot;checkmark.square.fill&quot;)
      : UIImage(systemName: &quot;square&quot;)
      config?.subtitle = self.done
      ? &quot;01/16 Done!&quot;
      : &quot;01/16&quot;
      button.configuration = config
    }</code></pre>
<pre><code class="language-swift">button.setNeedsUpdateConfiguration()</code></pre>
<p><code>setNeedsUpdateConfiguration()</code>을 호출해서, 버튼 UI의 갱신상태를 변경 요청할 수 있다.</p>
<h2 id="other-features">Other Features</h2>
<p><code>Button.Configuration</code>은 이외에도 다양하고 유용한 기능을 제공한다.</p>
<ul>
<li>Activity Indicator: UIButton에 별도로 서브클래싱 없이 Activity Indicator를 넣을 수 있다.</li>
<li>Metrics adjustments: 타이틀, 이미지, 서브 타이틀간의 위치를 설정할 수 있다.</li>
<li>Semantic styling: 버튼을 만들기가 더 쉬워졌다.</li>
<li>Customization: Color, TextAttribute 등 커스텀에 필요한 기능을 Configuration을 통해 제공한다.</li>
</ul>
<p><img src="https://images.velog.io/images/hey_hen/post/5643bcb8-753c-40b4-8db4-c7f85a3454eb/image.png" alt=""></p>
<p>기존에 UIButton에 Activity Indicator를 이용하려면 서브클래싱을 통해서 해야했는데 Button.Configuration에 이 기능이 들어갔다.</p>
<pre><code class="language-swift">
var loggin: Bool = false {
    didSet {
      button.setNeedsUpdateConfiguration()
      button.isEnabled = !loggin
    }
  }

button.configurationUpdateHandler =  { [unowned self] button in
      var config = button.configuration
      config?.title = &quot;Sign In&quot;
      config?.showsActivityIndicator = loggin
      config?.image = UIImage(systemName: &quot;externaldrive.connected.to.line.below&quot;)
      button.configuration = config
    }

func requestLoggin() {
  loggin = true
  //Network Request Code
  DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
     self.loggin = false
  }
}   
</code></pre>
<p><code>configurationUpdateHandler()</code>에 <code>showActivityIndicator</code>만 설정해 주면 된다.
예를들어서 로그인을 요청하면 서버에서 응답이 올때까지 버튼에 인디케이터가 표시 되어야 하는 경우, 다음 과 같이 코드를 작성할 수 있다.
<img src="https://images.velog.io/images/hey_hen/post/73d8f66f-f1f6-4151-9b61-2063f05813d4/%E1%84%92%E1%85%AA%E1%84%86%E1%85%A7%E1%86%AB%20%E1%84%80%E1%85%B5%E1%84%85%E1%85%A9%E1%86%A8%202022-01-16%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.38.08.gif" alt=""></p>
<h1 id="toggle-button">Toggle Button</h1>
<p>버튼의 On/Off 상태를 좀더 편리하게 사용해주는 새 기능으로 해당 프로퍼티를 true로 설정하게 되면 버튼 선택에 따른 상태를 얻을 수 있다.</p>
<pre><code>button.changesSelectionAsPrimaryAction = true</code></pre><p><code>changeSelectionAsPrimaryAction</code>을 true로 하면 버튼 토글 상태가 <code>button.isSelected</code>로 전달된다.
맨 위에서 했던 예제로 다시 돌아가자면</p>
<pre><code class="language-swift">button.changesSelectionAsPrimaryAction = true

button.configurationUpdateHandler =  { button in
  var config = button.configuration
  config?.title = &quot;오늘의 할일&quot;
  config?.image = button.isSelected
  ? UIImage(systemName: &quot;checkmark.square.fill&quot;)
  : UIImage(systemName: &quot;square&quot;)
  config?.subtitle = button.isSelected
  ? &quot;01/16 Done!&quot;
  : &quot;01/16&quot;
  button.configuration = config
}</code></pre>
<p>버튼의 상태를 isSelected로 선택해볼 수 있다.</p>
<h1 id="pop-up-button">Pop-up button</h1>
<p>버튼을 구성할 때 팝업 버튼 형식으로 쓰고 싶을 때는 <code>showMenuAsPrimaryAction</code> 을 <code>true</code>로 두기만 하면 된다.</p>
<pre><code class="language-swift">button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(children: [
  UIAction(title: &quot;삭제하기&quot;, attributes: .destructive, handler: deleteComment),
  UIAction(title: &quot;수정하기&quot;, handler: editComment)
])</code></pre>
<p><img src="https://images.velog.io/images/hey_hen/post/541abb56-6f43-4aec-a7a8-bf2e0a85ad3f/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-16%20at%2023.28.52.gif" alt=""></p>
<p>만약 팝업 버튼 안에서 토글 방식이 필요하다면 <code>changeSelectionAsPrimaryAction</code>을 true로 두면 팝업 안에서 토글 형식으로 쓸 수 있다</p>
<pre><code class="language-swift">button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(children: [
  UIAction(title: &quot;매일반복&quot;, handler: repeatEveryday),
  UIAction(title: &quot;요일반복&quot;, handler: repeatDay)
])
button.changesSelectionAsPrimaryAction = true</code></pre>
<p><img src="https://images.velog.io/images/hey_hen/post/917781d2-627c-431d-a1f7-bd2160d9b18a/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-16%20at%2023.30.35.gif" alt=""></p>
<p>각 예제 프로젝트는 TIL에서 보실 수 있습니다.
<a href="https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/UIKit/UIButtonConfiguration.playground">https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/UIKit/UIButtonConfiguration.playground</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 Summer/Winter Coding(~2018) 점프와 순간이동, Swift]]></title>
            <link>https://velog.io/@hey_hen/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-SummerWinter-Coding2018-%EC%A0%90%ED%94%84%EC%99%80-%EC%88%9C%EA%B0%84%EC%9D%B4%EB%8F%99-Swift</link>
            <guid>https://velog.io/@hey_hen/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-SummerWinter-Coding2018-%EC%A0%90%ED%94%84%EC%99%80-%EC%88%9C%EA%B0%84%EC%9D%B4%EB%8F%99-Swift</guid>
            <pubDate>Fri, 14 Jan 2022 10:34:35 GMT</pubDate>
            <description><![CDATA[<p><a href="https://programmers.co.kr/learn/courses/30/lessons/12980">https://programmers.co.kr/learn/courses/30/lessons/12980</a></p>
<h1 id="풀이">풀이</h1>
<p>정해진 거리를 최소한의 비용으로 도착하는 문제</p>
<h2 id="1-시간초과">1. 시간초과</h2>
<p>총 거리가 1일 때는 무조건 한칸을 점프해야 하므로 비용은 1
총 거리가 2일 때는 한칸 이동 후 순간이동 하면 비용이 1
총 거리가 3일 때는 한칸 이동 후 순간이동 후 한칸이동 해서 비용이 2
총 거리가 4일 때는 거리가 2일 때에서 순강이동을 하면된다. 따라서 비용 1
총 거리가 5일 때는 거리가 4일 때에서 한칸만 이동하는게 최소한의 비용이 된다. 따라서 비용이 2</p>
<p>이런식으로 풀어가면, 타겟 거리를 짝수이면 반으로 나누고 홀수이면 1을 빼는 식으로 반복하면 해법이 나온다.</p>
<p>처음에는 생각없이 재귀를 통해서 풀었는데, 재귀 호출 회수 때문에 core dump가 나왔다.</p>
<pre><code class="language-swift">  var dp: [Int] = .init(repeating: -1, count: n + 1)
  dp[1] = 1

  func table(_ index: Int, _ offset: Int) -&gt; Int {
    guard dp[index] == -1 else { return dp[index] + offset }
    if index.isMultiple(of: 2) {
      return table(index / 2, offset)
    } else {
      return table((index - 1) / 2, offset + 1)
    }
  }</code></pre>
<h2 id="2-수정코드">2. 수정코드</h2>
<pre><code class="language-swift">while curr &gt; 0 {        
    if curr % 2 != 0 {
       curr -= 1
       battery += 1
    }
    else {
        curr /= 2
    }
}   </code></pre>
<h2 id="3-한줄해법">3. 한줄해법?</h2>
<p>그런데 문제를 보면
거리 N과 결과 result를
N : result 로 나타내서 쭈욱 보면 </p>
<p>1 : 1
2 : 1
3 : 2
4 : 1
5 : 2
6 : 2
7 : 3
8 : 1
9 : 2</p>
<p>이런 패턴인데 거리 N을 이진 수로 보면
0001 : 1
0010 : 2
0011 : 3
0100 : 1
0101 : 2
0110 : 2
0111 : 3
1000 : 1
1001 : 2</p>
<p>로 N을 2진수로 봤을 때 1의 개수와 동일하다 따라서 본 문제는 Swift에서 한줄로 해결이 가능하다.</p>
<pre><code class="language-swift">n.nonzeroBitCount</code></pre>
<p><code>nonzeroBitCount</code>는 정수의 0이 아닌 비트 수를 반환하는 프로퍼티이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift, Concurrency]]></title>
            <link>https://velog.io/@hey_hen/Swift-Concurrency</link>
            <guid>https://velog.io/@hey_hen/Swift-Concurrency</guid>
            <pubDate>Wed, 12 Jan 2022 17:57:08 GMT</pubDate>
            <description><![CDATA[<h1 id="concurrency동시성">Concurrency(동시성)</h1>
<p>동시성 프로그래밍이라고도 한다. <code>Concurrency</code>는 여러 작업을 나누어서 처리하는 것으로, 우리가 사용하는 아이폰이 노래도 재생하면서, 유저의 입력에 따라 이메일도 작성할 수 있고, 중간에 전화도 받을 수 있는 것이 이 동시성이다.
만약 노래를 재생하는 동안은 화면을 터치할 수 없고, 인터넷 검색도 불가능하다면, 굉장히 불편할 것이다.</p>
<p>코드로 실제로 어떻게 되는지 보자</p>
<pre><code class="language-swift">//1
func calculatePrimes() {
    for number in 0...1_000_000 {
      let isPrimeNumber = isPrime(number: number)
      print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
    }
  }

//2
func isPrime(number: Int) -&gt; Bool {
    if number &lt;= 1 {
      return false
    }
    if number &lt;= 3 {
      return true
    }

    var i = 2
    while i * i &lt;= number {
      if number % i == 0 {
        return false
      }
      i = i + 2
    }
    return true
 }</code></pre>
<p><code>calculatePrimes()</code>는 1에서 1백만 까지의 자연수 중에 <em>소수</em> 를 구하는 메서드로, 해본 사람은 알겠지만 꽤 오랜 시간이 걸리는 작업이다.</p>
<pre><code class="language-swift">var body: some View {
    VStack {
      Spacer()
      DatePicker(selection: .constant(Date())) {
        Text(&quot;Date&quot;)
      }
      .datePickerStyle(.wheel)
      .labelsHidden()
      Button {
        calculatePrimes()
      } label: {
        Text(&quot;Calculate Primes&quot;)
      }
      Spacer()
 }</code></pre>
<p>화면에는 DatePicker UI가 있고, 버튼을 누르면 소수를 구하기 시작한다.
<img src="https://images.velog.io/images/hey_hen/post/281eb1a2-8fd5-4b09-a61d-783b40908059/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-13%20at%2001.48.44.gif" alt="">
소수 구하기 버튼을 누르면 해당 계산을 하느라 DatePicker의 UI가 동작하지 않는다.
그 이유는 DatePicker의 UserInteraction과 <code>calculatePrimes()</code>를 둘다 메인 쓰레드에서 수행하고 있기 때문이다. <code>calculatePrimes()</code>가 쓰레드를 점유하고 있어서 다른 메인 쓰레드에서 작업해야 하는 User Interaction이 동작하지 않는 것이다.</p>
<h2 id="threadmultithread">Thread/Multithread</h2>
<p>멀티쓰레드 라는 얘기 많이 들어보았는데, 그럼 쓰레드는 뭘까, </p>
<blockquote>
<p><a href="https://www.semanticscholar.org/paper/How-to-Make-a-Multiprocessor-Computer-That-Executes-Lamport/bdacc240ee817e6565f27f40f8658c037a457a13">컴퓨팅에서 쓰레드는 OS 스케줄러에 의해 독립적으로 관리될 수 있는 프로그래밍된 명령의 가장 작은 시퀀스다.</a></p>
</blockquote>
<p>요즘은 CPU는 다수의 코어와 그 이상의 쓰레드를 가지고 있어, 물리적으로 작업을 동시에 처리할 수 있다. 이게 멀티 쓰레딩이다.
이 부분이 <code>Parallelism</code>과 차이점이다. 병렬프로그래밍이라고 하는 <code>Parallelism</code>는 작업의 단위가 스레드가 아니라, CPU가 된다. 앞서 <code>caculatePrimes()</code>메서드를 하나의 스레드에서 점유해서 계산하지 않고, 분할해서 계산해서 작업의 속도를 높이는 것이 <code>Parallelism</code>이고
<code>Concurrency</code>의 핵심은 스레드이다.</p>
<h3 id="context-swiching">Context Swiching</h3>
<p><code>Concurrency</code>의 또 하나의 핵심은 Context Switching으로, 하나의 코어는 Time Slicing이라는 방법을 통해 Concurrency하게 움직인다. 쉽게 이해해보면, 내가(아이폰) 커피를 만드는데 이 커피를 만드는 동작안에는 원두를 분쇄하고, 물을 끓이고, 컵을 준비하는 일련의 동작이고, 이를 아주 빠르게 한다, 다른 사람(사용자) 입장에서 보면 거의 동시에 일어나는 것 처럼 보인다.</p>
<h1 id="use-concurrency">Use Concurrency</h1>
<p>그래서 다시 맨 처음 어플리케이션으로 돌아와서, iOS는 기본적으로 멀티쓰레드이다. 소수를 구하는 것과 동시에 UI작업을 처리할 수 있는 것이 당연한 것이다. 그런데 실제로는 계산을 하느라 UI작업을 하지 못했다. 우리는 소수를 구하는 작업을 다른 쓰레드에서 하도록 명시할 필요가 있다.</p>
<p>애플은 고맙게도? 쓰레드를 편리하게 사용할 수 있는 프레임워크를 제공한다. 굉장히 Low하게 보면 NSThread와 더 내려가서 Unix POSIX 쓰레드를 통해서 이 쓰레드를 사용할 수 있지만 우리에게는 <strong>Grand Central Dispatch</strong>(GCD)가 있다. GCD 또한 Concurrency 작업을 위한 프레임워크로 꽤 LowLevel 측면으로 디자인 되어있다.</p>
<p>또다른 옵션은 Operation Queue로 GCD위에 만들어져 있고, 더 쉽고 간결한 코드를 제공한다.</p>
<p>그 다음은 2019년에 나온 Combine으로 이는 백그라운드에서 작업을 선언형으로 관리한다. 오퍼레이터를 통해 스레드 간의 쉬운 전환이 가능하다.</p>
<p>또! 그다음은 Swift Concurrency가 있다.</p>
<h2 id="gcd">GCD</h2>
<p>GCD는 스레드 위에 구축되며, 공유 스레드 풀을 관리한다. 이를 사용해서 <strong>DispatchQueue</strong>에 코드 블록을 처리하고 GCD는 이를 실행할 쓰레드를 결정하게 된다.</p>
<h3 id="queue">Queue</h3>
<p>Queue는 FIFO구조로 먼저 들어온 작업을 먼저 내보낸다는 특징이 있다. GCD는 같은 방식으로 작업 순서를 보장한다. 이때 Queue는 Seiral이거나 Concurrent일 수 있다.
<img src="https://images.velog.io/images/hey_hen/post/9ca1c73a-4844-4bcb-9039-3303d03dcf61/image.png" alt="">
이처럼 Serial은 선형 시간동안 하나의 작업만 실행된다. Task1이 끝나면 Task2가 실행되는 식이다. 쓰레드를 사용해서 이를 Concurrent하게 할 수 있다.
<img src="https://images.velog.io/images/hey_hen/post/99454a07-6acb-40a0-a905-b6a2491a6361/image.png" alt="">
Concurrent는 Task순서대로 일단 작업을 할당하긴 했지만 Serial과는 다르게 Task2보다 Task3과 Task4가 훨씬 더 빨리 끝나서 작업물을 반환한다. 그래서 작업 순서를 보장할 수 없다.
커피를 다 볶은 다음에 컵에 넣고 물을 부어야 하는데, 물이 먼저 끓었다고 물부터 냅다 부어버린 것</p>
<h3 id="operation-queue">Operation Queue</h3>
<p>GCD를 사용하기 위해서는 Operation을 상속해서 만들 수 있다.</p>
<pre><code class="language-swift">class CaculatePrimeOperation: Operation {


  override func main() {

    for number in 0...1_000_000 {
      let isPrimeNumber = isPrime(number: number)
      print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
    }
  }

  func isPrime(number: Int) -&gt; Bool {
    if number &lt;= 1 {
      return false
    }
    if number &lt;= 3 {
      return true
    }

    var i = 2
    while i * i &lt;= number {
      if number % i == 0 {
        return false
      }
      i = i + 2
    }
    return true
  }

}</code></pre>
<p><code>main()</code>에서 앞에서 작성한 소수 구하기 메서드를 이식한다.</p>
<pre><code class="language-swift">let operation = CaculatePrimeOperation()
func calculatePrimes() {

  let queue = OperationQueue()
  queue.addOperation(operation)

}</code></pre>
<p>그런 다음 해당 클래스의 인스턴스를 만든 뒤, OperationQueue의 인스턴스에<code>addOperation</code>으로 넘겨준다.</p>
<pre><code class="language-swift">  func calculatePrimes() {
    let operation = CalculatePrimeOperation()
    let queue = OperationQueue()
    queue.addOperation(operation)
  }</code></pre>
<p>사실 꼭 Operation을 상속하는 클래스를 만들 필요는 없고 addOperation이 클로져를 제공하기 때문에 메서드를 해당 클로져 안에 작성해주면 된다.</p>
<p><code>OperationQueue</code>는 인스턴스를 만들 때 자동으로 남는 쓰레드를 할당해 준다. 만약 메인 쓰레드를 사용하라고 명시적으로 알리고 싶다면</p>
<pre><code>let mainQueue = OpearationQueue.main</code></pre><p>으로 쓸 수 있다.</p>
<p><code>OperationQueue</code>말고도 더 쉽고 간편한 방법은 <code>DispatchQueue</code>가 있다</p>
<h3 id="dispatchqueue">DispatchQueue</h3>
<pre><code class="language-swift">DispatchQueue.global(qos: .userInitiated).async {
  for number in 0...1_000_000 {
    let isPrimeNumber = number.isPrime
    print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
  }
}</code></pre>
<p>쓰레드를 global로 보낼 수 있다. qos설정은 애플 문서에서 자세히 확인할 수 있다.
<a href="https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass">https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass</a></p>
<p>그래서 이제 앞에서 소수를 구하느라 반응하지 않던 UI를 구조할 수 있게 되었다.
<img src="https://images.velog.io/images/hey_hen/post/cf7eeaa4-d89c-4fa1-9278-939e79b4dcfb/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-13%20at%2002.41.53.gif" alt=""></p>
<h3 id="swift-concurrency">Swift Concurrency</h3>
<p>이 작업을 Swift Concurrency를 이용하면 코드 한줄? 추가하는 것으로 가능하다.</p>
<pre><code class="language-swift">  func calculatePrimes() {
    doneLabel = &quot;Calculating!&quot;
    Task {
      for number in 0...1_000_000 {
        let isPrimeNumber = number.isPrime
        print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
      }
    }
    doneLabel = &quot;Done!&quot;
  }</code></pre>
<blockquote>
<p>Refereces </p>
</blockquote>
<ul>
<li><a href="https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2">raywenderlich Grand Cental Dispatch Tutorial for Swift4 by Evan Dekhayser</a></li>
<li><a href="https://www.amazon.com/dp/1950325520?tag=raywend-20">Swift Apprentice</a></li>
<li>Monocoding</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>