<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sookim-1.log</title>
        <link>https://velog.io/</link>
        <description>iOS 공부 중 🧑🏻‍💻</description>
        <lastBuildDate>Wed, 02 Feb 2022 14:36:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sookim-1.log</title>
            <url>https://images.velog.io/images/sookim-1/profile/cef4e3bf-9ff6-452c-83a1-c223f57acf3b/ani.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sookim-1.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sookim-1" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[iOS] - ExpandableTableView, SnapKit]]></title>
            <link>https://velog.io/@sookim-1/iOS-ExpandableTableView-SnapKit</link>
            <guid>https://velog.io/@sookim-1/iOS-ExpandableTableView-SnapKit</guid>
            <pubDate>Wed, 02 Feb 2022 14:36:10 GMT</pubDate>
            <description><![CDATA[<h1 id="expandableview">ExpandableView</h1>
<h2 id="스택뷰를-활용한-방식">스택뷰를 활용한 방식</h2>
<ol>
<li>UITableViewCell 내부에 스택뷰를 배치합니다.</li>
<li>스택뷰 내부의 UIView를  Hidden시키는 방식</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/ff78cf10-245b-4489-bd05-58f04d86e746/%E3%84%B1.png" alt=""></p>
<pre><code class="language-swift">    @IBOutlet weak var bottomView: UIView! {
        didSet {
            bottomView.isHidden = true
        }
    }</code></pre>
<ol start="3">
<li><p>셀을 클릭했을 때 처리</p>
<pre><code class="language-swift"> func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
     tableView.deselectRow(at: indexPath, animated: true)
     guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableTableViewCell
     else { return }

     tableView.performBatchUpdates {
         UIView.animate(withDuration: 0.3) {
               cell.bottomView.isHidden = !cell.bottomView.isHidden
                         if !cell.bottomView.isHidden {
                 cell.clickImageView.image = UIImage(systemName: &quot;arrow.up&quot;)
             } else {
                 cell.clickImageView.image = UIImage(systemName: &quot;arrow.down&quot;)
             }
         }
     }
 }</code></pre>
</li>
</ol>
<h1 id="snapkit">SnapKit</h1>
<p>오토레이아웃을 쉽게 만들 수 있도록 도와주는 라이브러리</p>
<p><img src="https://images.velog.io/images/sookim-1/post/861b9112-e9fa-4883-a91c-a9c5c816601e/%E3%84%B4.png" alt=""></p>
<p>💡 translatesAutoresizingMaskIntoConstraints = false 생략가능하지만 확인이 필요합니다.</p>
<h3 id="paddingoffset">Padding(Offset)</h3>
<pre><code class="language-swift">view.snp.makeConstraints { make in
    make.top.equalToSuperView().offset(20)
    make.left.equalToSuperView().offset(20)
    make.bottom.equalToSuperView().offset(-20)
    make.right.equalToSuperView().offset(-20)
}</code></pre>
<h3 id="safearea">SafeArea</h3>
<ul>
<li>iOS11이후 topLayoutGuide, bottomLayoutGuide를 지원하지 않습니다.</li>
</ul>
<pre><code class="language-swift">view.snp.makeConstraints { make in
    make.left.equalTo(view.safeAreaLayoutGuide)
    make.right.equalTo(view.safeAreaLayoutGuide)
    make.bottom.equalTo(view.safeAreaLayoutGuide)
    make.top.equalTo(view.safeAreaLayoutGuide)
}</code></pre>
<h3 id="center">Center</h3>
<pre><code class="language-swift">view.snp.makeConstraints { make in
        make.center.equalToSuperview()
        make.centerX.equalToSuperview()
        make.centerY.equalToSuperview()
}</code></pre>
<h3 id="aspectratio">AspectRatio</h3>
<pre><code class="language-swift">view.snp.makeConstraints { make in
    make.width.equalTo(view.snp.height).multipliedBy(1.0 / 1.0)
        make.width.height.equalTo(50) // 같은 방식
}</code></pre>
<h3 id="edge--inset">Edge + Inset</h3>
<pre><code class="language-swift">view.snp.makeConstraints { make in
    make.edges.equalToSuperview()
        .inset(UIEdgeInsets(top: 10,left: 10, bottom: 10, right: 10))
}</code></pre>
<h3 id="size">Size</h3>
<pre><code class="language-swift">view.snp.makeConstraints { make in
    make.size.equalTo(CGSize(width: 300, height: 300))
}</code></pre>
<h3 id="디버그">디버그</h3>
<ul>
<li><code>.labeled</code> : 디버그 로그에 대한 제약 조건 레이블을 지정할 수 있습니다.</li>
</ul>
<pre><code class="language-swift">button.snp.makeConstraints { (make) -&gt; Void in
  make.top.equalTo(otherView).labeled(&quot;buttonViewTopConstraint&quot;)
}

// 디버그 출력값
&quot;&lt;SnapKit.LayoutConstraint:buttonViewTopConstraint@SignUpViewController.swift#311
UIView:0x7fd98491e4c0.leading == UIView:0x7fd983633880.leading&gt;&quot;</code></pre>
<h1 id="트러블슈팅">트러블슈팅</h1>
<ol>
<li><p>스토리보드로 UITableView예제 만들다가 난 에러 (Unexpectedly found nil while implicitly unwrapping an Optional value)</p>
<ul>
<li><p>nil인 요소에 값을 할당할 때 생기는 에러</p>
</li>
<li><p>UITableViewCell을 Nib파일로 만들었는데 등록하는 과정을 생략</p>
<pre><code class="language-swift">  private func registerXib() {
      let nibName = UINib(nibName: &quot;ExpandableTableViewCell&quot;, bundle: nil)
      tableView.register(nibName, forCellReuseIdentifier: &quot;ExpandableTableViewCell&quot;)
  }</code></pre>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p>스택뷰 코드로 작성할 때 UIView 배열형식으로 추가해야함 </p>
<pre><code class="language-swift"> lazy var firstBtnStackView: UIStackView = {
     let btnStackView = UIStackView(arrangedSubviews: [mannerBtn, timeCheckBtn])
     btnStackView.axis = .horizontal
     btnStackView.alignment = .fill
     btnStackView.distribution = .fillEqually

     return btnStackView
 }()</code></pre>
<ul>
<li>arrangedSubviews 파라미터에 배열[UIView] 형식으로 추가해야함</li>
</ul>
</li>
</ol>
<h1 id="참고링크">참고링크</h1>
<ul>
<li><p>스택뷰 숨기는 방식</p>
<ul>
<li><a href="https://zhiyao92.medium.com/ios-tutorials-expandable-table-cell-85d1c64f89fc">https://zhiyao92.medium.com/ios-tutorials-expandable-table-cell-85d1c64f89fc</a></li>
</ul>
</li>
<li><p>tableview begin, endUpdate → performBatchUpdates</p>
<ul>
<li><a href="https://ios-development.tistory.com/704">https://ios-development.tistory.com/704</a></li>
<li><a href="https://jcsoohwancho.github.io/2019-10-12-TableView%EC%9D%98-%EB%B3%80%ED%99%94%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95(1)-Batch-Update/">https://jcsoohwancho.github.io/2019-10-12-TableView의-변화를-처리하는-방법(1)-Batch-Update/</a></li>
</ul>
</li>
<li><p>SnapKit</p>
<ul>
<li><a href="https://github.com/SnapKit/SnapKit">https://github.com/SnapKit/SnapKit</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[git] - 잘못올린파일 원격저장소 삭제하기]]></title>
            <link>https://velog.io/@sookim-1/git-%EC%9E%98%EB%AA%BB%EC%98%AC%EB%A6%B0%ED%8C%8C%EC%9D%BC-%EC%9B%90%EA%B2%A9%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sookim-1/git-%EC%9E%98%EB%AA%BB%EC%98%AC%EB%A6%B0%ED%8C%8C%EC%9D%BC-%EC%9B%90%EA%B2%A9%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 31 Jan 2022 18:03:59 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>.gitignore파일에서 제거할 항목을 추가한 후 원격저장소에 커밋/푸시 </p>
</li>
<li><p>.git있는 위치에서 명령어입력</p>
<pre><code class="language-swift"> git filter-branch --force --index-filter &#39;git rm --cached --ignore-unmatch 경로/경로/파일명.확장자&#39; --prune-empty --tag-name-filter cat -- --all</code></pre>
</li>
<li><p>원격저장소에 변경사항 푸시</p>
<pre><code class="language-swift"> git push origin --force --all</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] - Encryption,  PushNotification설정]]></title>
            <link>https://velog.io/@sookim-1/iOS-Encryption-PushNotification%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@sookim-1/iOS-Encryption-PushNotification%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 31 Jan 2022 12:34:13 GMT</pubDate>
            <description><![CDATA[<h1 id="encryption">Encryption</h1>
<h2 id="메시지-암호화-전송---rncryptor-라이브러리">메시지 암호화 전송 - RNCryptor 라이브러리</h2>
<ul>
<li>메시지를 암호화한 후 받을 때 해독하는 방식<ul>
<li>🌟  서버에 저장될 때 원본 그대로 저장되는 것이 아닌 암호화된 문자로 저장된다.</li>
</ul>
</li>
<li><code>pod RNCryptor</code> 라이브러리 사용</li>
</ul>
<h3 id="인코딩과정암호화">인코딩과정(암호화)</h3>
<pre><code class="language-swift">import RNCryptor

class Encryption {

    // 암호화할 메서드
    class func encryptText(keyValue: String, message: String) -&gt; String {

        let data = message.data(using: String.Encoding.utf8)
        let encryptedData = RNCryptor.encrypt(data: data!, withPassword: keyValue)

        return encryptedData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
    }

}</code></pre>
<ol>
<li>String → Data타입으로 변경</li>
<li>RNCryptor의 encrypt메서드를 사용하여 암호화된 데이터를 반환합니다.</li>
<li>암호화된 데이터를 String으로 변환 후 반환합니다.</li>
</ol>
<h3 id="디코딩과정복호화">디코딩과정(복호화)</h3>
<pre><code class="language-swift">import RNCryptor

class Encryption {
    class func decryptText(keyValue: String, encryptedMessage: String) -&gt; String {

        let decryptor = RNCryptor.Decryptor(password: keyValue)

        let encryptedData = NSData(base64Encoded: encryptedMessage, options: NSData.Base64DecodingOptions(rawValue: 0))

        var message: NSString = &quot;&quot;

        if encryptedData != nil {
            do {
                let decryptedData = try decryptor.decrypt(data: encryptedData! as Data)
                message = NSString(data: decryptedData, encoding: String.Encoding.utf8.rawValue)!
            } catch {
                print(&quot;error decrypting text \(error.localizedDescription)&quot;)
            }
        }

        return message as! String
    }

}</code></pre>
<ol>
<li>암호화된 메시지를 저장한 password로 복호화합니다.</li>
<li>암호화된 String을  Data타입으로 변환 후 decrypt메서드로 복호화하여 사용합니다.</li>
</ol>
<h1 id="push-notification-푸쉬알림보내기">Push Notification 푸쉬알림보내기</h1>
<ul>
<li>알림을 받기위해서는 인증서를 받아야 합니다.</li>
</ul>
<h2 id="인증서-생성과정">인증서 생성과정</h2>
<ul>
<li>순서<ul>
<li>App ID의 PushNotification서비스 체크</li>
<li>APNs 인증서 발급 (Apple Push Notification service)</li>
<li>APNs 키 발급</li>
<li>xcode 프로젝트에서 PushNotification, BackgroundModes활성화</li>
</ul>
</li>
</ul>
<ol>
<li>애플 개발자 계정사이트로 이동합니다<ul>
<li><a href="https://developer.apple.com/account/#!/overview/2MRY8P7863">https://developer.apple.com/account/#!/overview/2MRY8P7863</a></li>
</ul>
</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/d208caca-2fb4-4ff7-b620-8f9acf26f3bd/11111.png" alt=""></p>
<ul>
<li>Certificates탭으로 이동합니다.</li>
</ul>
<ol start="2">
<li><p>이동한 후 Identifiers 탭에서 <code>+</code> 버튼을 클릭하여 새로운 App Id를 생성합니다.
<img src="https://images.velog.io/images/sookim-1/post/8c0af89e-f91d-4581-a167-8597c3a73bd9/222222.png" alt=""></p>
</li>
<li><p>프로젝트의 Bundle ID를 작성합니다.</p>
</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/fc302196-671f-4ebc-a626-11e883c8b1a6/333333.png" alt=""></p>
<ol start="4">
<li>인증서에 필요한 서비스를 선택합니다.</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/1285976e-1021-44fb-9674-bb54d7f4dfba/444444.png" alt=""></p>
<ol start="5">
<li>Configure 버튼 클릭후 Create Certificate버튼클릭</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/f69bc546-91bb-4940-bbb9-1d0c967b1edd/55555.png" alt=""></p>
<ol start="6">
<li>로컬에서 키체인접근을 열어줍니다. - 상단 메뉴에서 인증 기관에서 인증서 요청 클릭</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/18efd3f5-197a-453d-8fe4-be0e0405bcf7/666666.png" alt="">
<img src="https://images.velog.io/images/sookim-1/post/f5ca368f-d502-40ff-abc6-ae811be14787/777777.png" alt="">
<img src="https://images.velog.io/images/sookim-1/post/395d4a75-e90a-4605-b011-90bc47d60145/88888.png" alt="">
<img src="https://images.velog.io/images/sookim-1/post/abfc4f30-b7db-4580-9805-f923a7db20eb/999999.png" alt="">
7. 같은 방식으로 ProductionCertificate도 지정합니다. .p12파일 저장</p>
<p>💡 ProductionCertificate를 지정할 때 <code>.certSigningRequest</code>  파일은 다른 파일로 하는 것을 권장합니다.(충돌 예방)</p>
<h2 id="onesignal">OneSignal</h2>
<blockquote>
<p><strong><a href="https://onesignal.com/">OneSignal</a>이란?</strong></p>
</blockquote>
<ul>
<li>OneSignal은 웹 사이트 및 모바일 응용 프로그램을위한 <strong>무료</strong> 푸시 알림 서비스입니다.</li>
<li>각 플랫폼에 전용 SDK를 제공하여 모든 주요 네이티브 및 모바일 플랫폼을 지원합니다.</li>
<li>RESTful 서버 API 및 마케터가 푸시 알림을 디자인하고 보내는 온라인 대시 보드를 제공합니다.</li>
</ul>
<h3 id="초기설정---프로젝트-생성">초기설정 - 프로젝트 생성</h3>
<ol>
<li>새로운 앱 생성 후 플랫폼 선택합니다.</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/816c132b-4e22-464c-ac8f-782d5b03a6f5/10.png" alt=""></p>
<ol start="2">
<li>Product .p12파일을 업로드 한 후 비밀번호를 입력합니다.</li>
<li>APNs Configuration 정의</li>
</ol>
<p>   <img src="https://images.velog.io/images/sookim-1/post/b234b0ca-cf77-421a-a68e-bdce1e9725cf/11.png" alt="">
3. SDK를 설치 한 후, App ID를 저장합니다.</p>
<p><img src="https://images.velog.io/images/sookim-1/post/a79c27bc-c5f8-4ef9-8a35-140d1593d72b/12.png" alt=""></p>
<h3 id="프로젝트-capability에서-추가">프로젝트 Capability에서 추가</h3>
<p><img src="https://images.velog.io/images/sookim-1/post/35f12084-52b0-4091-aca1-19a14ae3414c/13.png" alt=""></p>
<h3 id="사용방법">사용방법</h3>
<ol>
<li><code>pod &#39;OneSignal&#39;</code> 코코아팟 추가</li>
<li><code>AppDelegate.swift</code> 에서 초기화<ul>
<li>🚨  시뮬레이터에서는 에러가 발생합니다.</li>
</ul>
</li>
</ol>
<h1 id="참고링크">참고링크</h1>
<ul>
<li>Info.plist - CFBundleIconName 설정방법<ul>
<li><a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=websearch&amp;logNo=221110556257">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=websearch&amp;logNo=221110556257</a></li>
</ul>
</li>
<li>RNCryptor<ul>
<li><a href="https://github.com/RNCryptor/RNCryptor">https://github.com/RNCryptor/RNCryptor</a></li>
</ul>
</li>
<li>PushNotifications<ul>
<li>onesignal - 무료푸시알림서비스<ul>
<li>OneSignal을 사용하여 무료 push서비스 구현하기 - <a href="https://ithub.tistory.com/135">https://ithub.tistory.com/135</a></li>
<li>iOS-OneSignal으로-푸쉬알림-보내보기 - <a href="https://fomaios.tistory.com/entry/iOS-OneSignal%EC%9C%BC%EB%A1%9C-%ED%91%B8%EC%89%AC%EC%95%8C%EB%A6%BC-%EB%B3%B4%EB%82%B4%EB%B3%B4%EA%B8%B0OneSignal-Push-Notification">https://fomaios.tistory.com/entry/iOS-OneSignal으로-푸쉬알림-보내보기OneSignal-Push-Notification</a></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] - 회원가입화면 설계 및 레이아웃의 이해]]></title>
            <link>https://velog.io/@sookim-1/iOS-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%ED%99%94%EB%A9%B4-%EC%84%A4%EA%B3%84-%EB%B0%8F-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83%EC%9D%98-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@sookim-1/iOS-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%ED%99%94%EB%A9%B4-%EC%84%A4%EA%B3%84-%EB%B0%8F-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83%EC%9D%98-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Thu, 20 Jan 2022 19:34:22 GMT</pubDate>
            <description><![CDATA[<h1 id="회원가입-화면-설계">회원가입 화면 설계</h1>
<h2 id="rxswift를-사용한다면">RxSwift를 사용한다면??</h2>
<blockquote>
<p>RxSwift는 동기적인 동작을 비동기적으로 변경할 때 사용하는 라이브러리인데 회원가입화면에서 어느 곳에 필요할 지, 그리고 사용한다면 어느 장점이 있을까? 🤔</p>
</blockquote>
<ol>
<li>비동기적으로 실행할 필요가 없는 곳에서 사용은 당장은 필요없지만 Rx로 구현한다면 확장성측면에서 화면에서 비동기적으로 실행하도록 변경된다면 쉽게 변경가능할 수 있는 장점이 있을 것 같다.</li>
<li>비동기처리에서 @escaping클로저를 사용하면 반환값이 없기 때문에 가독성 측면에서 좋지 않기 때문에 반환값을 받는 RxSwift를 사용하면 가독성측면이나 개발하는데 편리한 장점이 있을 것 같다.</li>
<li>아키텍처패턴을 MVVM으로 구현한다면 패턴 구조상 반환값을 받는 Rx로 구현하면 패턴구현이 좀 더 수월한 장점이 있을 것 같다.</li>
</ol>
<h2 id="회원가입-화면에서는-어떤-곳에서-사용하는-것이-좋을까">회원가입 화면에서는 어떤 곳에서 사용하는 것이 좋을까?</h2>
<ul>
<li>인증받기 버튼을 클릭하면 파이어베이스에서 인증을 받는 동안 모든 화면이 멈춰서 인증번호 유효시간타이머가 멈추게 되서 비동기처리를 해야할 것 같다.</li>
<li>텍스트필드의 유효성을 검사할 때 서버와 통신하여 검사를 하거나, 실시간으로 잘못되었다고 표시를 해야하는 경우 사용자에게 알리도록 처리할 수 있을 것 같다. (비동기 처리를 하지않으면 유효성검사를 하는동안 화면이 멈추기때문에)</li>
</ul>
<blockquote>
<p>🤔  idToken → 닉네임 → 생년월일 → 이메일 →  성별 순으로 화면이동하면서 값을 계속 전달할 때 어떤방법으로 하는게 좋을지??</p>
</blockquote>
<ol>
<li>구조체를 만든 후 각각의 프로퍼티를 옵셔널타입으로 지정하고 구조체를 전달해가면서 값전달하기<ul>
<li>계속해서 화면마다 구조체프로퍼티를 만들어야 할 것 같다.</li>
</ul>
</li>
<li>UserDefault를 사용하여 저장한 후 사용하기<ul>
<li>보안측면에서 중요한 내용을 저장한다면 단점이 있을 것 같다 → 회원가입한 후 바로 삭제</li>
</ul>
</li>
</ol>
<h1 id="ios-레이아웃-이해">iOS 레이아웃 이해</h1>
<p>뷰의 레이아웃이나 컨텐츠를 다룰 때 실질적으로 뷰 업데이트가 일어날때를 잘못 알고 있을 때 에러가 발생할 수 있습니다. 뷰 업데이트에 대해 알기 위해서는 <code>Main Run Loop</code> 와 <code>UIView</code> 를 자세히 알아야합니다.</p>
<h2 id="main-run-loop">Main Run Loop</h2>
<p>어플리케이션이 실행되면 iOS의 <code>UIApplication</code>이 매인 스레드에서 main run loop를 실행시킵니다.</p>
<p><code>Main Run Loop</code> 는 모든 사용자 입력 이벤트를 처리하고 적절한 응답을 발생시키는 것 입니다. 사용자와 상호작용은 이벤트큐에 추가합니다. Application객체는 이벤트큐에서 이벤트를 가져와 Application의 다른 객체에 전달합니다. 이런 입력이벤트를 해석하고 Application의 핵심 객체에서 해당 입력이벤트에 대한 해당하는 handler를 호출하여 런루프를 실행합니다. 이러한 핸들러는 개발자가 작성한 코드를 호출이되고 이 메서드가 반환되면 <code>Main Run Loop</code> 와 업데이트 주기가 시작됩니다. 업데이트주기는 레이아웃과 뷰다시그리기를 담당하고 있습니다. </p>
<p><img src="https://images.velog.io/images/sookim-1/post/0fafb2a4-14a0-497f-ac46-27c79619b4ca/%E3%85%81%E3%85%81.png" alt=""></p>
<ul>
<li>사용자 터치 → Operating system → 이벤트큐추가 → 이벤트를 가져와 Application객체가 이벤트를 전달 Core객체에서 해당하는 handler를 처리한 후 반환 → MainRunLoop와 업데이트 주기 발생(레이아웃 및 뷰다시그리기) → 화면에 표시</li>
</ul>
<h2 id="update-cycle">Update Cycle</h2>
<ul>
<li>updateCycle은 앱이 모든 이벤트처리코드를 실행을  마친 후에 컨트롤이 mainRunLoop로 반환되는 시점입니다. 이 시점에서 시스템이 <code>layout</code> , <code>display</code> , <code>constraints</code> 의 업데이트를 시작합니다.</li>
<li>이벤트처리하는 과정에서 뷰를 변경시키기를 요청하면 업데이트주기에서 변경사항을 실행합니다. 사용자의상호작용과 레이아웃 업데이트 사이의 지연은 사용자가 알 수 없어야 합니다.</li>
<li>ios앱은 일반적으로 60fps을 애니메이션이되면 refreshCycle이 1초에 60번 그린다는 의미입니다. 따라서 사용-자는 상호작용하는 것과 컨텐츠 및 레이아웃업데이트를 보는 것의 지연을 알 지못합니다.</li>
<li>하지만 이벤트가 처리되는 시점과 해당 뷰를 다시그리는 시점사이에 간격이 있기 때문에 특정시점에서 뷰를 업데이트를 하기원해도 뷰가 업데이트되지 않을 수 도 있습니다.(어느 뷰가 가장 최근의 컨텐츠나레이아웃에 의존하여 계산하는 경우) 이러한 문제를 해결하기위해 run loop와 업데이트주기, UIview메서드를 알아야합니다.</li>
</ul>
<p><img src="https://images.velog.io/images/sookim-1/post/f8aba941-88bb-4774-908c-e8c9d6b53633/a.png" alt=""></p>
<p><img src="https://images.velog.io/images/sookim-1/post/368b99b0-54c0-4c1f-a79b-d12730bd26ec/b.png" alt=""></p>
<p><img src="https://images.velog.io/images/sookim-1/post/5256e4c7-3020-44ea-9b99-f5e83da916ef/c.png" alt=""></p>
<h1 id="layout">Layout</h1>
<p>뷰의 레이아웃은 화면의 size와 position 참조합니다. 모든 뷰는 슈퍼뷰의 좌표계에서 존재하는 size, position를 정의하는 프레임이 있습니다. UIView는 뷰의 레이아웃이 변경되었음을 시스템에 알릴 수 있는 방법을 제공할 뿐만 아니라 뷰의 레이아웃을 다시 계산한 후 수행할 작업을 정의하기 위해 overriding을 제공합니다.</p>
<h1 id="layoutsubviews">layoutSubviews()</h1>
<ul>
<li>뷰와 뷰의 모든subview들을 repositioning, resizing을 처리하는 메서드</li>
<li>현재 뷰와 모든하위뷰들의 location과 size를 제공합니다.</li>
<li>모든 하위뷰들을 호출하기때문에 이 메서드는 무거운 작업입니다.</li>
<li>뷰들의 frame을 recalculate할 때 시스템은 이 메서드를 호출하므로 프레임을 설정하고 위치지정 및 크기 조정을 지정하고 싶을 때 사용합니다.</li>
<li>view hierarchy(뷰의 계층구조)에서 layout refresh가 필요할 때 명시적으로 이메서드를 호출하면 안됩니다. 대신에 다른 메서드들을 사용합니다.</li>
<li>layoutSubviews가 완료되면 뷰를 소유한 뷰 컨트롤러에서 viewDidLayoutSubviews에 대한 호출이 발생합니다. 따라서 레이아웃이나 위치를 계산해서 정하는 경우 viewDidLoad, viewDidAppear에서가 아닌 viewDidLayoutSubviews에서 호출하는 것이 안정적입니다.</li>
</ul>
<h2 id="automatic-refresh-triggers">Automatic refresh triggers</h2>
<p>개발자가 직접 작성하지 않아도 뷰의 레이아웃을 변경한 것을 자동으로 표시하는 여러 이벤트가 있습니다. </p>
<ul>
<li>뷰의 레이아웃 변경<ol>
<li>Resizing a view</li>
<li>Adding a subview</li>
<li>User scrolling a UIScrollView (<code>layoutSubviews</code> is called on the <code>UIScrollView</code> and its superview) <code>layoutSubviews</code> 는 UIScrollView 및 해당 Superview에서 호출됩니다</li>
<li>User rotating their device (화면 회전)</li>
<li>Updating a view’s constraints</li>
</ol>
</li>
</ul>
<p>이러한 목록들은 모두 뷰의 위치를 다시 계산한 후 자동으로 마지막에 <code>layoutSubviews</code> 호출로 가야할 것을 시스템에 전달합니다. </p>
<h2 id="직접-layoutsubviews를-발생하기">직접 layoutSubviews를 발생하기</h2>
<h3 id="setneedslayout">setNeedsLayout()</h3>
<ul>
<li>시스템에 뷰의레이아웃을 다시 계산할 필요가 있다고 전달합니다. 호출되는 즉시 실행되며 반환됩니다. 반환되기 전에 뷰를 실제로 업데이트하지않지만, 뷰가 다음 업데이트 주기에 뷰가 업데이트됩니다.</li>
<li>반환되기전까지 간격이 있기 때문에 사용자에게 UI지연을 주의해야합니다.</li>
</ul>
<h3 id="layoutifneeded">layoutIfNeeded()</h3>
<ul>
<li>뷰의 레이아웃업데이트가 필요한 경우 다음 updateCycle에 실행하기 위해 큐에 넣는 것 대신에 즉시 뷰의 레이아웃업데이트를 합니다.</li>
<li>같은 runloop에서 이메서드를 두번 호출하면 두번째 메서드는 호출이 되지않습니다.</li>
<li>setNeedsLayout()와 가장 큰 차이점은 이 메서드가 반환되기전에 즉시 뷰업데이트가 발생하는 것이기 때문에 필요한 경우 비교해서 사용합니다. (이 메서드는 constraint를 변경하는 애니메이션을 할 때 유용합니다.)</li>
</ul>
<h1 id="display">Display</h1>
<p>뷰의 display는 color,text,image,coreGraphics drawing(코어그래픽도면)을 포함하는 sizing(크기)와 positioning(위치)가 지정되지 않은 뷰 및 하위뷰들의 프로퍼티를 포함하고 있습니다.</p>
<h2 id="draw_">draw(_:)</h2>
<ul>
<li>뷰의 컨텐츠를 그릴 때 사용합니다. (하위뷰들에서는 호출되지 않습니다.)</li>
<li>따라서 즉시 그리지 않을 경우가 아닌 다른 runloop에서는 호출하면 안됩니다.</li>
</ul>
<h2 id="setneedsdisplay">setNeedsDisplay()</h2>
<ul>
<li>뷰의 컨텐츠가 업데이트 되어야한다고는 하지만 실제로 그리기 전까지는 변경되지 않고 다음 updateCycle에서 변경됩니다.</li>
<li>다음 updateCycle에서 뷰의 일부컨텐츠만 변경할 때 이 메서드를 호출합니다.</li>
</ul>
<h1 id="constraints">Constraints</h1>
<p>오토레이아웃에서 뷰를 배치하고 다시그리는 단계</p>
<ol>
<li>constraints를 업데이트 (시스템은 뷰들의 constraints를 설정한후 계산합니다.)</li>
<li>레이아웃엔진이 뷰와 하위뷰들의 프레임을 계산합니다. 레이아웃 전달</li>
<li>display는 뷰를 다시그릴 필요가 있을 때 다시 그리게됩니다.</li>
</ol>
<h2 id="updateconstraints">updateConstraints()</h2>
<ul>
<li>이 메서드를 사용하면 오토레이아웃을 사용한 뷰에서 dynamically(동적으로) constraints를 변경가능 합니다. (layoutSubviews()와 동작방식이 유사합니다.)</li>
<li>변경될수 있는 constraints만 구현해야합니다. static(정적)인 constraints는 인터페이스빌더나, 뷰의 초기화 및 viewDidLoad()에서 구현해야합니다.</li>
</ul>
<h2 id="setneedsupdateconstraints">setNeedsUpdateConstraints()</h2>
<ul>
<li>다음 updateCycle에 constraints를 업데이트할 경우 사용합니다.</li>
</ul>
<h2 id="updateconstraintsifneeded">updateConstraintsIfNeeded()</h2>
<ul>
<li>constraint update플래그를 확인한 후 runloop가 끝나기전에 즉시 updateConstraints()를 호출하여 constraints를 변경합니다.</li>
</ul>
<h2 id="invalidateintrinsiccontentsize">invalidateIntrinsicContentSize()</h2>
<ul>
<li>오토레이아웃에는 intrinsicContentSize프로퍼티가 있는데 다시 변경할 필요가 있을 때 플래그를 설정할 수 있습니다.</li>
</ul>
<h1 id="참고링크">참고링크</h1>
<ul>
<li>iOS 레이아웃<ul>
<li>Demystifying iOS Layout - <a href="https://tech.gc.com/demystifying-ios-layout/">https://tech.gc.com/demystifying-ios-layout/</a></li>
<li>입력이벤트 처리이미지 - <a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/MainEventLoop.html">https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/MainEventLoop.html</a></li>
<li>레이아웃 요약이미지 - <a href="https://i.stack.imgur.com/i9YuN.png">https://i.stack.imgur.com/i9YuN.png</a></li>
<li>[ios] setNeedsLayout vs layoutIfNeeded - <a href="https://baked-corn.tistory.com/105">https://baked-corn.tistory.com/105</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] - 온보딩화면구현]]></title>
            <link>https://velog.io/@sookim-1/iOS-%EC%98%A8%EB%B3%B4%EB%94%A9%ED%99%94%EB%A9%B4%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@sookim-1/iOS-%EC%98%A8%EB%B3%B4%EB%94%A9%ED%99%94%EB%A9%B4%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 19 Jan 2022 22:20:29 GMT</pubDate>
            <description><![CDATA[<h1 id="온보딩-화면-구현---uicollectionview">온보딩 화면 구현 - UICollectionView</h1>
<p>온보딩화면을 구현하면 새로운 사용자에게 서비스를 이용하기위한 안내 및 설정들을 제공할 수 있습니다.                           </p>
<blockquote>
<p>주의할 점</p>
</blockquote>
<ol>
<li>서비스사용의 도움이 되는 내용들을 제공하는 것을 권장합니다.</li>
<li>온보딩화면이 끝난 후 앱 시작화면으로 이동할 수 있도록 처리합니다.</li>
<li>너무 많은 내용을 제시하기 보다는 필요한내용들만 직관적으로 표시합니다.</li>
</ol>
<h2 id="uipageviewcontroller-방식으로-보여주는-방법-vs-uicollectionview로-보여주는-방법">UIPageViewController 방식으로 보여주는 방법 vs UICollectionView로 보여주는 방법</h2>
<ul>
<li>온보딩화면이나 테스트화면을 매번 새로운 UI디자인을 보여주는 것이 아닌 비슷한 디자인형식으로 보여주는데 UIPageViewController를 사용하면 여러 ViewController를 사용하게 되므로 메모리측면에서 비효율적이라고 생각합니다.</li>
</ul>
<h2 id="uicollectionview-page나누는-원리">UICollectionView Page나누는 원리</h2>
<p><img src="https://images.velog.io/images/sookim-1/post/c1a5509c-1f59-4a7f-8732-10f52a1ec91a/123.png" alt=""></p>
<p><img src="https://images.velog.io/images/sookim-1/post/4b022b57-62d1-4680-923b-de83d0236e97/456.png" alt=""></p>
<ul>
<li>아이템 마다 컬렉션뷰의 높이와 너비를 지정</li>
<li>Scrollview.frame.width = collection view.frame.width 와 동일합니다.</li>
<li>contentOffset.x 을 사용하여 스크롤을 했을 때 x좌표를 이동시킵니다.</li>
<li><code>collectionView.isPagingEnabled</code> : 컬렉션뷰를 스크롤할 때 아이템단위로 스크롤하는 속성(페이징옵션)<ul>
<li>🚨  위 속성을 사용할 때 컬렉션뷰의 아이템크기와 아이템간의 거리를 정확히 설정해야합니다.</li>
</ul>
</li>
</ul>
<p>🚨  <code>UICollectionView must be initialized with a non-nil layout parameter</code> : UIFlowLayout초기화 안해줬을 때 디버그에러</p>
<h3 id="온보딩화면-종료-후-첫-화면-이동-window의-루트뷰를-변경하기">온보딩화면 종료 후 첫 화면 이동 (window의 루트뷰를 변경하기)</h3>
<pre><code class="language-swift">guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
windowScene.windows.first?.rootViewController = ViewController()
windowScene.windows.first?.makeKeyAndVisible()</code></pre>
<ul>
<li>온보딩화면을 모두 본 후에는 다시 돌아올 수 없도록 루트뷰를 변경합니다.</li>
</ul>
<h2 id="앱-최초실행---테스트-가능한-코드">앱 최초실행 - 테스트 가능한 코드</h2>
<p><img src="https://images.velog.io/images/sookim-1/post/5d15097f-a73e-4280-917c-a531c436261d/789.png" alt=""></p>
<ul>
<li>앱 최초실행시 UserDefault를 flag를 사용해서 여부를 판단할 수 있지만 매번 앱을 삭제 후 설치해서 판단하기가 테스트할 때는 번거롭기 때문에 두가지 방식으로 분리</li>
</ul>
<h2 id="nsmutableattributedstring">NSMutableAttributedString</h2>
<h3 id="텍스트-지정한-범위만-색깔-변경하기">텍스트 지정한 범위만 색깔 변경하기</h3>
<pre><code class="language-swift">let stringLength = attributedString.length

let attributedString = NSMutableAttributedString(string: &quot;위치 기반으로 빠르게\n주위 친구를 확인&quot;)
attributedString.addAttributes([.foregroundColor: UIColor.green],
                                range: NSRange(location: 0, length: 5))
attributedString.addAttributes([.foregroundColor: UIColor.red],
                                range: NSRange(location: 6, length: stringLength - 6))</code></pre>
<ul>
<li>range를 매개변수로 문자열의 지정한 범위의 색상을 변경할 수 있습니다.</li>
</ul>
<h3 id="텍스트-행-간의-간격-지정하기">텍스트 행 간의 간격 지정하기</h3>
<pre><code class="language-swift">let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1.08
paragraphStyle.alignment = .center // 문자열을 중앙에 정렬

let attributedString = NSMutableAttributedString(string: &quot;위치 기반으로 빠르게\n주위 친구를 확인&quot;)
attributedString.addAttributes([.kern: -0.5, .paragraphStyle: paragraphStyle])</code></pre>
<ul>
<li>텍스트를 중앙에 정렬하기 위해서는 UILable의 <code>textAlignMent</code> 옵션이 아닌 <code>paragraphStyle</code> 를 정렬해줘야 합니다.</li>
</ul>
<h1 id="참고링크">참고링크</h1>
<ul>
<li>온보딩화면<ul>
<li>H.I.G - <a href="https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/onboarding/">https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/onboarding/</a></li>
<li>온보딩구현 CollectionView (Swift5, Xcode 12) - <a href="https://youtu.be/VMiaNFabsZA">https://youtu.be/VMiaNFabsZA</a></li>
<li>최초실행 여부 - <a href="https://dongkyprogramming.tistory.com/30">https://dongkyprogramming.tistory.com/30</a></li>
</ul>
</li>
<li>NSMutableAttributedString<ul>
<li>텍스트 특정범위 폰트크기, 색상변경 - <a href="https://0urtrees.tistory.com/183">https://0urtrees.tistory.com/183</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] - 커스텀컬러 및 폰트, 텍스트필드(밑줄), IQKeyboardManager]]></title>
            <link>https://velog.io/@sookim-1/iOS-%EC%BB%A4%EC%8A%A4%ED%85%80%EC%BB%AC%EB%9F%AC-%EB%B0%8F-%ED%8F%B0%ED%8A%B8-%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%95%84%EB%93%9C%EB%B0%91%EC%A4%84-IQKeyboardManager</link>
            <guid>https://velog.io/@sookim-1/iOS-%EC%BB%A4%EC%8A%A4%ED%85%80%EC%BB%AC%EB%9F%AC-%EB%B0%8F-%ED%8F%B0%ED%8A%B8-%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%95%84%EB%93%9C%EB%B0%91%EC%A4%84-IQKeyboardManager</guid>
            <pubDate>Tue, 18 Jan 2022 21:43:02 GMT</pubDate>
            <description><![CDATA[<h1 id="asset-초기설정">Asset 초기설정</h1>
<h2 id="커스텀컬러-지정하기">커스텀컬러 지정하기</h2>
<h3 id="코드로-customcolor지정">코드로 CustomColor지정</h3>
<blockquote>
<p>Extension으로 초기화 설정하기 (rgb 255.0 나누기설정 및 Hex값 초기화)</p>
</blockquote>
<pre><code class="language-swift">extension UIColor {
    // rgb로 초기화하는 경우 255.0 디폴트 설정
    convenience init(red: Int, green: Int, blue: Int, a: Int = 0xFF) {
        self.init(
            red: CGFloat(red) / 255.0,
            green: CGFloat(green) / 255.0,
            blue: CGFloat(blue) / 255.0,
            alpha: CGFloat(a) / 255.0
        )
    }

    // Hex초기화
    convenience init(hex: Int) {
        self.init(
            red: (hex &gt;&gt; 16) &amp; 0xFF,
            green: (hex &gt;&gt; 8) &amp; 0xFF,
            blue: hex &amp; 0xFF
        )
    }

    // 알파값(투명도를 포함한 Hex초기화)
    convenience init(hexWithAlpha: Int) {
        self.init(
            red: (hexWithAlpha &gt;&gt; 16) &amp; 0xFF,
            green: (hexWithAlpha &gt;&gt; 8) &amp; 0xFF,
            blue: hexWithAlpha &amp; 0xFF,
            a: (hexWithAlpha &gt;&gt; 24) &amp; 0xFF
        )
    }

        enum Colors {
            static let customRed: UIColor = { UIColor(hex: 0x67776) }()
        }
}</code></pre>
<blockquote>
<p>코드로 다크모드지원하기</p>
</blockquote>
<pre><code class="language-swift">let color = UIColor { (traitCollection) -&gt; UIColor in
    if traitCollection.userInterfaceStyle == .dark {
        return UIColor.systemGreen
    } else {
        return UIColor.systemRed
    }
}

view.backgroundColor = color</code></pre>
<p>→ 인터페이스빌더에서 커스텀컬러 사용불가능</p>
<h3 id="asset에-colorset추가">Asset에 ColorSet추가</h3>
<p><img src="https://images.velog.io/images/sookim-1/post/76679238-20c7-4c7d-9a51-51c6bb9bd57e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%206.01.17.png" alt=""></p>
<ol>
<li><p>asset 폴더로 이동한 후 New ColorSet</p>
</li>
<li><p>Inspector탭에서 원하는 컬러색상 지정</p>
</li>
<li><p>다크모드를 지원한다면 Inspector탭에서 Appearance속성을 변경해서 컬러를 추가하면됩니다.</p>
</li>
<li><p><code>UIColor(named: &quot;ColorSet명&quot;)</code> 사용</p>
<ul>
<li><p>extension으로 사용하면 편리</p>
<pre><code class="language-swift">extension UIColor {
  class var ColorSet명: UIColor? { return UIColor(named: &quot;ColorSet명&quot;) }
}</code></pre>
</li>
</ul>
</li>
</ol>
<p>→ 오타위험이 있다., 다크모드 지원이 간편하다.</p>
<h2 id="커스텀폰트-지정하기">커스텀폰트 지정하기</h2>
<h3 id="폰트파일-사용해서-추가하는-경우">폰트파일 사용해서 추가하는 경우</h3>
<ol>
<li>프로젝트에 폰트파일 추가<ul>
<li>Target Membership과 Copy if you needed 체크 확인 한 후 추가하기
<img src="https://images.velog.io/images/sookim-1/post/8898548e-251d-4310-8eb3-03a45df3b9b2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%206.23.27.png" alt=""></li>
</ul>
</li>
</ol>
<ol start="2">
<li>info.plist에서 Fonts provided by application속성 추가 후 Value에 파일명추가</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/316853f5-82d4-4b14-8557-248ceaa30ff5/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%206.23.47.png" alt=""></p>
<ol start="3">
<li><code>label.font = UIFont(name: &quot;S-CoreDream-1Thin&quot;, size: 51)</code> 처럼 사용</li>
</ol>
<h2 id="swiftgen-rswift라이브러리">SwiftGen, R.Swift라이브러리</h2>
<blockquote>
<p>R.Swift라이브러리</p>
</blockquote>
<p>프로젝트의 리소스들을 자동완성 및 Strong Type을 시켜주는 라이브러리 </p>
<p>💡 안드로이드에서는 R이라는 클래스를 이용해 모든 리소스를 관리한다고 합니다.</p>
<p><strong>사용하는 이유?</strong></p>
<ol>
<li>컴파일 시간을 체크할 수 있습니다.</li>
<li>자동완성기능 제공 (이름을 생각해서 작성하지 않아도 됩니다.)</li>
<li>Type화를 통해서 어떤 값을 반환하는지 추측할 수 있습니다.<ul>
<li>R.swift파일에</li>
</ul>
</li>
</ol>
<p><strong>사용방법</strong></p>
<ol>
<li><code>Podfile</code> 에  <code>pod &#39;R.swift</code> 추가</li>
<li><code>pod install</code></li>
<li>타겟의 <code>Build Phases</code> 에서 <code>New Run Script Phase</code> </li>
<li><code>Run Script</code> 에 <code>&quot;$PODS_ROOT/R.swift/rswift&quot; generate &quot;$SRCROOT/R.generated.swift&quot;</code> 코드를 추가한 후 <code>Build Phases</code> 의 순서를 변경해주어야합니다.</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/b3120d54-f783-49d6-9b33-9515912e701b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.16.27.png" alt=""></p>
<ol start="5">
<li><code>Run Script</code> 의 <code>Output Files</code> 에 <code>$SRCROOT/R.generated.swift</code> 코드를 추가합니다.<ul>
<li>이 과정을 통해 빌드과정에서 리소스가 루트폴더에 <code>R.generated.swift</code> 파일로 추가됩니다.</li>
<li><strong>🚨  프로그래밍 과정 중 리소스를 추가한다면 새로운 <code>R.generated.swift</code>  파일로 변경하면 바로 수정되서 편리합니다.(가끔 바로 Xcode상에서 수정안되는문제)</strong></li>
</ul>
</li>
<li>프로젝트에 생성된 <code>Copy items if needed</code> 를 체크해제한 후 <code>R.generated.swift</code> 를 추가합니다.<ul>
<li><code>Copy items if needed</code> : 복사하지 않고 파일원본 이동</li>
</ul>
</li>
</ol>
<ul>
<li>사용예시 - <a href="https://github.com/mac-cain13/R.swift/blob/master/Documentation/Examples.md#images">https://github.com/mac-cain13/R.swift/blob/master/Documentation/Examples.md#images</a></li>
</ul>
<blockquote>
<p>Swiftgen라이브러리</p>
</blockquote>
<p>프로젝트 리소스들을 자동완성 시켜주는 라이브러리</p>
<p><strong>사용하는 이유</strong>?</p>
<ol>
<li>리소스를 문자열로 접근할 때 에러발생을 최소화할 수 있습니다.</li>
<li>자동완성으로 편리하게 사용가능합니다.</li>
<li>리소스의 변경이 일어났을 때 에러를 빠르게 찾을 수 있다.</li>
</ol>
<p><strong>사용방법 (CocoaPod)</strong></p>
<ol>
<li><code>Podfile</code> 에  <code>pod &#39;SwiftGen&#39;, &#39;~&gt; 6.0’</code> 추가</li>
<li><code>pod install</code></li>
<li>터미널(프로젝트루트폴더)에서 <code>./Pods/SwiftGen/bin/swiftgen config init</code> 실행<ul>
<li><code>swiftgen.yml</code> 파일을 생성하는 과정</li>
</ul>
</li>
<li>프로젝트에 생성된 <code>Copy items if needed</code> 를 체크해제한 후 <code>swiftgen.yml</code> 를 추가합니다.<ul>
<li>🚨  위치는 프로젝트 네비게이터 바로 아래</li>
</ul>
</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/f8538dde-7cec-46b5-b64d-f4c4c056ee54/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%204.09.22.png" alt=""></p>
<ol start="5">
<li><p><code>swiftgen.yml</code> 파일 작성하기</p>
<pre><code class="language-swift"> strings:
   inputs: test/Base.lproj
   filter: .+\.strings$
   outputs:
     - templateName: structured-swift5
       output: test/Generated/Strings.swift
 xcassets:
   inputs:
     - test/Assets.xcassets
   outputs:
     - templateName: swift5
       output: test/Generated/Assets.swift</code></pre>
<ul>
<li>루트디렉토리명 : test</li>
<li>생성된 swift파일 저장하기 위한 폴더 Generated생성합니다.</li>
</ul>
</li>
<li><p>터미널(프로젝트루프폴더)에서 <code>./Pods/SwiftGen/bin/swiftgen</code> 실행</p>
<ul>
<li>🚨  리소스를 자주변경한다면 터미널말고 <code>Run Script</code>  에 작성해서 사용할 수 있다.</li>
</ul>
</li>
</ol>
<p><img src="https://images.velog.io/images/sookim-1/post/1f1d3a72-a4d4-40c1-9ae8-023d5c62f0bf/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%204.23.53.png" alt="">
7. Generated 폴더에 생성된 Assets.swift와 Strings.swift를 xcode로 이동 후 사용하면 됩니다.</p>
<p><img src="https://images.velog.io/images/sookim-1/post/85499288-5dc5-4075-94b7-88ac261f8d41/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%204.26.17.png" alt=""></p>
<ul>
<li>느낀점 : 🤔  Localization을 할 때는 SwiftGen이 strings파일을 처리할 때 좀 더 편리한 것 같고, 다른 리소스에 사용하기에는 R.Swift가 편리하지만 파일이 좀 더 큰 것 같다.</li>
</ul>
<h1 id="uitextfield">UITextField</h1>
<h3 id="텍스트필드-singline">텍스트필드 Singline</h3>
<blockquote>
<p>밑줄 UIView로 넣는 방식</p>
</blockquote>
<pre><code class="language-swift">override func viewDidLayoutSubviews() {
    let borderLine = UIView()
    borderLine.frame = CGRect(x: 0, y: Double(textField.frame.height) - 2, width: Double(textField.frame.width), height: 2)
    borderLine.backgroundColor = UIColor.systemGreen.cgColor

        textField.borderStyle = .none
    self.addSubview(borderLine)
}</code></pre>
<blockquote>
<p>밑줄 CALayer로 넣는 방식</p>
</blockquote>
<pre><code class="language-swift">override func viewDidLayoutSubviews() {
    let bottomLine = CALayer()
    bottomLine.frame = CGRect(x: 0, y: textField.frame.height - 2, width: textField.frame.width, height: 2)
    bottomLine.backgroundColor = UIColor.systemGreen.cgColor

    textField.borderStyle = .none
    textField.layer.addSublayer(bottomLine)
}</code></pre>
<ul>
<li>두가지 방법 모두 상관 없고 y좌표를 텍스트필드 높이에서 일정높이 아래에 위치시키고 height로 굵기를 지정할 수 있습니다.</li>
<li>🚨 viewDidLoad에서는 오토레이아웃규칙이 모두 적용되지 않기 때문에 viewDidLayoutSubviews()에서 실행해야합니다.</li>
<li>자주 사용한다면 UITextField에 Extension으로 사용하면 편리하게 사용할 수 있을 것 같습니다.</li>
</ul>
<h3 id="화면-키보드-가리는-현상해결---iqkeyboardmanager라이브러리">화면 키보드 가리는 현상해결 - IQKeyboardManager라이브러리</h3>
<p>UITextField 및 UITextView를 사용하는 경우 키보드가 화면을 가리는 경우 처리를 도와주는 라이브러리</p>
<p><strong>사용방법(CocoaPod)</strong></p>
<ol>
<li><p><code>Podfile</code> 에  <code>pod &#39;IQKeyboardManagerSwift&#39;</code> 추가</p>
</li>
<li><p><code>pod install</code></p>
</li>
<li><p><code>AppDelegate.swift</code> 파일</p>
<pre><code class="language-swift"> import IQKeyboardManagerSwift

 @main
 class AppDelegate: UIResponder, UIApplicationDelegate {
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&gt; Bool {

       IQKeyboardManager.shared.enable = true

       return true
     }
 }</code></pre>
</li>
</ol>
<p><strong>커스텀화하기</strong></p>
<pre><code class="language-swift">
import IQKeyboardManagerSwift

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&gt; Bool {

      IQKeyboardManager.shared.enable = true
      IQKeyboardManager.shared.enableDebugging = true //키보드에 관한 정보들을 확인할 수 있습니다.
      IQKeyboardManager.shared.enableAutoToolbar = true // 기본으로 생성되는 툴바옵션 사용여부

      IQKeyboardManager.shared.overrideKeyboardAppearance = true // 기본제공 키보드Appearance 변경여부
      IQKeyboardManager.shared.keyboardAppearance = .dark // 기본제공 키보드Appearance 다크색으로 변경

      IQKeyboardManager.shared.keyboardDistanceFromTextField = 100.0 // 키보드와 텍스트요소와의 거리지정
      IQKeyboardManager.shared.toolbarTintColor = .systemGreen // 툴바색상 변경가능 (overrideKeyboardAppearance 함께 쓰면 적용안됨)
      IQKeyboardManager.shared.toolbarDoneBarButtonItemText = &quot;확인&quot; // 툴바 Done버튼 텍스트변경
      IQKeyboardManager.shared.toolbarDoneBarButtonItemImage = UIImage(systemName: &quot;star.fill&quot;) // 툴바 Done버튼 이미지변경
      IQKeyboardManager.shared.shouldShowToolbarPlaceholder = true // 툴바 중간에 텍스트요소의 placeholder를 표시합니다.
      IQKeyboardManager.shared.placeholderFont = UIFont(name: &quot;Setting&quot;, size: 15) // 툴바 placeholder의 폰트지정
      IQKeyboardManager.shared.shouldResignOnTouchOutside = true // 화면에서 텍스트요소의 바깥부분 누르면 사라지도록 지정
      IQKeyboardManager.shared.shouldPlayInputClicks = false // 툴바의 화살표이동할 때 소리여부

      return true
    }
}</code></pre>
<ul>
<li>AutoToolbar</li>
</ul>
<p><img src="https://images.velog.io/images/sookim-1/post/de6d9553-7c35-429c-af40-784a2fdded1e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-19%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%205.33.05.png" alt=""></p>
<h1 id="참고링크">참고링크</h1>
<ul>
<li>커스텀컬러<ul>
<li>CustomColor 생성과 관리는 어떤방식이 효율적일까요? -  <a href="https://wodyios.tistory.com/11">https://wodyios.tistory.com/11</a></li>
<li>HexColor Swift에서 적용하기 - <a href="https://sosoingkr.tistory.com/31">https://sosoingkr.tistory.com/31</a></li>
<li>김종권의 iOS앱 개발 알아가기 - <a href="https://ios-development.tistory.com/686">https://ios-development.tistory.com/686</a></li>
</ul>
</li>
<li>커스텀폰트<ul>
<li>TTF <code>vs</code> OTF 차이점 - <a href="https://zeddios.tistory.com/198">https://zeddios.tistory.com/198</a></li>
</ul>
</li>
<li>IQKeyboardManager<ul>
<li>공식 깃허브 - <a href="https://github.com/hackiftekhar/IQKeyboardManager">https://github.com/hackiftekhar/IQKeyboardManager</a></li>
<li>커스텀화 - <a href="https://youtu.be/b6njX3Ui8qU">https://youtu.be/b6njX3Ui8qU</a></li>
</ul>
</li>
<li>SwiftGen<ul>
<li>공식 깃허브 - <a href="https://github.com/SwiftGen/SwiftGen">https://github.com/SwiftGen/SwiftGen</a></li>
<li>제드 블로그 - <a href="https://zeddios.tistory.com/1017?category=682196">https://zeddios.tistory.com/1017?category=682196</a></li>
<li>dev.ssun 블로그 - <a href="https://hyesunzzang.tistory.com/230">https://hyesunzzang.tistory.com/230</a></li>
<li>SwiftGen in Xcode 11 - <a href="https://www.youtube.com/watch?v=DHmenm94PlI">https://www.youtube.com/watch?v=DHmenm94PlI</a></li>
<li>raywenderlich - <a href="https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios">https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios</a></li>
</ul>
</li>
<li>R.swift<ul>
<li>공식 깃허브 - <a href="https://github.com/mac-cain13/R.swift">https://github.com/mac-cain13/R.swift</a></li>
<li>제드 블로그 - <a href="https://zeddios.tistory.com/1016">https://zeddios.tistory.com/1016</a></li>
<li>5anniversary - <a href="https://blog.5anniversary.dev/50">https://blog.5anniversary.dev/50</a></li>
</ul>
</li>
<li>UITextField Singline<ul>
<li>UIView - <a href="https://medium.com/@codepany/swift-3-custom-uitextfield-with-single-line-input-ae91e10f30db">https://medium.com/@codepany/swift-3-custom-uitextfield-with-single-line-input-ae91e10f30db</a></li>
<li>CALayer - <a href="https://www.youtube.com/watch?v=xm0WK1L2DjI">https://www.youtube.com/watch?v=xm0WK1L2DjI</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] - FireBase를 통한 SMS전화번호 인증하기]]></title>
            <link>https://velog.io/@sookim-1/iOS-FireBase%EB%A5%BC-%ED%86%B5%ED%95%9C-SMS%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sookim-1/iOS-FireBase%EB%A5%BC-%ED%86%B5%ED%95%9C-SMS%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 17 Jan 2022 16:31:15 GMT</pubDate>
            <description><![CDATA[<h1 id="firebase로-전화번호-인증">Firebase로 전화번호 인증</h1>
<ul>
<li>Firebase를 사용하기 위해서 첫번째로 Apple프로젝트에 Firebase를 추가해야합니다.</li>
<li><a href="https://firebase.google.com/docs/ios/setup?hl=ko">https://firebase.google.com/docs/ios/setup?hl=ko</a> - 공식문서에 자세한 방법이 설명되어 있습니다.<ul>
<li><a href="https://console.firebase.google.com/">https://console.firebase.google.com/</a> - console링크</li>
</ul>
</li>
</ul>
<p>🚨 <strong>GoogleService-Info.plist를 추가하기 전에 원격저장소(Github)에 프로젝트를 업로드한다면 제거하고 올리는 것을 권장 (개인정보 유출 가능성이 있습니다.)</strong></p>
<h2 id="ios에서-전화번호로-firebase에-인증">iOS에서 전화번호로 Firebase에 인증</h2>
<ul>
<li><a href="https://firebase.google.com/docs/auth/ios/phone-auth?hl=ko">https://firebase.google.com/docs/auth/ios/phone-auth?hl=ko</a> - 공식문서</li>
<li>Firebase 인증을 사용하면 사용자의 전화로 SMS 메시지를 전송하여 로그인하는 것이 가능합니다. 사용자는 SMS 메시지에 포함된 일회용 코드를 사용하여 로그인합니다.</li>
<li>여러 Firebase제품 중 인증에 관련된 Auth를 추가합니다.<ol>
<li><a href="https://firebase.google.com/docs/ios/swift-package-manager?hl=ko">https://firebase.google.com/docs/ios/swift-package-manager?hl=ko</a> - SPM설치</li>
<li><code>pod &#39;Firebase/Auth&#39;</code> - CocoaPods</li>
</ol>
</li>
</ul>
<h3 id="firebase-프로젝트에서-전화번호-로그인-사용-설정">Firebase 프로젝트에서 전화번호 로그인 사용 설정</h3>
<ul>
<li>Console에서 Authetication섹션에서 Sign in method탭에서 전화를 제공합니다.
<img src="https://images.velog.io/images/sookim-1/post/e0549483-fa78-49ca-be80-181881aa3cc0/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-17%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.25.48.png" alt=""></li>
</ul>
<h3 id="앱-인증-사용-설정">앱 인증 사용 설정</h3>
<p>전화번호 인증을 사용하려면 Firebase에서 전화번호 로그인 요청이 내 앱에서 발생한 것인지 확인할 수 있어야 합니다.</p>
<ol>
<li><strong><code>Silent APN 알림</code></strong> : 기기에서 처음으로 전화번호를 통해 사용자를 로그인 처리하면 Firebase 인증에서 사용자 모르게 기기로 푸시 알림을 전송하여 토큰을 보냅니다.<ul>
<li>iOS 8.0이상은 silent notification은 사용자의 권한요청이 필수가 아닙니다.</li>
</ul>
</li>
<li><strong><code>reCAPTCHA 인증</code> :</strong> 사용자가 앱의 백그라운드 새로고침을 중지했거나 iOS 시뮬레이터에서 앱을 테스트하는 경우와 같이 자동 푸시 알림을 주고받을 수 없는 경우, Firebase 인증은 reCAPTCHA 인증을 사용하여 전화번호 로그인 과정을 완료합니다.</li>
</ol>
<h2 id="사용자-전화로-인증-코드-보내기">사용자 전화로 인증 코드 보내기</h2>
<p>사용자에게 전화번호를 제공하도록 요청하는 UI를 작성한 후, <strong><code>verifyPhoneNumber:UIDelegate:completion:</code></strong> 를 호출하여 Firebase가 사용자의 전화에 SMS로 인증 코드를 전송하도록 요청합니다.</p>
<pre><code class="language-swift">var phoneNumber = &quot;123-456-789&quot; // 전화번호 

PhoneAuthProvider.provider()
  .verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
      if let error = error {
        self.showMessagePrompt(error.localizedDescription)
        return
      }
      // 에러가 없다면 사용자에게 인증코드와 verificationID(인증ID) 전달
  }</code></pre>
<ul>
<li><p><strong><code>verifyPhoneNumber:UIDelegate:completion:</code></strong> 가 호출하면 Firebase가 앱에 사용자 모르게 푸시 알림을 보내거나 사용자에게 reCAPTCHA 챌린지를 표시합니다.</p>
</li>
<li><p>지정된 전화번호로 인증코드가 포함된 SMS메시지를 보낸 후 성공적으로 처리되면 인증ID를 전달합니다.</p>
<ul>
<li>전달된 인증 ID는 UserDefault 및 DB에 저장합니다.</li>
</ul>
</li>
<li><p>Auth 인스턴스의 <code>languageCode</code> 속성을 사용하여 인증언어를 지정하면 SMS메시지를 현지화할 수 있습니다.</p>
<pre><code class="language-swift">  Auth.auth().languageCode = &quot;kr&quot;</code></pre>
</li>
</ul>
<blockquote>
<p>권장사항</p>
</blockquote>
<ol>
<li>지역마다 법에따라 다르지만, 일반적으로 사용자에게 SMS메시지가 발송되고 요금이 발생할 수 있다는 점을 알려야 합니다.</li>
</ol>
<h2 id="인증코드로-사용자-로그인-처리">인증코드로 사용자 로그인 처리</h2>
<p>정상적으로 인증코드와 인증ID를 받았다면 <strong><code>signInWithCredential:completion:</code></strong> 를 호출하여 <strong><code>FIRPhoneAuthCredential</code></strong> 객체를 만든 후 사용자로그인합니다.</p>
<ul>
<li><p><strong><code>FIRPhoneAuthCredential</code> 객체 생성</strong></p>
<pre><code class="language-swift">  let credential = PhoneAuthProvider.provider().credential(
    withVerificationID: verificationID,
    verificationCode: verificationCode
  )</code></pre>
</li>
<li><p><strong><code>FIRPhoneAuthCredential</code> 객체를 사용하여 로그인</strong></p>
<pre><code class="language-swift">  Auth.auth().signIn(with: credential) { authResult, error in
      if let error = error {
        print(error.debugDescription)
      }
      // 인증 완료 -&gt; 로그인 진행
  }</code></pre>
<ul>
<li>세부적인 에러처리는 <a href="https://firebase.google.com/docs/auth/ios/phone-auth">공식문서</a> 참고</li>
</ul>
</li>
</ul>
<h2 id="가상-전화번호로-할당량제한-없이-테스트하기">가상 전화번호로 할당량제한 없이 테스트하기</h2>
<blockquote>
<p>가상전화번호로 테스트 장점</p>
</blockquote>
<ol>
<li>할당량을 소모하지 않고 전화번호 인증을 테스트합니다.</li>
<li>실제 SMS 메시지를 보내지 않고 전화번호 인증을 테스트합니다.</li>
<li>동일한 전화번호로 연속으로 테스트를 하는경우 위험없이 실행할 수 있습니다. (앱스토어 리뷰과정에서 리뷰어가 같은 전화번호로 테스트를 할 때 reject의 위험을 최소화할 수 있습니다.)</li>
<li>시뮬레이터에서 테스트를 쉽게 할 수 있습니다.</li>
<li>실제 전화번호에 적용되는 보안검사를 하지않고 테스트를 할 수 있습니다.</li>
</ol>
<blockquote>
<p>가상전화번호를 위한 요구사항</p>
</blockquote>
<ol>
<li>실제로 존재하지 않는 전화번호를 사용해야합니다.</li>
<li>가상전화번호는 최대 10개까지 추가할 수 있습니다.</li>
<li>추측하기 어려운 테스트 전화번호/코드를 사용하고 자주 변경합니다.</li>
<li>전화번호는 길이 및 기타 제약 조건에 따라 올바른 형식이어야 합니다.(실제번호처럼 동일한 유효성검사를 진행합니다.)</li>
</ol>
<h3 id="가상전화번호-생성하기">가상전화번호 생성하기</h3>
<ul>
<li><p>Console에서 Authetication섹션에서 Sign in method탭에서 전화번호를 제공합니다.</p>
<p><img src="https://images.velog.io/images/sookim-1/post/64e71351-0358-4f1e-9c18-55017f4131a2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-18%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.12.19.png" alt=""></p>
</li>
<li><p>로그인이 완료되면 해당전화번호로 Firebase사용자가 생성됩니다.</p>
</li>
<li><p>사용자는 실제 전화번호 사용자와 동일한 행동 및 속성을 가지며 동일한 방식으로 실시간 데이터베이스/Cloud Firestore 및 기타 서비스에 액세스할 수 있습니다. 이 과정에서 생성된 ID 토큰은 실제 전화번호 사용자와 동일한 서명을 갖습니다.</p>
</li>
<li><p><a href="https://firebase.google.com/docs/auth/admin/custom-claims#swift">Control Access with Custom Claims and Security Rules</a> - Firebase Admin SDK는 사용자계정 커스텀을 지원합니다.</p>
<ul>
<li>사용자에게 데이터나 리소스에 접근할 관리자권한을 줄 수 있습니다.</li>
<li>사용자가 속한 다른 그룹을 정의할 수 있습니다.</li>
<li>다양한 단계 접근을 제공합니다.<ul>
<li>유료/무료 가입자</li>
<li>관리자 및 일반사용자</li>
<li>교사 및 학생</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="통합-테스트">통합 테스트</h3>
<ul>
<li><p><code>Firebase Authentication</code> 는 수동으로 테스트를 진행하는 경우, reCAPTCHA인증 및 silent push notifications를 무시하고 진행할 수 있습니다.</p>
<ul>
<li><p><strong><code>appVerificationDisabledForTesting</code></strong> 값을 TRUE로 설정</p>
</li>
<li><p>가상전화번호만 사용할 수 있습니다.</p>
<pre><code class="language-swift">let phoneNumber = &quot;+15605551342&quot;
let testVerificationCode = &quot;220118&quot;

Auth.auth().settings?.isAppVerificationDisabledForTesting = true

PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate:nil) {
  (verificationID, error) in
  if error != nil {
      print(&quot;인증Error: \(error.debugDescription)&quot;)

      return
  }
  let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID ?? &quot;&quot;,
                                                           verificationCode: testVerificationCode)
  Auth.auth().signIn(with: credential) { (authData, error) in
      if error != nil {
          print(&quot;로그인Error: \(error.debugDescription)&quot;)

          return
      }
      print(&quot;authData: \(authData)&quot;)
  }
}</code></pre>
</li>
<li><p>🚨 Capability에서 Background Modes에 Remote notifications가 체크되어 있으면 에러가 발생함</p>
<ul>
<li><a href="https://stackoverflow.com/questions/62369891/this-fake-notification-should-be-forwarded-to-firebase-swift5-ios?rq=1">Optional(Error Domain=FIRAuthErrorDomain Code=17054 &quot;If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to FIRAuth&#39;s canHandleNotificaton: method.&quot; UserInfo={NSLocalizedDescription=If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to FIRAuth&#39;s canHandleNotificaton: method., FIRAuthErrorUserInfoNameKey=ERROR_NOTIFICATION_NOT_FORWARDED})</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="swizzling-없이-전화번호-로그인">Swizzling 없이 전화번호 로그인</h2>
<p>Swizzling이란? 런타임때 어떤 메서드를 다른 메서드로 바꾸는 것을 말합니다. </p>
<p>Firebase가 silent push notification으로 APNs 토큰을 얻거나, reCAPTCHA 지정한 custom scheme으로 리다이렉트를 하는 과정에서 Swizzling을 합니다.</p>
<ul>
<li>swizzling 사용하지 않는다면 명시적으로 APNs 토큰을 전달하고 redirect URL도 제공해야합니다.</li>
</ul>
<blockquote>
<p>Swizzling 비활성화</p>
</blockquote>
<ul>
<li>info.plist에서 <strong><code>FirebaseAppDelegateProxyEnabled</code></strong> 속성울 추가한 후 No로 설정합니다. (Firebase 모든 products에서 swizzling 비활성화) ****</li>
</ul>
<ol>
<li><p>APNs device token 얻기</p>
<pre><code class="language-swift"> func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
   // 디바이스토큰을 auth에 전달
   Auth.auth().setAPNSToken(deviceToken, type: .prod)

   // 앱에서 필요한 경우 디바이스토큰 추가 처리
   // ...
 }</code></pre>
</li>
<li><p>redirect URL</p>
<pre><code class="language-swift"> // AppDelegate.swift

 // iOS 9이상
 func application(_ application: UIApplication, open url: URL,
     options: [UIApplicationOpenURLOptionsKey : Any]) -&gt; Bool {
   if Auth.auth().canHandle(url) {
     return true
   }
 }

 // iOS 9미만
 func application(_ application: UIApplication,
                  open url: URL,
                  sourceApplication: String?,
                  annotation: Any) -&gt; Bool {
   if Auth.auth().canHandle(url) {
     return true
   }
 }

 // SceneDelegate.swift
 func scene(_ scene: UIScene, openURLContexts URLContexts: Set&lt;UIOpenURLContext&gt;) {
   for urlContext in URLContexts {
       let url = urlContext.url
       Auth.auth().canHandle(url)
   }
 }</code></pre>
</li>
</ol>
<h3 id="사용자가-로그인-되면-firebase프로젝트에-정보가-저장">사용자가 로그인 되면 Firebase프로젝트에 정보가 저장</h3>
<ul>
<li>사용자가 처음 로그인하면 새로운 사용자 계정이 생성되고 사용자 이름 및 암호, 전화 번호 또는 인증 공급자 정보와 같은 자격 증명에 연결됩니다.</li>
<li>새로운 계정은 Firebase 프로젝트의 일부로 저장되며, 사용자가 로그인하는 방법에 관계없이 프로젝트의 모든 앱에서 사용자를 식별하는 데 사용할 수 있습니다.<ul>
<li>따라서 <a href="https://firebase.google.com/docs/reference/ios/firebaseauth/api/reference/Classes/FIRUser">FIRUser</a>객체에서 사용자의 기본 프로필 정보를 가져올 수 있습니다.</li>
<li><a href="https://firebase.google.com/docs/auth/ios/manage-users">Manage Users in Firebase</a></li>
</ul>
</li>
<li>Firebase 실시간 데이터베이스 및 클라우드 저장소 <a href="https://firebase.google.com/docs/database/security/rules-conditions">보안 규칙</a>에서는 <code>auth</code> 변수에서 로그인한 사용자의 고유 사용자 ID를 가져와 사용자가 액세스할 수 있는 데이터를 제어할 수 있습니다.</li>
<li><a href="https://firebase.google.com/docs/auth/ios/account-linking">Link Multiple Auth Providers to an Account on Apple Platforms</a></li>
<li>인증 세부적인 에러처리 - <a href="https://firebase.google.com/docs/auth/ios/errors">Handle Firebase Apple Platforms Auth Errors</a></li>
</ul>
<blockquote>
<p>로그아웃</p>
</blockquote>
<pre><code class="language-swift">let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print(&quot;Error signing out: %@&quot;, signOutError)
}</code></pre>
<h2 id="💡-authentication-vs-authorization-vs-certification">💡 Authentication <code>vs</code> Authorization <code>vs</code> Certification</h2>
<ol>
<li>Authentication<ul>
<li>(사용 또는 입장하는 상황에서) 인증</li>
<li>ex. 로그인하는 절차</li>
</ul>
</li>
<li>Authorization<ul>
<li>(권한) 인증</li>
<li>ex. 로그인 한 계정이 관리자인지, 일반사용자인지 구분하는 절차</li>
</ul>
</li>
<li>Certification<ul>
<li>(심사를 통과한) 인증</li>
<li>일정 기준을 넘었을때 주어지는 것</li>
</ul>
</li>
</ol>
<h1 id="참고링크">참고링크</h1>
<ul>
<li>Firebase 블로그<ul>
<li><a href="https://fomaios.tistory.com/entry/Firebase-%ED%9C%B4%EB%8C%80%ED%8F%B0%EB%B2%88%ED%98%B8%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%98%EA%B8%B0Authenticate-with-Phone-Number">https://fomaios.tistory.com/entry/Firebase-휴대폰번호로-로그인하기Authenticate-with-Phone-Number</a></li>
<li><a href="https://ichi.pro/ko/iosui-firebase-jeonhwa-beonho-injeung-64735371857612">https://ichi.pro/ko/iosui-firebase-jeonhwa-beonho-injeung-64735371857612</a></li>
</ul>
</li>
<li>문자 인증번호 자동완성 - <a href="https://im-designloper.tistory.com/59">https://im-designloper.tistory.com/59</a></li>
<li>Swizzling 블로그<ul>
<li><a href="https://zeddios.tistory.com/554">Swift ) Method Swizzling</a></li>
<li><a href="https://jinsangjin.tistory.com/140">https://jinsangjin.tistory.com/140</a></li>
</ul>
</li>
<li>URL Scheme 등록 - <a href="https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app">https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app</a></li>
<li>Apple push notification service (APNs) 설정하기 - <a href="https://spiralmoon.tistory.com/entry/Apple-Apple-push-notification-service-APNs-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0">https://spiralmoon.tistory.com/entry/Apple-Apple-push-notification-service-APNs-설정하기</a></li>
<li>authentication 과 authorization의 차이 - <a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=iq_up&amp;logNo=100057746027">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=iq_up&amp;logNo=100057746027</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 애플 개발자 계정 등록하기 - 개인개발자]]></title>
            <link>https://velog.io/@sookim-1/iOS-%EC%95%A0%ED%94%8C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EA%B3%84%EC%A0%95-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0-%EA%B0%9C%EC%9D%B8%EA%B0%9C%EB%B0%9C%EC%9E%90</link>
            <guid>https://velog.io/@sookim-1/iOS-%EC%95%A0%ED%94%8C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EA%B3%84%EC%A0%95-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0-%EA%B0%9C%EC%9D%B8%EA%B0%9C%EB%B0%9C%EC%9E%90</guid>
            <pubDate>Mon, 15 Nov 2021 13:06:18 GMT</pubDate>
            <description><![CDATA[<h3 id="1-개요">1. 개요</h3>
<p>나만의 앱을 만들어서 앱스토어에 배포하려면 애플은 개발자 계정을 등록한 후에 배포를 할 수 있습니다. </p>
<p>애플 개발자계정을 등록하는 방법에 대해서 알아보겠습니다. 
개발자계정은 개인과 회사단위로 등록이 가능하지만 이 포스터는 개인을 기준으로 진행합니다.</p>
<h3 id="2-계정-등록시-필요한-것들">2. 계정 등록시 필요한 것들</h3>
<ul>
<li>개발자 계정시 필요한 것들<ul>
<li>애플 계정</li>
<li>애플 기기</li>
<li>계정 내 국가/지역 인증, 이메일 인증, 휴대폰 번호 인증, 이중 인증설정</li>
<li>한화 129000원</li>
</ul>
</li>
</ul>
<p>😅  이중인증설정이 되어있지 않다면 공식사이트를 참고하여 설정을 켜주시면 됩니다. </p>
<ul>
<li><a href="https://support.apple.com/ko-kr/HT204915">https://support.apple.com/ko-kr/HT204915</a></li>
</ul>
<h3 id="3-개발자-계정-등록과정">3. 개발자 계정 등록과정</h3>
<p>이중인증설정이 모두 켜져있다면 AppleDeveloperProgram 사이트로 들어가서 등록을 진행합니다.</p>
<ul>
<li>AppleDeveloperProgram - <a href="https://developer.apple.com/kr/programs/">https://developer.apple.com/kr/programs/</a></li>
</ul>
<p>진행하게 된다면 개인과 조직으로 등록에 필요한 사항들이 나옵니다. 개인으로 등록하는 경우 등록시작버튼을 클릭합니다.</p>
<p>개인 주소와 이름등 개인정보에관한 내용등을 현지 언어와 로마자(영어)로 작성한 후에</p>
<p>아래 이미지대로 순서대로 진행합니다.
<img src="https://images.velog.io/images/sookim-1/post/6b8a5b34-a512-449f-a8e8-58cda88e5e80/Untitled.png" alt="">
<img src="https://images.velog.io/images/sookim-1/post/183d838d-c6ca-4f25-92ec-13d987719ddf/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-15%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.34.06.png" alt="">
<img src="https://images.velog.io/images/sookim-1/post/96afc9f1-ee18-489f-9f9b-aed46c5e8c25/1.png" alt=""></p>
<p>모든 등록이 끝났다면 구입버튼을 클릭하여 원하는 결제수단으로 결제를 완료하면 등록이 모두 완료됩니다.</p>
<p>등록이 났다고 바로 승인을 해주지는 않고 약간의 시간이 소요됩니다.</p>
<p>따라서 등록을 모두 마친 후에 다시 AppleDeveloperProgram 개발자 계정 사이트로 들어가게 되면  아직 승인이 나지 않아서 화면에 등록과정이 아직 남아있습니다.</p>
<p><img src="https://images.velog.io/images/sookim-1/post/dda0794e-129d-4f71-a243-a101f3caae3f/Untitle1d.png" alt=""></p>
<p>주문승인이 아닌 개발자 승인이 완료되면 메인페이지 보이는 이미지가 아래이미지처럼 변경됩니다!
<img src="https://images.velog.io/images/sookim-1/post/afe8e424-14c9-48e0-8204-e905e7d988ea/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.15.05.png" alt=""></p>
<ul>
<li>승인 확인 방법<ol>
<li><a href="https://appstoreconnect.apple.com/login">appstoreconnect.apple.com</a></li>
<li>메일로 확인</li>
</ol>
</li>
</ul>
<h3 id="참고링크">참고링크</h3>
<ul>
<li>애플 개발자 계정 도움말 - <a href="https://help.apple.com/developer-account/#/">https://help.apple.com/developer-account/#/</a></li>
<li>애플 개발자 등록 설명 공식사이트 - <a href="https://developer.apple.com/kr/support/app-account/">https://developer.apple.com/kr/support/app-account/</a></li>
<li>개발자 계정 구매 블로그 - <a href="https://m.blog.naver.com/nemonangoom/222024589236">https://m.blog.naver.com/nemonangoom/222024589236</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] UML (Unified Modeling Language)]]></title>
            <link>https://velog.io/@sookim-1/iOS-UML-Unified-Modeling-Language</link>
            <guid>https://velog.io/@sookim-1/iOS-UML-Unified-Modeling-Language</guid>
            <pubDate>Mon, 15 Nov 2021 13:01:11 GMT</pubDate>
            <description><![CDATA[<h2 id="uml이란">UML이란?</h2>
<ul>
<li>UML은 의사소통을 좀 더 쉽게 하기위한 것</li>
<li>UML은 코드를 설명하는 여러 방식 중 하나입니다.<ul>
<li>ex. 코드를 설명하는 방법 중 코드 자체를 보여주는 방법, 주요 기능에 대한 코드단락을 보여주는 방법, 글로 설명하는 방법등등 여러가지가 있습니다.</li>
<li>도표 - 구현방식 및 디자인한 구조를 시각화/도식화를 추상화한 방식으로 보여주는 것</li>
</ul>
</li>
<li>UML은 Unified Modeling Language의 약자입니다. (통합 모델링 언어)</li>
<li>UML은 표준화된 모델링언어이기 때문에 OMG(Objective Management Group)에서 표준으로 채택한 언어이고 계속해서 수정보완되어 나가고 있습니다.</li>
<li>UML의 목적은 시스템이 디자인된 방식을 시각화하는 기준/표준 방식을 제공하는 것입니다.</li>
</ul>
<h2 id="uml이-필요한-이유">UML이 필요한 이유?</h2>
<ul>
<li>자신한테는 전체 구조 및 클래스 의존성을 파악하는데 유용합니다.</li>
<li>동료나 협업을 할 때 의사소통 및 설계를 논의하는데 유용합니다.</li>
<li>미래에 이 프로그램을 보았을 때 문서화가 되어있어 빠르게 이해하는데 유용합니다.</li>
</ul>
<h2 id="uml의-종류">UML의 종류</h2>
<ul>
<li>UML은 크게 2가지 종류로 나뉩니다.<ul>
<li>첫 번째는 구조 다이어그램 (정적인 구조) : 시스템의 개념 관계등에 관한 요소를 보여줍니다.</li>
<li>두 번째는 행위 다이어그램 (동적인 구조) : 각 요소들간에 변화와 흐름 등을 보여줍니다.</li>
</ul>
</li>
</ul>
<h3 id="클래스-다이어그램-구조-다이어그램-중-하나">클래스 다이어그램 (구조 다이어그램 중 하나)</h3>
<ul>
<li>클래스 다이어그램은 의존관계를 파악하고 싶을 때 사용합니다.</li>
<li>한 클래스를 네모로 표현하고 프로퍼티와 메서드 2부분으로 나눠서 표기합니다.</li>
<li>클래스 간의 관계를 선을 사용하여 표현합니다.
  <img src="https://images.velog.io/images/sookim-1/post/cedb1103-e6d3-4a74-b9f4-60ef687d7de8/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-15%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%209.10.52.png" alt=""></li>
</ul>
<pre><code>- 일반화 : 한 클래스가 다른클래스를 포함하는 상위 관계일 때 (예를 들어, 상속 관계) 화살표는 자식클래스가 부모클래스를 가리켜야 합니다.
- 의존 : 한 클래스가 다른클래스를 참조하는 관계</code></pre><h3 id="시퀀스-다이어그램-행위-다이어그램-중-하나">시퀀스 다이어그램 (행위 다이어그램 중 하나)</h3>
<ul>
<li>시퀀스다이어그램은 순서를 파악하고 싶을 때 사용합니다.</li>
<li>시간의 순서대로 작성합니다.</li>
</ul>
<h2 id="uml-작성하는-방법">UML 작성하는 방법</h2>
<ul>
<li>손이나 UML툴을 사용하여 작성합니다.</li>
</ul>
<ol>
<li>손으로 직접 그려서 작성하기</li>
<li>Gliffy </li>
<li><a href="http://draw.io">draw.io</a> </li>
<li>miro</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] UI스케치]]></title>
            <link>https://velog.io/@sookim-1/iOS-UI%EC%8A%A4%EC%BC%80%EC%B9%98</link>
            <guid>https://velog.io/@sookim-1/iOS-UI%EC%8A%A4%EC%BC%80%EC%B9%98</guid>
            <pubDate>Mon, 15 Nov 2021 12:57:18 GMT</pubDate>
            <description><![CDATA[<h2 id="🎨-느린우체통-ui스케치">🎨 느린우체통 UI스케치</h2>
<p><img src="https://images.velog.io/images/sookim-1/post/d7536a12-ae72-475f-8708-fdc4984a1f22/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-15%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%207.36.35.png" alt=""></p>
<ul>
<li>Figma 툴로 작성</li>
<li>링크 - <a href="https://www.figma.com/file/NhmkJF2ZfFxzMT0fcT7iWr/%EB%8A%90%EB%A6%B0-%EC%9A%B0%EC%B2%B4%ED%86%B5?node-id=0%3A1">https://www.figma.com/file/NhmkJF2ZfFxzMT0fcT7iWr/느린-우체통?node-id=0%3A1</a></li>
</ul>
<h3 id="🍎-ios-앱-스케치-tip">🍎 iOS 앱 스케치 Tip</h3>
<ul>
<li><p>Frame을 생성할 때 원하는 기기 사이즈를 Frame으로 생성합니다.
<img src="https://images.velog.io/images/sookim-1/post/0585a506-559a-48f9-b372-55f2152e02df/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-15%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%209.50.36.png" alt=""></p>
</li>
<li><p>iOS에서 사용하는 UIKit요소등을 미리 정의해 놓은 프로젝트등을 참고해서 복사 붙여넣기로 사용하면 편리하게 구현이 가능합니다.</p>
</li>
<li><p>🚨  만약 미리 구현해놓은 프로젝트에서 복사 붙여넣기로 하는 경우 텍스트가 변경이 안된다면 폰트를 변경해서 사용하면 텍스트 변경이 가능합니다.
<img src="https://images.velog.io/images/sookim-1/post/5a57db4c-bca7-45a2-aa7e-bbd2268c3ada/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-15%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%209.54.03.png" alt=""></p>
</li>
<li><p>참고사이트 목록</p>
<ul>
<li><a href="https://www.figma.com/file/x8rFkJEzePrXUjohv2kmOj/Figma-iOS-UI-Kit-(Free)-(Community)?node-id=635%3A695">https://www.figma.com/file/x8rFkJEzePrXUjohv2kmOj/Figma-iOS-UI-Kit-(Free)-(Community)?node-id=635%3A695</a></li>
<li><a href="https://www.figma.com/file/19a0WzKqvZtwVhL9LdyZK7/iOS%2FiPadOS-13-Design-UI-Kit-(Community)?node-id=29%3A2890">https://www.figma.com/file/19a0WzKqvZtwVhL9LdyZK7/iOS%2FiPadOS-13-Design-UI-Kit-(Community)?node-id=29%3A2890</a></li>
</ul>
</li>
</ul>
<h2 id="💡-ui스케치-tip">💡 UI스케치 Tip</h2>
<ul>
<li><p>UI적으로 어떻게 화면에 보여야 좋을지에 대해서도 생각합니다. (사용자의 편리성, 이해가 유용할 지?)</p>
</li>
<li><p>직접 스케치 해서 화면간의 관계를 생각해봅니다.</p>
<p>예시</p>
<ul>
<li>테이블 뷰에서 셀들을 사이즈가 큰 형식으로 팔로워목록을 나타내면 팔로워수가 많아질 때 알아보기 힘들기 때문에 적당한 크기로 만들어야합니다.</li>
<li>화면들간의 문맥이 연결되어 있는 부분과 연결되어 있지 않는 부분들을 어떻게 분리할지에 대해서 생각해야합니다. (네비게이션 컨트롤러)</li>
</ul>
</li>
<li><p>API 응답으로 받은 정보를 바탕으로 화면을 작성합니다.</p>
</li>
<li><p><a href="https://developer.apple.com/design/resources/">Apple Design Resource 링크</a></p>
</li>
<li><p>Goodnotes 5와 Sketch는 디자인을 할 때 유용한 툴</p>
<ul>
<li>[Sketch.com](</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 프로젝트 요약서 ]]></title>
            <link>https://velog.io/@sookim-1/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9A%94%EC%95%BD%EC%84%9C</link>
            <guid>https://velog.io/@sookim-1/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9A%94%EC%95%BD%EC%84%9C</guid>
            <pubDate>Mon, 15 Nov 2021 12:46:16 GMT</pubDate>
            <description><![CDATA[<h1 id="📃-프로젝트요약서">📃 프로젝트요약서</h1>
<h2 id="🌟-앱-기능">🌟 앱 기능:</h2>
<ul>
<li>사용자가 최초 앱을 실행할 경우, 팝업 화면(walkthrough)을 페이지뷰로 띄어줍니다.<ul>
<li>이 화면은 최초 1회만 뜨고, 이후에는 뜨지 않습니다.</li>
<li>이 화면에는 간단한 앱에 대한 설명등을 표시합니다.</li>
</ul>
</li>
<li>사용자는 로그인 기능을 통해 편지 내용 및 개인정보등을 저장할 수 있습니다.<ul>
<li>중복된 아이디는 DB에서 검색하여 확인합니다.</li>
<li>소셜로그인  - Ver2.0에 추가</li>
<li>이메일 및 전화 인증기능 - Ver2.0에 추가</li>
</ul>
</li>
<li>사용자는 이메일을 통해 임시비밀번호를 전송해 비밀번호를 변경할 수 있습니다.</li>
<li>알림기능을 통해 우편함에 우편이 도착했을 때 알림을 전송합니다.</li>
<li>날씨 API를 통해 편지를 작성하는 날짜에 대한 날씨정보를 같이 전송할 수 있습니다.</li>
<li>사용자는 편지를 작성하는 위치를 같이 전송할 수 있습니다.</li>
<li>사용자는 메일이 전송이 완료된 후에는 확인은 가능하지만 수정/삭제가 불가능합니다. - 1년뒤에 전송</li>
<li>사용자는 보낸이에게 편지를 전송합니다.<ul>
<li>앱을 삭제한 경우에 MessageUI, Swift-SMTP를 활용하여 이메일주소로 전송합니다.</li>
</ul>
</li>
<li>사용자는 편지 전송횟수가 계정마다 3번씩 제한이 있습니다.<ul>
<li>전송버튼을 클릭한 경우 매번 남은 횟수를 경고창을 통해 사용자에게 알려줍니다. 남은 횟수가 0이라면 홈화면으로 전환 됩니다.</li>
<li>전송횟수 추가 관련 - 추후 예정</li>
</ul>
</li>
<li>사이드메뉴 기능을 통해 프로필, 설정, 홈 이동 , 편지함 (받은 편지함, 보낸 편지함) 화면 전환이 가능합니다.</li>
<li>편지지 배경, 폰트, 텍스트 색상, 텍스트크기 지정은 컬렉션뷰로 표시합니다.</li>
</ul>
<h2 id="🌳-git-규칙">🌳 Git 규칙:</h2>
<ul>
<li>main브런치는 프로젝트 배포 시점에 메인으로 머지</li>
<li>각각의 버전별 개발은 ver[버전]-test 브런치에서 진행 후 테스트가 통과하거나 이슈문제가 없다면 ver[버전]으로 머지하는 방식으로 개발</li>
<li>커밋은 issue단위로 나누어 확인하기<ul>
<li>커밋메시지는 깃모지와 커밋 종류를 사용하여 작성</li>
</ul>
</li>
</ul>
<h2 id="🚨-요구사항">🚨 요구사항:</h2>
<ul>
<li>아이폰용으로 앱 제작, 최소 버전 iOS13.0이상 지원</li>
<li>스토리보드 UI 구현</li>
</ul>
<hr>
<h1 id="💡-기능요구서-tip">💡 기능요구서 Tip</h1>
<ul>
<li><p>프로젝트를 시작하는 경우 기능요구서를 작성하거나 주어진다면 읽고 무슨 기술을 사용할지 간략하게 생각해봅니다.</p>
</li>
<li><p>기능요구는 목적이 변경되면 수정 될 수도 있습니다. (작은 단위 → 큰 단위)</p>
</li>
<li><p>기능요구서에 의문이 가는 부분은 질문을 하는 것이 좋습니다.</p>
</li>
<li><p>JSON을 파싱하는 경우거나 API를 활용할 경우 직접 API문서를 본 후 디자인을 작성합니다.</p>
<ul>
<li>지금 프로젝트같은경우 깃헙API(따로 인증받아지 않아도되서 인증키 필요없음)를 활용하므로 사용자정보를 어떤 식으로 받아오는 지 확인합니다.</li>
</ul>
</li>
<li><p>API를 사용하는 경우 어떻게 사용할 지에 대해서 자세히 알아봅니다</p>
<p>→ 통신을 확인하기 위해서 <a href="https://www.postman.com/">postman</a> 앱을 사용하거나,</p>
<p>터미널에서 curl 명령어를 사용하여 dummy값을 확인할 수 있다. (미리 확인가능)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] (WWDC) What's New in UICollectionView in iOS 10 정리]]></title>
            <link>https://velog.io/@sookim-1/iOS-Whats-New-in-UICollectionView-in-iOS-10-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sookim-1/iOS-Whats-New-in-UICollectionView-in-iOS-10-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 27 Aug 2021 14:28:08 GMT</pubDate>
            <description><![CDATA[<p>UICollectionView는 앱을 관리하고 뷰의 레이아웃을 원하는대로 만들 수 있는 강력한 클래스입니다.</p>
<p>UICollectionView가 iOS10에서 달라진 점에 대해서 설명하는 세션입니다.</p>
<p>크게 세가지 부분으로 나누어 설명합니다.</p>
<ol>
<li><p>Smooth Scrolling</p>
<p> 스크롤을 하는 경우 부드럽게 나타내기 위해서는 프레임삭제,손실(drop frame)을 하면 안됩니다.</p>
<p> iOS앱이 부드럽게 나타내려면 초당 60프레임으로 실행되는 앱 애니메이션을 위해 노력해야 합니다 .</p>
<p> 즉, 애니메이션이 &quot;부드럽게&quot; 나타나려면 사용자 인터페이스의 주어진 프레임이 16.67ms 미만으로 표시되어야 합니다. (16.67ms * 60 = 1second)</p>
<p> 16.67ms 프레임속도가 넘어가면 프레임손실이 나타나게 됩니다.</p>
<p> 이러한 방법을 해결하기위해 로딩을 줄이고 로딩되지 않는 셀을 사용합니다.</p>
<ul>
<li><p>UICollectionView에서 셀의 생명주기</p>
<ol>
<li>prepareForReuse</li>
<li>cellForItemAtIndexPath - 모델에서 셀 데이터를 채우는 작업을 많이 하는 부분</li>
<li>willDisplayCell - 셀이 화면에 표시되려고 할 때 가벼운 준비</li>
<li>셀이 화면에 나타나고 스크롤이 계속되면서 화면 밖으로 이동하기 시작합니다.</li>
<li>didEndDisplayingCell</li>
</ol>
<p>iOS 9이하에서는 10과 차이점은 willDisplayCell의 호출시점이 10에서는 실제로 뷰에 표시되기전까지 호출되지 않도록 변경되고 didEndDisplayingCell메서드가 호출된 후 cell은 즉시 재사용큐에 반환되지 않고 약간 유지되도록 변경되었습니다.</p>
<p>이러한 변경사항으로 실제로 필요하기전에 그리지 않으므로 병목현상을 피할 수 있게 되었습니다.</p>
<p>Cell Pre-fetching</p>
<p>UICollectionView가 기본적으로 셀을 미리 가져오도록 개선했습니다.</p>
<p>pre-fetching API</p>
</li>
</ul>
</li>
<li><p>Self-Sizing Cells</p>
<p> iOS 10이전에는 UICollectionViewFlowLayout에 아이템들의 사이즈를 제공하기위해 estimatedItemSize이 존재했습니다.</p>
<p> 하지만 아이템들의 사이즈를 예측하기가 어렵기 때문에 UICollectionViewFlowLayoutAutomaticSize을 제공합니다. 아이템의 사이즈를 과거의 값을 활용하여 자동으로 사이즈를 예측합니다.</p>
</li>
<li><p>Interactive Reordering</p>
<p> 페이징으로 재정렬하는 기능이 추가되었습니다.</p>
<pre><code class="language-swift"> collectionView.isPagingEnabled = true</code></pre>
</li>
</ol>
<blockquote>
<p>UIRefreshControl</p>
</blockquote>
<p>테이블뷰나 컬렉션뷰 상단으로 드래그할 때 indicator생기면 refresh되는 기능</p>
<h3 id="참고링크">참고링크</h3>
<ul>
<li><a href="https://velog.io/@hanseop95/%EB%B2%88%EC%97%AD-UICollectionView-Tutorial-Prefetching-APIs">[번역] UICollectionView Tutorial: Prefetching APIs</a></li>
<li><a href="https://brunch.co.kr/@tilltue/28">UICollectionView prefetch</a></li>
<li><a href="http://cleanswifter.com/ios-10-uicollectionview-highlights/">What&#39;s New in UICollectionView in iOS 10 해외정리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] CollectionView - H.I.G 정리]]></title>
            <link>https://velog.io/@sookim-1/iOS-CollectionView-H.I.G-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sookim-1/iOS-CollectionView-H.I.G-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 24 Aug 2021 14:26:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="collectionview---hig-설명">CollectionView - H.I.G 설명</h1>
</blockquote>
<p><img src="https://images.velog.io/images/sookim-1/post/a613f5a6-00a3-4557-afc6-fd75af97cb33/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-08-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.24.29.png" alt=""></p>
<p>컬렉션은 사진과 같은 컨텐츠의 집합을 관리하고 레이아웃을 통해 표현합니다. 또한 직선형식뿐만 아니라 다양한 사이즈로 아이템들을 보여줄 수 있습니다. 일반적으로 컬렉션은 이미지기반컨텐츠들을 보여줄 때 가장 적합합니다.</p>
<p>컬렉션은 상호작용(interactivity)과 애니메이션(animation)을 모두 지원합니다. </p>
<p>(예시: tap to select, touch and hold to edit, and swipe to scroll, 아이템 추가, 삭제, 정렬할 때 발생하는 animation, custom animation 등등)</p>
<ol>
<li>row형식이나 gridLayout으로 충분하다면 과하게 새로운디자인을 만드는 것은 피하는 것을 권장합니다.<ul>
<li>컨텐츠를 쉽게 선택할 수 있도록 하는 것이 중요합니다.</li>
<li>관심의 중심이 되는 것이 아닌 사용자경험(UX)를 향상시키기 위한 것입니다.</li>
</ul>
</li>
<li>컨텐츠들이 텍스트기반이라면 컬렉션 대신에 테이블을 사용하는 것을 고려해봅니다.</li>
<li>동적으로(급격하게) 레이아웃을 변경하는 경우 경고를 사용합니다.<ul>
<li>레이아웃을 동적으로 변경하는 경우 변경사항이 의미가 있고 추적하기 쉬운지 확입합니다.</li>
<li>즉, 사용자 입장에서 예측 가능하도록 설계합니다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] Xcode의 타겟 iOS13미만 버전 지원하기]]></title>
            <link>https://velog.io/@sookim-1/iOS-Xcode%EC%9D%98-%ED%83%80%EA%B2%9F-iOS13%EB%AF%B8%EB%A7%8C-%EB%B2%84%EC%A0%84-%EC%A7%80%EC%9B%90%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sookim-1/iOS-Xcode%EC%9D%98-%ED%83%80%EA%B2%9F-iOS13%EB%AF%B8%EB%A7%8C-%EB%B2%84%EC%A0%84-%EC%A7%80%EC%9B%90%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 24 Aug 2021 14:20:59 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 </p>
<p>Xcode에서 프로젝트를 새로 생성한 후 DeploymentTarget을 iOS13이하로 설정한다면 iOS12까지는 대부분 하나의 앱에 하나의 window였지만 iOS 13부터는 window의 개념이 scene으로 대체되면서 scene을 사용하기 때문에 따로 설정을 해줘야합니다. 이러한 설정을 어떻게 해야 하는지에 대해서 알아보겠습니다.</p>
<blockquote>
<h3 id="ios-deployment-target이란">iOS Deployment Target이란?</h3>
</blockquote>
<p>지원하는 최소 OS지원 버전이라고 생각할 수 있습니다.
DepolymentTarget은 PROJECT와 TARGETS 각각 설정이 가능합니다.
TARGETS가 PROJECT를 OVERRIDE를 하고 두 버전이 다르다면 TARGETS를 기준으로 최소버전이 설정됩니다.</p>
<blockquote>
<h3 id="첫번째-방법--scene관련부분들을-모두-삭제하는-방법">첫번째 방법 : Scene관련부분들을 모두 삭제하는 방법</h3>
</blockquote>
<ol>
<li>SceneDelegate.swift 파일을 삭제합니다.</li>
<li>AppDelegate의 UISceneSession Lifecycle관련 함수를 삭제합니다.</li>
<li>AppDelegate의 아래 코드를 추가합니다.<pre><code class="language-swift">var window: UIWindow?</code></pre>
</li>
<li>info.plist의 ApplicationSceneManifest항목을 삭제합니다.</li>
</ol>
<blockquote>
<h3 id="두번째-방법--ios13미만과-이상인-경우-분기처리하는-방법">두번째 방법 : iOS13미만과 이상인 경우 분기처리하는 방법</h3>
</blockquote>
<p><strong>@available</strong></p>
<p>프로젝트 첫 생성시 Xcode는 앱의 최소 버전을 13버전 이상으로 셋팅합니다. </p>
<p>최소 버전을 12 이하로 설정하려면 몇가지를 수정해줘야 합니다.</p>
<ol>
<li><p><strong>SceneDelegate.swift</strong>
SceneDelegate는 iOS 13 이후 버전에 서 필요하므로 availability attributes인 @available을 SceneDelegate 클래스에 추가해줍니다.</p>
<pre><code class="language-swift">    @available(iOS 13.0, *)
    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      ...
    }</code></pre>
</li>
<li><p><strong>AppDelegate.swift</strong>
AppDelegate에 선언되어 있는 메서드 중 두개의 메서든 SceneSession을 관리하는 메서드이므로 마찬가지로 각각 @available 를 추가해줍니다.</p>
<pre><code class="language-swift">    // MARK: UISceneSession Lifecycle
    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -&gt; UISceneConfiguration {
      ...
    }

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set&lt;UISceneSession&gt;) {
      ...
    }</code></pre>
</li>
<li><p><strong>UIWindow</strong>
마지막으로 SceneDelegate에서 Scene을 관리하기 위하여 선언해주었던 UIWindow 변수를 Window 관리를 위하여 AppDelegate에 선언해줍니다.</p>
<pre><code class="language-swift">    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
    }</code></pre>
</li>
</ol>
<blockquote>
<h3 id="참고링크">참고링크</h3>
</blockquote>
<ul>
<li><a href="https://github.com/PayPong/iOS-Public-Documents-Usage-App/issues/1">https://github.com/PayPong/iOS-Public-Documents-Usage-App/issues/1</a></li>
<li><a href="https://appleceo.github.io/2019/10/25/xcode11BuildLesstheniOS13/">https://appleceo.github.io/2019/10/25/xcode11BuildLesstheniOS13/</a></li>
<li><a href="https://hoonstyle.tistory.com/36">https://hoonstyle.tistory.com/36</a></li>
<li><a href="https://adervise1.tistory.com/104">https://adervise1.tistory.com/104</a></li>
<li><a href="https://velog.io/@dev-lena/iOS-AppDelegate%EC%99%80-SceneDelegate">https://velog.io/@dev-lena/iOS-AppDelegate%EC%99%80-SceneDelegate</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] .gitignore파일생성]]></title>
            <link>https://velog.io/@sookim-1/iOS-.gitignore%ED%8C%8C%EC%9D%BC%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@sookim-1/iOS-.gitignore%ED%8C%8C%EC%9D%BC%EC%83%9D%EC%84%B1</guid>
            <pubDate>Tue, 24 Aug 2021 14:02:58 GMT</pubDate>
            <description><![CDATA[<p>깃 레포지토리에 push를 하기전에 불필요한 파일들이 업로드 되는 것을 방지하기 위해 gitignore파일을 생성하여 관리할 수 있습니다. </p>
<blockquote>
<p>gitignore 키워드</p>
</blockquote>
<ul>
<li><p><a href="https://www.toptal.com/developers/gitignore">https://www.toptal.com/developers/gitignore</a></p>
<ul>
<li>위의 사이트 접속하여 swift, xcode, cocoapods, macos 키워드 입력후 파일 생성 (코코아팟을 사용할 계획이 없다면 cocoapods제외)</li>
<li>.gitignore 파일명 입력후 내용 저장</li>
</ul>
</li>
<li><p>참고사이트</p>
<ul>
<li><a href="https://silver-g-0114.tistory.com/32">https://silver-g-0114.tistory.com/32</a></li>
<li><a href="https://mseagle.tistory.com/m/61?category=509474">https://mseagle.tistory.com/m/61?category=509474</a> - 이미 올린 파일 원격에서 삭제하기</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[36일차 - 21.07.13]]></title>
            <link>https://velog.io/@sookim-1/36%EC%9D%BC%EC%B0%A8-21.07.13</link>
            <guid>https://velog.io/@sookim-1/36%EC%9D%BC%EC%B0%A8-21.07.13</guid>
            <pubDate>Tue, 13 Jul 2021 13:28:09 GMT</pubDate>
            <description><![CDATA[<h1 id="학습키워드">학습키워드</h1>
<ul>
<li>text alignment</li>
<li>layout margins</li>
<li>UIFont</li>
</ul>
<h2 id="1-setting-up">1. Setting up</h2>
<ul>
<li>프로젝트 개요<ul>
<li>스토리보드를 사용하지 않고 코드로 UI 작성</li>
<li>버튼으로 된 단어들을 선택하여 합쳐서 의미있는 단어를 만들기 게임</li>
<li>iPad에서만 작동</li>
<li>SpriteKit 프레임워크</li>
</ul>
</li>
</ul>
<h2 id="2-building-a-uikit-user-interface-programmatically">2. Building a UIKit user interface programmatically</h2>
<p><img src="https://images.velog.io/images/sookim-1/post/0bce2cd5-db8e-4d7a-b1ac-667956827f73/22.png" alt=""></p>
<ul>
<li>clues: 힌트가 들어가 레이블</li>
<li>answers: 정답인 단어의 철자 수</li>
<li>Score: 점수표시 레이블</li>
<li>Tap letters to guess : 텍스트필드 사용자의 현재 답변</li>
<li>submit: 제출버튼</li>
<li>clear: 초기화버튼</li>
<li>WWW: 여러 단어가 들어갈 버튼</li>
</ul>
<blockquote>
<p>코드로 UI작성 (스토리보드 제거는 하지않습니다.)</p>
</blockquote>
<pre><code class="language-swift">//ViewController.swift

var cluesLabel: UILabel!

override func loadView() {
    view = UIView()
    view.backgroundColor = .white
}</code></pre>
<ol>
<li><p>뷰가 로드되기 전 뷰컨트롤러의 view속성에 할당합니다. (UIView의 새 인스턴스를 만들고 흰색 배경색을 지정합니다.)</p>
</li>
<li><p>레이블의 속성을 설정합니다.</p>
<pre><code class="language-swift"> scoreLabel = UILabel()
 scoreLabel.translatesAutoresizingMaskIntoConstraints = false
 scoreLabel.textAlignment = .right
 scoreLabel.text = &quot;Score: 0&quot;
 scoreLabel.font = UIFont.systemFont(ofSize: 24)
 scoreLabel.numberOfLines = 0
 view.addSubview(scoreLabel)</code></pre>
<ul>
<li>레이블 인스턴스를 생성한 후 오토레이아웃 설정을 할 수 있도록 <code>translatesAutoresizingMaskIntoConstraints</code> 를 false로 지정합니다.</li>
<li><code>textAlignment</code> 텍스트정렬의 위치를 지정합니다.</li>
<li><code>addSubview()</code> : 메서드를 호출하여 뷰에 추가합니다.</li>
<li><code>numberOfLines</code> : 텍스트가 줄바꿈할 수 있는 줄 수 (0이라면 필요한 만큼 줄바꿈)</li>
<li><code>font</code> : 시스템폰트는 현재 iOS 사용중인 글꼴을 사용합니다.</li>
</ul>
</li>
<li><p>제약조건 설정하기</p>
<p> 매번 제약조건에 <code>isActive = true</code> 를 여러번 사용해야 하는 경우 <code>NSLayoutConstraint.activate()</code> 제약 조건을 배열안에 삽입하면 편리하게 설정할 수 있습니다.</p>
<p> UIKit은 여러가이드를 제공합니다.</p>
<p> 🌟  <code>safeAreaLayoutGuide</code> : 둥근 모서리나 노치를 뺀 사용가능한 공간</p>
<p> <code>layoutMarginsGuide</code> : 뷰가 화면의 왼쪽 및 오른쪽 가장자리로 실행되지 않도록 여분의 여백(margin)을 추가합니다.</p>
<pre><code class="language-swift"> NSLayoutConstraint.activate([
     scoreLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
     scoreLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor)
 ])</code></pre>
</li>
</ol>
<blockquote>
<p>텍스트필드 속성</p>
</blockquote>
<p><code>placeholder</code> : 사용자의 입력을 대기하는 동안 보이는 텍스트</p>
<p><code>isUserInteractionEnabled</code> : 사용자와 상호작용의 활성여부 ( <code>false</code>라면 탭해서 텍스트입력이 불가능합니다.)</p>
<blockquote>
<p>UIButton 속성</p>
</blockquote>
<pre><code class="language-swift">let submit = UIButton(type: .system)
submit.translatesAutoresizingMaskIntoConstraints = false
submit.setTitle(&quot;SUBMIT&quot;, for: .normal)
view.addSubview(submit)</code></pre>
<ul>
<li>버튼에는 다양한 스타일이 내장되어있습니다. (일반적으로 <code>.custom</code>, <code>.system</code>을 자주 사용합니다.)</li>
<li><code>setTitle()</code> : 버튼의 제목을 작성합니다.</li>
</ul>
<blockquote>
<p>intrinsic content size</p>
</blockquote>
<p>각각의 뷰가 content를 얼마나 크게 보여줘야하는지 크기</p>
<ul>
<li>Content hugging priority : 뷰가 intrinsic content size보다 크게 될 가능성을 결정합니다. 우선순위가 높으면 오토레이아웃을 늘리지 않는 것을 선호한다는 의미입니다.</li>
<li>Content compression resistance priority : 뷰가 intrinsic content size보다 작게만들어지는 것이 좋을 지를 결정합니다.</li>
</ul>
<p>예시)</p>
<pre><code class="language-swift">cluesLabel.setContentHuggingPriority(UILayoutPriority(1), for: .vertical)</code></pre>
<blockquote>
<p>뷰안에 4행 5열로 이루어진 버튼을 삽입하기</p>
</blockquote>
<pre><code class="language-swift">let width = 150
let height = 80

for row in 0..&lt;4 {
    for column in 0..&lt;5 {
        let letterButton = UIButton(type: .system)
        letterButton.titleLabel?.font = UIFont.systemFont(ofSize: 36)
        letterButton.setTitle(&quot;WWW&quot;, for: .normal)

        let frame = CGRect(x: column * width, y: row * height, width: width, height: height)
        letterButton.frame = frame

        buttonsView.addSubview(letterButton)
        letterButtons.append(letterButton)
    }
}</code></pre>
<p>X및 Y좌표에 너비와 높이를 더한 직사각형 프레임을 만든 다음 이를 frame속성에 할당합니다 </p>
<p>이러한 직사각형은 CGRect타입을 갖습니다.(Core Graphics에서 제공)</p>
<h3 id="링크">링크</h3>
<p><a href="https://www.hackingwithswift.com/100/36">100 Days of Swift - Day 36 - Hacking with Swift</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[35일차 - 21.07.12]]></title>
            <link>https://velog.io/@sookim-1/35%EC%9D%BC%EC%B0%A8-21.07.12</link>
            <guid>https://velog.io/@sookim-1/35%EC%9D%BC%EC%B0%A8-21.07.12</guid>
            <pubDate>Mon, 12 Jul 2021 16:09:13 GMT</pubDate>
            <description><![CDATA[<h1 id="학습키워드">학습키워드</h1>
<ul>
<li>review(복습)</li>
</ul>
<h2 id="1-wrap-up">1. Wrap up</h2>
<blockquote>
<p>도전과제</p>
</blockquote>
<ol>
<li><p>UIBarButtonItem을 사용하여 Credit버튼 추가하기 (API에서 가져온 데이터를 사용자에게 알리는 경고창을 표시합니다.)</p>
</li>
<li><p>테이블뷰에 데이터를 필터기능을 추가합니다. 검색했을 때 입력한 문자열과 일치하는 제목만 표시되는 기능</p>
<p> <code>contains()</code> 메서드를 활용하여 문자열내부에 문자열이 포함되어 있는지의 여부를 확인할 수 있습니다.</p>
</li>
<li><p>HTML을 조금 더 다양하게 사용해봅니다.</p>
</li>
</ol>
<h2 id="2-review-for-project-7-whitehouse-petitions">2. Review for Project 7: Whitehouse Petitions</h2>
<ul>
<li><p>탭바컨트롤러 내부에 네비게이션컨트롤러가 위치합니다.</p>
</li>
<li><p>UIStoryboard can load storyboards from our bundle and create view controllers from there.</p>
<p>  → Just provide it the storyboard name as its first parameter and it will do the rest.</p>
</li>
<li><p>AppDelegate는 시스템 알림에 응답 및 처리할 수 있습니다.</p>
</li>
<li><p>Apple은 UITabBarItem일반적인 용도로 몇 가지 기본유형을 제공합니다. → 기본유형을 사용하는 경우 아이콘과 텍스트를 모두 제공하는 것을 사용해야 합니다. 사용자가 텍스트를 정의할 수 없습니다.</p>
</li>
<li><p>스위프트의 Data타입은 모든 종류의 바이너리 데이터를 보유할 수 있습니다. → 이미지, zip 파일, 문자열 또는 원하는 것을 저장하는 데 사용할 수 있습니다.</p>
</li>
<li><p>JSON</p>
<ul>
<li>JSON은 데이터를 저장하고 보내는 간단한 방법입니다.<strong>JSON(JavaScript Object Notation)은 이러한 목적을 위해 여러 언어에서 일반적으로 사용됩니다.</strong></li>
<li>JSON 속성과 Codable객체의 속성 모두 이름이 같아야 합니다.</li>
<li>JSON은 인터넷 사용 여부에 관계없이 모든 종류의 애플리케이션에서 사용할 수 있습니다.</li>
</ul>
</li>
</ul>
<h3 id="링크">링크</h3>
<p><a href="https://www.hackingwithswift.com/100/35">100 Days of Swift - Day 35 - Hacking with Swift</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[34일차 - 21.07.11]]></title>
            <link>https://velog.io/@sookim-1/34%EC%9D%BC%EC%B0%A8-21.07.11</link>
            <guid>https://velog.io/@sookim-1/34%EC%9D%BC%EC%B0%A8-21.07.11</guid>
            <pubDate>Mon, 12 Jul 2021 14:16:59 GMT</pubDate>
            <description><![CDATA[<h1 id="학습키워드">학습키워드</h1>
<ul>
<li>injecting HTML into a web view</li>
<li>UIStoryboard</li>
<li>adding tabs to a tab bar controller in code</li>
</ul>
<h2 id="1-rendering-a-petition-loadhtmlstring">1. Rendering a petition: loadHTMLString</h2>
<p>스위프트에서 HTML을 사용하여 WKWebView에 삽입할 수 있습니다.</p>
<pre><code class="language-swift">guard let detailItem = detailItem else { return }

let html = &quot;&quot;&quot;
&lt;html&gt;
&lt;head&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;style&gt; body { font-size: 150%; } &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
\(detailItem.body)
&lt;/body&gt;
&lt;/html&gt;
&quot;&quot;&quot;

webView.loadHTMLString(html, baseURL: nil)</code></pre>
<ul>
<li>HTML은 모바일장치 사이즈로 설정합니다.</li>
<li>글꼴크기는 표준 크기의 150%로 설정합니다.</li>
<li><body></body> 태그에 본문의 내용을 작성합니다.</li>
<li><code>func loadHTMLString(_ string: String, baseURL: URL?) -&gt; WKNavigation?</code> : 지정된 HTML 문자열의 내용을 로드하고 탐색합니다</li>
<li><a href="https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring">loadHTMLString(_:baseURL:) 공식문서</a></li>
</ul>
<h2 id="2-finishing-touches-didfinishlaunchingwithoptions">2. Finishing touches: didFinishLaunchingWithOptions</h2>
<blockquote>
<p>탭바컨트롤러에 탭 추가</p>
</blockquote>
<p>두번 째 탭을 스토리보드에 삽입할 수 있지만 유지관리를 하는데 좋지 않습니다. 왜냐하면 그렇게 하려면 스토리보드에서 뷰컨트롤러를 복제해야 하기 때문입니다.</p>
<p><code>AppDelegate</code>의  <code>didFinishLaunchingWithOptions</code> 의 메서드는 앱이 로드를 완료하고 사용할 준비가 되었을 때 iOS에 의해 호출됩니다.</p>
<pre><code class="language-swift">var window: UIWindow?

if let tabBarController = window?.rootViewController as? UITabBarController {
    let storyboard = UIStoryboard(name: &quot;Main&quot;, bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: &quot;NavController&quot;)
    vc.tabBarItem = UITabBarItem(tabBarSystemItem: .topRated, tag: 1)
    tabBarController.viewControllers?.append(vc)
}</code></pre>
<ul>
<li>window는 초기에 보여줄 루트뷰컨트롤러를 설정해야 합니다.</li>
<li><code>UIStoryboard</code> 클래스를 사용하여 Main.storyboard파일을 참조합니다. 번들이 <code>nil</code> 이라는 의미는 현재 앱번들을 사용한다는 의미입니다.</li>
<li>참조하여 새로운 뷰컨트롤러를 만든후 탭바항목을 추가합니다.</li>
<li><code>tag</code> 를 사용하여 탭바를 구분할 수 있습니다.</li>
<li>탭바컨트롤러의 <code>viewControllers</code> 배열에 새로운 뷰컨트롤러를 추가하면 탭표시줄에 표시됩니다.</li>
</ul>
<blockquote>
<p>iOS Depolyment Target을 13이하로 개발하는 경우</p>
</blockquote>
<p><a href="https://yagom.net/forums/topic/scenedelegate-%EC%A0%9C%EA%B1%B0-%EB%B0%A9%EB%B2%95/">야곰닷넷 참고링크</a></p>
<ul>
<li>SceneDelegate 제거하는 방법<ol>
<li>SceneDelegate.swift 파일제거</li>
<li>AppDelegate에서 Scene과 관련된 메서드 제거</li>
<li>AppDelegate에 window 추가
<img src="https://images.velog.io/images/sookim-1/post/a1a755ff-52ab-464c-8234-727cc9fc4b69/1.png" alt=""></li>
<li>info.plist에서 Scene Manifest제거
<img src="https://images.velog.io/images/sookim-1/post/c8b54eee-4a73-4547-a1b4-5f004c4f3fc7/2.png" alt=""></li>
</ol>
</li>
</ul>
<ul>
<li><p>@available을 사용하여 타겟 버전 낮추는 방법</p>
<ul>
<li><p><a href="https://appleceo.github.io/2019/10/25/xcode11BuildLesstheniOS13/">xcode11 에서 iOS13 미만의 타겟을 빌드하는 방법</a></p>
<pre><code class="language-swift">// SceneDelegate.swift
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
...
}

// AppDelegate.swift
var window: UIWindow?

@available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -&gt; UISceneConfiguration {
...
}

@available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set&lt;UISceneSession&gt;) {
...
}</code></pre>
</li>
</ul>
</li>
</ul>
<p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag">Using the viewport meta tag to control layout on mobile browsers</a> - 웹 페이지를 작은 화면에 맞추기 위한 방법</p>
<h3 id="링크">링크</h3>
<p><a href="https://www.hackingwithswift.com/100/34">100 Days of Swift - Day 34 - Hacking with Swift</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[33일차 - 21.07.10]]></title>
            <link>https://velog.io/@sookim-1/33%EC%9D%BC%EC%B0%A8-21.07.10</link>
            <guid>https://velog.io/@sookim-1/33%EC%9D%BC%EC%B0%A8-21.07.10</guid>
            <pubDate>Mon, 12 Jul 2021 00:40:14 GMT</pubDate>
            <description><![CDATA[<h1 id="학습키워드">학습키워드</h1>
<ul>
<li>UITabBarController</li>
<li>Data</li>
<li>Codable</li>
</ul>
<h2 id="1-setting-up">1. Setting up</h2>
<ul>
<li>프로젝트 개요<ul>
<li>인터넷에서 데이터를 가져와 사용자에게 표시하는 프로젝트</li>
<li><code>UITabBarController</code> 를 사용하여 옵션을 선택합니다.</li>
</ul>
</li>
</ul>
<h2 id="2-creating-the-basic-ui-uitabbarcontroller">2. Creating the basic UI: UITabBarController</h2>
<p><code>tab bar</code> 를 사용하면 사용자가 관심있는 항목을 탭하여 보고 싶은 화면을 제어할 수 있습니다.</p>
<ul>
<li>Editor &gt; Embed In &gt; Tab Bar Controller - 탭바컨트롤러 래핑</li>
<li>Editor &gt; Embed In &gt; Navigation Controller - 네비게이션컨트롤러 래핑</li>
</ul>
<p><code>UITabBarItem</code> </p>
<ul>
<li>시스템 항목 설정할 때 SystemItem을 커스텀속성 및 여러가지 속성을 제공합니다.</li>
<li>텍스트를 직접 설정하는 경우 아이콘이 제거되고 직접 제공해야 합니다.</li>
</ul>
<p>Cell스타일이 <code>Subtitle</code> 인경우 <code>textLabel.text</code> 와 <code>detailTextLabel.text</code> 로 텍스트를 설정할 수 있습니다.</p>
<h2 id="3-parsing-json-using-the-codable-protocol">3. Parsing JSON using the Codable protocol</h2>
<p><code>Codable</code> 프로토콜을 채택하면 문자열, 딕셔너리 또는 구조체와 같은 스위프트 데이터를 인터넷을 통해 전송할 수 있는 데이터로 변환할 수 있습니다.</p>
<p>데이터가 <code>Codable</code> 을 준수한다고 하면 해당 데이터와 JSON간에 자유롭게 변환할 수 있습니다,</p>
<p><code>JSON</code> (<code>JavaScript Object Notation</code>) 데이터를 설명하는 방법입니다. 컴퓨터용으로 분석하기 쉽기 때문에 대역폭이 중요한 온라인에서 자주 사용됩니다.</p>
<blockquote>
<p>JSON 모델 작성</p>
</blockquote>
<pre><code class="language-swift">{
    &quot;metadata&quot;:{
        &quot;responseInfo&quot;:{
            &quot;status&quot;:200,
            &quot;developerMessage&quot;:&quot;OK&quot;,
        }
    },
    &quot;results&quot;:[
        {
            &quot;title&quot;:&quot;Legal immigrants should get freedom before undocumented immigrants – moral, just and fair&quot;,
            &quot;body&quot;:&quot;I am petitioning President Trump&#39;s Administration to take a humane view of the plight of legal immigrants. Specifically, legal immigrants in Employment Based (EB) category. I believe, such immigrants were short changed in the recently announced reforms via Executive Action (EA), which was otherwise long due and a welcome announcement.&quot;,
            &quot;issues&quot;:[
                {
                    &quot;id&quot;:&quot;28&quot;,
                    &quot;name&quot;:&quot;Human Rights&quot;
                },
                {
                    &quot;id&quot;:&quot;29&quot;,
                    &quot;name&quot;:&quot;Immigration&quot;
                }
            ],
            &quot;signatureThreshold&quot;:100000,
            &quot;signatureCount&quot;:267,
            &quot;signaturesNeeded&quot;:99733,
        },
        {
            &quot;title&quot;:&quot;National database for police shootings.&quot;,
            &quot;body&quot;:&quot;There is no reliable national data on how many people are shot by police officers each year. In signing this petition, I am urging the President to bring an end to this absence of visibility by creating a federally controlled, publicly accessible database of officer-involved shootings.&quot;,
            &quot;issues&quot;:[
                {
                    &quot;id&quot;:&quot;28&quot;,
                    &quot;name&quot;:&quot;Human Rights&quot;
                }
            ],
            &quot;signatureThreshold&quot;:100000,
            &quot;signatureCount&quot;:17453,
            &quot;signaturesNeeded&quot;:82547,
        }
    ]
}</code></pre>
<ol>
<li>메타데이터 값이 있으며 <code>responseInfo</code> 에는 상태값이 포함됩니다. 200상태코드는 정상적인 상태를 의미합니다.</li>
<li><code>results</code> 배열안에 각각의 항목의 값들이 있습니다.</li>
<li>각 항목에는 제목, 본문, 관련된 문제 및 서명 정보가 포함되어 있습니다.</li>
<li><code>JSON</code> 에서는 정수는 따옴표를 감싸지 않습니다.</li>
</ol>
<p>💡  JSON모델을 구조체로 작성하면 memberwise initializer를 제공하기 때문에 편리합니다.</p>
<blockquote>
<p>데이터 다운로드</p>
</blockquote>
<pre><code class="language-swift">override func viewDidLoad() {
    super.viewDidLoad()

    // let urlString = &quot;https://api.whitehouse.gov/v1/petitions.json?limit=100&quot;
    let urlString = &quot;https://www.hackingwithswift.com/samples/petitions-1.json&quot;

    if let url = URL(string: urlString) {
        if let data = try? Data(contentsOf: url) {
            // we&#39;re OK to parse!
        }
    }
}</code></pre>
<ul>
<li>urlString → 서버 또는 데이터를 가지고 있는 주소에 접근합니다.</li>
<li>url이 유효한 경우 Data객체를 생성합니다.</li>
<li>이 코드는 인터넷에서 데이터를 다운로드하는 동안 메인스레드에서 작동하기 때문에 모든 데이터가 전송될 때까지 다른 작업을 할 수 없습니다.</li>
</ul>
<blockquote>
<p>JSON 파싱</p>
</blockquote>
<pre><code class="language-swift">func parse(json: Data) {
    let decoder = JSONDecoder()

    if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) {
        petitions = jsonPetitions.results
        tableView.reloadData()
    }
}</code></pre>
<ul>
<li>JSONDecoder : JSON과 Codable객체간의 변환부분을 맡습니다.</li>
<li><code>decode()</code> 메서드를 사용하여 변환하도록 요청합니다.</li>
</ul>
<h3 id="링크">링크</h3>
<p><a href="https://www.hackingwithswift.com/100/33">100 Days of Swift - Day 33 - Hacking with Swift</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[32일차 - 21.07.09]]></title>
            <link>https://velog.io/@sookim-1/32%EC%9D%BC%EC%B0%A8-21.07.09</link>
            <guid>https://velog.io/@sookim-1/32%EC%9D%BC%EC%B0%A8-21.07.09</guid>
            <pubDate>Fri, 09 Jul 2021 14:35:57 GMT</pubDate>
            <description><![CDATA[<h1 id="학습키워드">학습키워드</h1>
<ul>
<li>review(복습)</li>
<li>Project 4 ~ 6</li>
</ul>
<h2 id="1-what-you-learned">1. What you learned</h2>
<ul>
<li><code>WebKit</code>의 이벤트가 발생했을 때 <code>Delegation</code>을 사용하여 <code>ViewController</code>에 전송하여 이에 대해 조치를 취할 수 있었습니다.</li>
<li><code>UIAlertController</code>에 <code>ActionSheet</code>스타일로 지정하는 방법도 알아보았습니다.</li>
<li><code>toolbarItems</code>안에 <code>UIBarButtonItems</code>를 배치하여 표시한 후 <code>.flexibleSpace</code>를 사용하여 레이아웃을 보기좋게 설정했습니다.</li>
<li><code>Key-Value-Observing</code> 을 사용하여 웹브라우저에 로딩상황을 표시할 수 있었습니다.</li>
<li><code>contentsOf</code> 를 사용하여 디스크에서 텍스트파일을 로드하는 방법을 알아보았습니다.</li>
<li><code>UIAlertController</code> 에 텍스트필드를 추가하여 읽고 사용할 수 있습니다.</li>
<li>문자열 및 배열조작을 위한 <code>contains(), remove(at:), firstIndex(of:)</code></li>
<li><code>AutoLayout</code> , <code>Visual Format Language</code></li>
</ul>
<h2 id="2-key-points">2. Key points</h2>
<blockquote>
<p>웹뷰 설정</p>
</blockquote>
<pre><code class="language-swift">class ViewController: UIViewController, WKNavigationDelegate {
    var webView: WKWebView!

    override func loadView() {
        webView = WKWebView()
        webView.navigationDelegate = self
        view = webView
    }
}</code></pre>
<ul>
<li>메인 뷰 자체를 웹뷰로 사용하기 위한 경우 <code>viewDidLoad()</code> 가 아닌 <code>loadView()</code> 뷰가 로드가 완료되기전에 정의합니다.</li>
<li><code>view</code> 가 <code>WKWebView</code> 이기 때문 <code>view.reload()</code> 는 사용할 수 없습니다. 왜냐하면 <code>UIView</code> 가 아니기 때문입니다.</li>
</ul>
<blockquote>
<p>if let과 try?</p>
</blockquote>
<pre><code class="language-swift">var allWords = [String]()

if let startWordsURL = Bundle.main.url(forResource: &quot;start&quot;, withExtension: &quot;txt&quot;) {
    if let startWords = try? String(contentsOf: startWordsURL) {
        allWords = startWords.components(separatedBy: &quot;\n&quot;)
    }
}</code></pre>
<p><code>contentsOf</code> 로 디스크에서 텍스트를 로드할 수 있습니다. 위 코드에서 텍스트를 얻을 수 있지만 실패한 경우 <code>throw</code> 로 예외처리를 해야합니다.</p>
<p><code>try?</code> 는 예외처리를 nil반환을 합니다.</p>
<blockquote>
<p>VFL</p>
</blockquote>
<pre><code class="language-swift">view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat:&quot;V:|[label1(labelHeight)]-[label2(labelHeight)]-[label3(labelHeight)]-[label4(labelHeight)]-[label5(labelHeight)]-&gt;=10-|&quot;, options: [], metrics: metrics, views: viewsDictionary))</code></pre>
<ul>
<li>단 한줄의코드로 제약조건을 설정할 수 있는 장점이 있습니다.</li>
<li>동일한 높이의 5개의 레이블이 있고 각 사이에 공간이 있고 마지막레이블은 부모뷰의 가장자리와 10이상포인트 공간을 가집니다.</li>
</ul>
<h2 id="3-challenge">3. Challenge</h2>
<blockquote>
<p>도전과제</p>
</blockquote>
<p>테이블뷰로 쇼핑목록을 만들 수 있도록 하는 앱</p>
<p>쇼핑목록을 지울 수 있는 왼쪽 UIBarButtonItem 추가 → 테이블뷰 셀단위로 다시 로드</p>
<p><code>joined(separator:_)</code> : 배열에서 각 부분들을 결합하여 하나의 문자열을 생성할 수 있습니다.</p>
<h3 id="링크">링크</h3>
<p><a href="https://www.hackingwithswift.com/100/32">100 Days of Swift - Day 32 - Hacking with Swift</a></p>
]]></description>
        </item>
    </channel>
</rss>