<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[fluffy.es - iOS development tutorials]]></title><description><![CDATA[UIKit, Auto Layout, Swift and more]]></description><link>https://fluffy.es/</link><image><url>https://fluffy.es/favicon.png</url><title>fluffy.es - iOS development tutorials</title><link>https://fluffy.es/</link></image><generator>Ghost 2.25</generator><lastBuildDate>Sun, 19 Apr 2026 18:23:04 GMT</lastBuildDate><atom:link href="https://fluffy.es/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[End of the road, and thanks for reading this blog!]]></title><description><![CDATA[<p>TL;DR: I got demotivated from iOS development, and moved to web development instead, new blog at <a href="https://rubyyagi.com">https://rubyyagi.com</a> .</p><p></p><p>I have been writing on this iOS development blog for 2.5 years (start of 2018 until mid of 2020),  wrote 80+ articles and 52 Xcode tips in email! Thank</p>]]></description><link>https://fluffy.es/goodbye/</link><guid isPermaLink="false">5fb1383d2b012b0b9547f6cf</guid><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sun, 15 Nov 2020 14:17:28 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/11/end.png" medium="image"/><content:encoded><![CDATA[<img src="https://fluffy.es/content/images/2020/11/end.png" alt="End of the road, and thanks for reading this blog!"><p>TL;DR: I got demotivated from iOS development, and moved to web development instead, new blog at <a href="https://rubyyagi.com">https://rubyyagi.com</a> .</p><p></p><p>I have been writing on this iOS development blog for 2.5 years (start of 2018 until mid of 2020),  wrote 80+ articles and 52 Xcode tips in email! Thank you for reading this blog!</p><p></p><p>I started this blog after failing a job interview for an iOS developer position for a popular messaging app company, I was burnt out at previous iOS dev job and was thinking alternative way to make money aside from full time job.</p><p></p><p>I then came across <a href="https://30x500.com">30x500 course</a> by Amy Hoy and Alex Hillman (not affiliated, I just enrolled for their course), which taught about how to create products that people wants, by studying audiences behavior, helping them in public, and creating a product that addresses their pains. I was an iOS developer, so naturally I chose the audience of iOS developers as I might have more insight on the same field.</p><p></p><p>I had a few small iOS apps making coffee money (like few bucks a week, which is just enough to cover annual Apple developer fees) before starting this blog, and I wanted to give it a try on making product for iOS devs (as myself was an iOS dev). So I started writing articles and tutorials for iOS development stuff, helped answer question in iOS Dev Slack, StackOverflow and Reddit occassionally. I put a newsletter box below every post I wrote, and slowly more and more people subscribed to my newsletter, and it grew to 2500+ subscribers at current time of writing!</p><p></p><p>After writing a dozens of articles, I noticed a few common reoccuring pain points appearing in iOS dev subreddit and Slack, which revolve around not understanding how Auto Layout works, how Swift optionals work (the ?, ! was confusing to me at first too) and how to implement Sign in with Apple. I then wrote a book on these topics, and tried to market them to my newsletter subscribers, the sales was OK (wouldn't say its life changing, I still have a job lol).</p><p></p><p>As of today <strong>I have earned a total of $5,452 from all of my ebooks</strong>, in a span of two years since I first started selling my first book on <a href="https://autolayout.fluffy.es">Auto Layout</a>. I have never made that much money from product income in my life, my apps was making a few hundred dollars a year at best, I have learned a ton on technical writing , marketing and most importantly understanding audience's pain (sounds like a cliche, I know). </p><p></p><p>I have also learned a lot more on iOS development by writing articles, my previous job mostly was to print JSON prettily using UIKit and I didn't get much exposure to other framework like CoreLocation, automated testing etc. In order to write about these topics, I had to do research on them and made a lot of demo apps to learn about them before writing article.</p><p></p><p>I have made friends with other iOS/macOS developers on Twitter, gotten offer to guest blog (<a href="https://www.smashingmagazine.com/2019/02/ios-performance-tricks-apps/">Smashing Magazine</a>, <a href="https://blog.bitrise.io/author/axel-kee">Bitrise</a>) , and even gotten a few adhoc paid task from other iOS devs! All these wouldn't have happened without this blog, I am forever grateful for this.</p><p></p><p>Truth to be told, I was working as a Ruby/ Web developer while I was writing these iOS articles, and it's hard to churn out iOS articles regularly when you don't have frequent real life iOS development experiences.</p><p></p><p>I got demotivated slowly and eventually Apple introduced SwiftUI, which gotten a lot of attention of iOS devs. I can feel that people are slowly moving from UIKit to SwiftUI, it was a big shift in programming paradigm and at this point I lost interest on writing about iOS development.</p><p></p><p>I have since decided to switch from writing about iOS development to web development (mainly using Ruby and the framework Ruby on Rails) , if you are interested to follow along, you can check out <a href="https://rubyyagi.com">https://rubyyagi.com</a> , I would not update this blog anymore but I will keep it as is for archival purpose.</p><p></p><p>Thanks for reading and supporting this blog for the past 2 years!</p><p></p><p>Here's my books if you are interested :</p><ol><li><a href="https://autolayout.fluffy.es">Making Sense of Auto Layout</a></li><li><a href="https://optionals.fluffy.es">Understanding Optionals and Delegate</a></li><li><a href="https://siwa.fluffy.es">Practical Sign in with Apple</a></li></ol>]]></content:encoded></item><item><title><![CDATA[Toggle iCloud sync on/off for NSPersistentCloudKitContainer]]></title><description><![CDATA[<h1></h1><p>I wanted an option for user to toggle iCloud sync on / off for my own app <a href="https://apps.apple.com/us/app/id1523508294">AuthCat</a> (2FA OTP app with iCloud sync).</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/81-toggle-icloud-sync/icloudsync.png" class="kg-image" alt="icloud sync toggle"></figure><!--kg-card-end: image--><p>Some users may not feel comfortable sharing their data to cloud, or just prefer to not sync data between devices, so it is important to have an</p>]]></description><link>https://fluffy.es/toggle-icloud-sync-nspersistentcloudkitcontainer/</link><guid isPermaLink="false">5f1ffa6a2b012b0b9547f67f</guid><category><![CDATA[icloud]]></category><category><![CDATA[coredata]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Tue, 28 Jul 2020 10:31:07 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/07/toggle.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/07/toggle.png" alt="Toggle iCloud sync on/off for NSPersistentCloudKitContainer"><p>I wanted an option for user to toggle iCloud sync on / off for my own app <a href="https://apps.apple.com/us/app/id1523508294">AuthCat</a> (2FA OTP app with iCloud sync).</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/81-toggle-icloud-sync/icloudsync.png" class="kg-image" alt="Toggle iCloud sync on/off for NSPersistentCloudKitContainer"></figure><!--kg-card-end: image--><p>Some users may not feel comfortable sharing their data to cloud, or just prefer to not sync data between devices, so it is important to have an option to let them disable it. My app default is iCloud sync off, they have to turn it on manually.</p><p>There's some NSPersistentCloudKitContainer tutorial online, but there isn't much guide on how to implement the sync toggling functionality, so I have decided to write this.</p><p>This tutorial assume that you already have a NSPersistentCloudKitContainer ready in your app. You can check <a href="https://www.andrewcbancroft.com/blog/ios-development/data-persistence/getting-started-with-nspersistentcloudkitcontainer/">Andrew's excellent tutorial</a> on how to implement NSPersistentCloudKitContainer if you haven't already implement it.</p><p></p><p>Table of Contents : </p><ol><li><a href="#boolean">A boolean to store if the sync is on/off</a></li><li><a href="#disable">Disable cloud sync on NSPersistentCloudKitContainer</a></li><li><a href="#reinitialize">Reinitialize the persistent container when iCloud sync is toggled</a></li><li><a href="#delete">Delete existing data from iCloud when iCloud sync is turned off</a></li></ol><p></p><!--kg-card-begin: html--><span id="boolean"></span><!--kg-card-end: html--><h2 id="a-boolean-to-store-if-the-sync-is-on-off">A boolean to store if the sync is on/off</h2><p>First, you will need to have a boolean variable to store whether the user has turned the sync on or off, and this variable must be accessible by other iOS devices owned by the same Apple ID as well.</p><p>To make this boolean variable accessible on another iOS device, we need to store it using <strong>iCloud Key-Value store</strong> (NSUbiquitousKeyValueStore). You can think of it like UserDefaults, but the value is shared across different devices for the same app, that ties to an Apple ID account.</p><p>Enable the iCloud key-value storage capability in your project :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/81-toggle-icloud-sync/enablekeyvalue.png" class="kg-image" alt="Toggle iCloud sync on/off for NSPersistentCloudKitContainer"></figure><!--kg-card-end: image--><p></p><p>Then in the UISwitch's IBAction, update the boolean value when user toggle the switch :</p><!--kg-card-begin: code--><pre><code class="language-swift">@IBAction func iCloudToggled(_ sender: UISwitch) {
    // set the icloud_sync key to be true/false depending on the UISwitch state
    NSUbiquitousKeyValueStore.default.set(sender.isOn, forKey: "icloud_sync")
}
</code></pre><!--kg-card-end: code--><p></p><p>Similar to UserDefaults, if the value for the key is not set previously, it will return false by default.</p><p>I have used the key name "icloud_sync" here, you can use other key name if you want to.</p><!--kg-card-begin: html--><span id="disable"></span><!--kg-card-end: html--><h2 id="disable-cloud-sync-on-nspersistentcloudkitcontainer">Disable cloud sync on NSPersistentCloudKitContainer</h2><p>When you initialize a NSPersistentCloudKitContainer stack, by default it will sync to an iCloud container (the remote database), with the container identifier you specified in the capabilities section :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/81-toggle-icloud-sync/containers.png" class="kg-image" alt="Toggle iCloud sync on/off for NSPersistentCloudKitContainer"></figure><!--kg-card-end: image--><p></p><p>If you print out the default value of the container identifier like this, you will get the same identifier shown in the capabilities section :</p><!--kg-card-begin: code--><pre><code class="language-swift">// your CoreData stack in AppDelegate.swift

lazy var persistentContainer: NSPersistentCloudKitContainer = {
  
  let container = NSPersistentCloudKitContainer(name: "AppName")

  guard let description = container.persistentStoreDescriptions.first else {
      fatalError("###\(#function): Failed to retrieve a persistent store description.")
  }
  
  // this will output "iCloud.es.fluffy.AuthCat"
  print("cloudkit container identifier : \(description.cloudKitContainerOptions?.containerIdentifier)")
  
  // ....
}
</code></pre><!--kg-card-end: code--><p></p><p>To disable iCloud syncing, we can <strong>set the cloudKitContainerOptions property to nil</strong>. By setting it to nil, the NSPersistentCloudKitContainer will not connect to any cloud container, hence no sync will occur.</p><p>We can then decide whether to set this property to nil based on the boolean we set earlier. Note that we need to set the cloudKitContainerOptions property to nil before we load the persistentStore.</p><!--kg-card-begin: code--><pre><code class="language-swift">// AppDelegate.swift
lazy var persistentContainer: NSPersistentCloudKitContainer = {
  let container = NSPersistentCloudKitContainer(name: "AppName")

  guard let description = container.persistentStoreDescriptions.first else {
      fatalError("###\(#function): Failed to retrieve a persistent store description.")
  }

  description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)

  description.setOption(true as NSNumber,
                              forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

  // if "icloud_sync" boolean key isn't set or isn't set to true, don't sync to iCloud
  if(!NSUbiquitousKeyValueStore.default.bool(forKey: "icloud_sync")){
      description.cloudKitContainerOptions = nil
  }

  container.loadPersistentStores(completionHandler: { (storeDescription, error) in
  })
  // ...
}
</code></pre><!--kg-card-end: code--><p></p><p>We have set "true" for the NSPersistentHistoryTrackingKey, so that when the user decide to turn on iCloud sync back, all the Core Data changes made during off-sync is tracked and synced to iCloud.</p><p></p><!--kg-card-begin: html--><span id="reinitialize"></span><!--kg-card-end: html--><h2 id="reinitialize-the-persistent-container-when-icloud-sync-is-toggled">Reinitialize the persistent container when iCloud sync is toggled</h2><p>When user toggle the iCloud sync switch, we need to update the <strong>cloudKitContainerOptions</strong> value and load the persistent store again to toggle iCloud sync. I find it easier to just reinitialize the whole persistent container variable.</p><p>I have extracted the previous NSPersistentCloudKitContainer code into a function  like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">func setupContainer(withSync iCloudSync: Bool) -&gt; NSPersistentCloudKitContainer{
    let container = NSPersistentCloudKitContainer(name: "AppName")
    
    guard let description = container.persistentStoreDescriptions.first else {
        fatalError("###\(#function): Failed to retrieve a persistent store description.")
    }
    
    description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
    
    description.setOption(true as NSNumber,
                          forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
    
    
    // if "cloud_sync" boolean key isn't set or isn't set to true, don't sync to iCloud
    if(!iCloudSync){
        description.cloudKitContainerOptions = nil
    }
    
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        // ...
    })

    // ...
    
    container.viewContext.automaticallyMergesChangesFromParent = true
    return container
}
</code></pre><!--kg-card-end: code--><p></p><p>Then when user toggle the iCloud sync switch, you can reinitialize the AppDelegate's <strong>persistentContainer</strong> variable like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">@IBAction func iCloudToggled(_ sender: UISwitch) {
    // set the icloud_sync key to be true/false depending on the UISwitch state
    NSUbiquitousKeyValueStore.default.set(sender.isOn, forKey: "icloud_sync")

    // reinitialize the persistentContainer and re-load persistentStores
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    appDelegate.persistentContainer = setupContainer(withSync: sender.isOn)
}
</code></pre><!--kg-card-end: code--><p></p><!--kg-card-begin: html--><span id="delete"></span><!--kg-card-end: html--><h2 id="delete-existing-data-from-icloud-when-icloud-sync-is-turned-off">Delete existing data from iCloud when iCloud sync is turned off</h2><p>When user turn off  iCloud sync, they might expect that the data on iCloud server is deleted, for privacy reasons. I think we should respect user's decision to remove existing data from Apple's server.</p><p>When we use NSPersistentCloudKitContainer, Apple will create a special zone in iCloud named "<strong>com.apple.coredata.cloudkit.zone</strong>" to store the user's private data.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/81-toggle-icloud-sync/zone.png" class="kg-image" alt="Toggle iCloud sync on/off for NSPersistentCloudKitContainer"></figure><!--kg-card-end: image--><p></p><p>To delete the existing data synced on iCloud, we can simply delete the whole zone like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// replace the identifier with your container identifier
let container = CKContainer(identifier: "iCloud.es.fluffy.AuthCat")

let database = container.privateCloudDatabase

// instruct iCloud to delete the whole zone (and all of its records)
database.delete(withRecordZoneID: .init(zoneName: "com.apple.coredata.cloudkit.zone"), completionHandler: { (zoneID, error) in
    if let error = error {
        print("deleting zone error \(error.localizedDescription)")
    }
})
</code></pre><!--kg-card-end: code--><p></p><p>Remember to replace the <strong>CKContainer identifier</strong> with your container identifier string.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/81-toggle-icloud-sync/containers.png" class="kg-image" alt="Toggle iCloud sync on/off for NSPersistentCloudKitContainer"></figure><!--kg-card-end: image--><p></p><p>In the iCloud sync switch IBAction :</p><!--kg-card-begin: code--><pre><code class="language-swift">@IBAction func iCloudToggled(_ sender: UISwitch) {
    // set the icloud_sync key to be true/false depending on the UISwitch state
    NSUbiquitousKeyValueStore.default.set(sender.isOn, forKey: "icloud_sync")

    // reinitialize the persistentContainer and re-load persistentStores
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    appDelegate.persistentContainer = setupContainer(withSync: sender.isOn)
  
    // delete the zone in iCloud if user switch off iCloud sync
    if(!sender.isOn){
        // replace the identifier with your container identifier
        let container = CKContainer(identifier: "iCloud.es.fluffy.AuthCat")

        let database = container.privateCloudDatabase

        // instruct iCloud to delete the whole zone (and all of its records)
        database.delete(withRecordZoneID: .init(zoneName: "com.apple.coredata.cloudkit.zone"), completionHandler: { (zoneID, error) in
            if let error = error {
                print("deleting zone error \(error.localizedDescription)")
            }
        })
    }
}
</code></pre><!--kg-card-end: code--><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Read and validate in-app purchase receipt locally using TPInAppReceipt]]></title><description><![CDATA[TPInAppReceipt library encapsulates the receipts data in an InAppReceipt object, you can retrieve the local receipt data using .localReceipt() method like this :
]]></description><link>https://fluffy.es/in-app-purchase-receipt-local/</link><guid isPermaLink="false">5f16bf002b012b0b9547f61f</guid><category><![CDATA[in-app-purchase]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Tue, 21 Jul 2020 10:45:00 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/07/receipt.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/07/receipt.png" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"><p>Table of contents:</p><ol><li><a href="#disclaimer">Disclaimer and Caveats</a></li><li><a href="#prerequisite">Prerequisite</a></li><li><a href="#installation">Installation</a></li><li><a href="#data">Getting the receipt data</a></li><li><a href="#purchased">Check if user has purchased products or has active subscription</a></li><li><a href="#original">Check receipt original app version</a></li><li><a href="#validate">Validate receipt</a></li></ol><p></p><!--kg-card-begin: html--><span id="disclaimer"></span><!--kg-card-end: html--><h2 id="disclaimer-and-caveats">Disclaimer and Caveats</h2><p>This tutorial is aimed to get you on feet to validate and read the in-app purchase receipt data as easy as possible using a library, using library might make it easier for jailbreaker to retrieve your app's premium content for free.</p><p></p><p>The reason so many tutorials out there asks you to include OpenSSL library as <strong>static library</strong> in the bundle, is to make it harder for the hacker (user who jailbroken their device) to replace the library during run time with a hacked version to work around validation checks. For example, if the library has a function that checks for if the in-app purchase is bought ( <em>func isPurchased()</em> ), they can replace it with a hacked library that always return true for this function.</p><p></p><p>Most Stack Overflow answers and tutorials also advocate for rolling your own validation function, to make it harder for hacker to guess and modify them. If you are using an open source library on Github for receipt validation, the hacker can just simply read the source of the library and know which part they should modify to get your premium content for free.</p><p></p><p>My belief is that <strong>people who decide to pirate your app won't buy it anyway</strong>, and there's no stopping a determined hacker to disassemble your app and get your premium content for free, you can only make it harder for them to do so, but usually it is not worth the effort as the percentage of people who decide to hack your app is very small.</p><p></p><p>If you don't care about the potential piracy issue and just want a straightforward way to check if the user has purchased your in-app purchase and show the premium content to them, this tutorial is for you.</p><p></p><p>I practice what I preach, I am using <a href="https://github.com/tikhop/TPInAppReceipt">TPInAppReceipt</a> library on my own app <a href="https://apps.apple.com/us/app/id1523508294">AuthCat</a> (Simple 2FA OTP app with iCloud sync) for reading and validating receipt.</p><p></p><!--kg-card-begin: html--><span id="prerequisite"></span><!--kg-card-end: html--><h2 id="prerequisite">Prerequisite</h2><p>This tutorial assumes you</p><ol><li>already have a paid Apple developer account</li><li>have created an app in App Store Connect</li><li>have created the in-app purchase product in App Store Connect with status 'Ready to Submit'</li><li>have implemented the in-app purchase flow in your app, as in you can purchase the product already</li></ol><p>More info on <a href="https://fluffy.es/zero-iap-products-checklist/">how to troubleshoot your in-app purchase setup here</a>.</p><p></p><!--kg-card-begin: html--><span id="installation"></span><!--kg-card-end: html--><h2 id="installation">Installation</h2><p>We will be using the <a href="https://github.com/tikhop/TPInAppReceipt">TPInAppReceipt</a> library created by Pavel for reading and validating the in-app purchase receipt. Thanks Pavel!</p><h3 id="cocoapods">Cocoapods</h3><p>Add this line into your Podfile :</p><!--kg-card-begin: code--><pre><code class="language-swift">pod 'TPInAppReceipt'
</code></pre><!--kg-card-end: code--><p><br></p><p>then run <code>pod install</code> .</p><p>In any swift file you'd like to use TPInAppReceipt, import the framework using <code>import TPInAppReceipt</code>.</p><h3 id="carthage">Carthage</h3><p>Add this line into your Cartfile :</p><!--kg-card-begin: code--><pre><code class="language-swift">github "tikhop/TPInAppReceipt" 
</code></pre><!--kg-card-end: code--><p><br></p><p>then run <code>carthage update</code> .</p><p></p><!--kg-card-begin: html--><span id="data"></span><!--kg-card-end: html--><h2 id="getting-the-receipt-data">Getting the receipt data</h2><p>TPInAppReceipt library encapsulates the receipts data in an <strong>InAppReceipt</strong> object, you can retrieve the local receipt data using <strong>.localReceipt()</strong> method like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">import InAppReceipt

if let receipt = try? InAppReceipt.localReceipt() {
  // do your validation or parsing here
} else {
  print("Receipt not found")
}
</code></pre><!--kg-card-end: code--><p></p><p>When a user downloads app from the App Store, App Store will generate an app receipt and bundle it with the app.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/55-paid-to-free/appdownload.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p></p><p>We can access the receipt file using <strong>Bundle.main.appStoreReceiptURL</strong> , the URL string might look like this : <code>YourAppPath/StoreKit/Receipt</code>.  The <strong>InAppReceipt.localReceipt()</strong> is a function that wraps around the Bundle.main.appStoreReceiptURL and attempt to read the file located at the URL.</p><p>When we build the app using Xcode or download it using Testflight, <strong>usually the receipt is not included</strong>, and the localReceipt() method will throw an error saying receipt not found.</p><p>We can manually request a receipt from the sandbox App Store by calling the StoreKit function <strong>SKReceiptRefreshRequest.start()</strong> . (<a href="https://fluffy.es/migrate-paid-app-to-iap/#appstorereceipt">Read more here</a>) . TPInAppReceipt library also provides a wrapper around this function, we can call <strong>InAppReceipt.refresh</strong> to retrieve or refresh the receipt file :</p><!--kg-card-begin: code--><pre><code class="language-swift">InAppReceipt.refresh { (error) in
  if let err = error
  {
    print(err)
  }else{
    // do your stuff with the receipt data here
    if let receipt = try? InAppReceipt.localReceipt() {
      // ...
    }
  }
}
</code></pre><!--kg-card-end: code--><p></p><!--kg-card-begin: html--><span id="purchased"></span><!--kg-card-end: html--><h2 id="check-if-user-has-purchased-products-or-has-active-subscription">Check if user has purchased products or has active subscription</h2><p>Here's the fun part. For <strong>non-consumable</strong> product, you can check if the user has purchased it using <strong>containsPurchase(ofProductIdentifier)</strong> function :</p><!--kg-card-begin: code--><pre><code class="language-swift">if let receipt = try? InAppReceipt.localReceipt(){
    if receipt.containsPurchase(ofProductIdentifier: "your.product.identifier"){
        // user has purchased this product
        // return true
    }
}
</code></pre><!--kg-card-end: code--><p></p><p>For <strong>auto-renewable subscription</strong>, you can check if the user has any active subscriptions with <strong>hasActiveAutoRenewablePurchases</strong> property :</p><!--kg-card-begin: code--><pre><code class="language-swift">if let receipt = try? InAppReceipt.localReceipt(){
    if receipt.hasActiveAutoRenewablePurchases {
        // user has active subscription
        // return true
    }
}
</code></pre><!--kg-card-end: code--><p></p><p>If you would like to be more specific, you can check if user has an active subscription of a specific product ID, until a specified date like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">if let receipt = try? InAppReceipt.localReceipt(){
    if receipt.hasActiveAutoRenewableSubscription(ofProductIdentifier: "your.product.identifier.", forDate: Date()) {
        // user has subscription of the product, which is still active at the specified date
        // return true
    }
}
</code></pre><!--kg-card-end: code--><p></p><!--kg-card-begin: markdown--><h3 id="noideaonhowtoimplementcodeforhandlinginapppurchase">No idea on how to implement code for handling in-app purchase?</h3>
<p>Get my preset IAPStore helper code, which you can use it to allow user to <strong>buy IAP</strong>, <strong>restore IAP</strong> and <strong>check if user has purchased IAP</strong> easily.</p>
<p><a href="https://gist.github.com/cupnoodle/63daf45c06aad525f81ddd195cb699fb">https://gist.github.com/cupnoodle/63daf45c06aad525f81ddd195cb699fb</a></p>
<!--kg-card-end: markdown--><!--kg-card-begin: html-->

<span id="original"></span><!--kg-card-end: html--><h2 id="check-receipt-original-app-version">Check receipt original app version</h2><p>If your app is a paid app and you want to make it free with in-app purchase, at the same time allowing user who have purchased the paid app previously to get the in-app purchase for free, you can use the <strong>originalAppVersion</strong> property to check what is the app version number when the user first downloaded the app.</p><p>According to <a href="https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt?changes=_2">Apple's documentation</a>,  originalAppVersion refers to the <strong>build number of your iOS app</strong> :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/55-paid-to-free/CFBundleVersion.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/55-paid-to-free/buildNumber.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p>If the original app version number is smaller than the build number that was last used when the app is still paid, grant the user access to the in-app purchase.</p><!--kg-card-begin: code--><pre><code class="language-swift">// cast the string into integer (build number)
guard let receipt = try? InAppReceipt.localReceipt(),
      let originalBuildNumber = Int(receipt.originalAppVersion) else {
    return
}

// the last build number that the app is still a paid app
if originalBuildNumber &lt; 37 {
    // grant user premium feature here
    print("premium granted")
}
</code></pre><!--kg-card-end: code--><p><br></p><!--kg-card-begin: html--><span id="validate"></span><!--kg-card-end: html--><h2 id="validate-receipt">Validate receipt</h2><p>TPInAppReceipt also provides methods to verify if the receipt is legit and not forged. You can verify the receipt's :</p><ol><li>Bundle Identifier and Version</li><li>GUID Hash</li><li>Signature</li></ol><!--kg-card-begin: code--><pre><code class="language-swift">// Verify GUID hash 
try? receipt.verifyHash()

// Verify bundle identifier and version
try? receipt.verifyBundleIdentifierAndVersion()

/// Verify signature
try? receipt.verifySignature()
</code></pre><!--kg-card-end: code--><p></p><p>You can verify these three steps at once using the <strong>verify()</strong> method :</p><!--kg-card-begin: code--><pre><code class="language-swift">// Verify all at once

do {
    try receipt.verify()
} catch IARError.validationFailed(reason: .hashValidation) 
{
    // Hash validation failed
} catch IARError.validationFailed(reason: .bundleIdentifierVefirication) 
{
    // Bundle identifier verification failed
} catch IARError.validationFailed(reason: .signatureValidation) 
{
    // Signature validation
} catch {
    // Miscellaneous error
}
</code></pre><!--kg-card-end: code--><p></p><p>objc.io has written an excellent article which explains these validation steps in detail here : <a href="https://www.objc.io/issues/17-security/receipt-validation/">https://www.objc.io/issues/17-security/receipt-validation/</a> , I highly recommend giving it a read even though you are using a library to handle it.</p><p>Here are some key points from objc.io article on receipt :</p><blockquote>A receipt is created and signed by Apple through the App Store.</blockquote><blockquote>A receipt is issued for a specific version of an application and a specific device.</blockquote><blockquote>A receipt is stored <strong>locally</strong> on the device.</blockquote><blockquote>A receipt is issued each time an installation or an update occurs.</blockquote><blockquote>When an application is installed, a receipt that matches the application and the device is issued.</blockquote><blockquote>When an application is updated, a receipt that matches the new version of the application is issued.</blockquote><blockquote>A receipt is issued each time a transaction occurs:</blockquote><blockquote>When an in-app purchase occurs, a receipt is issued so that it can be accessed to verify that purchase.</blockquote><blockquote>When previous transactions are restored, a receipt is issued so that it can be accessed to verify those purchases.</blockquote><p>Below I will explain each of the verification steps in simplified form.</p><h3 id="bundle-identifier-and-version">Bundle Identifier and Version</h3><p>When a receipt is issued from Apple and stored into your app bundle, the receipt contains data about the bundle identifier and bundle version (on iOS, the bundle version refers to the CFBundleVersion, which is the build number value).</p><p>The bundle identifier value should match your app bundle identifier, and the bundle version should match the build number value of the app. This verification step is to prevent the hacker to take a valid receipt from another app and replace it in your app bundle, thus bypassing the check if you didn't implement this step.</p><p>Here's an excerpt from the <a href="https://github.com/tikhop/TPInAppReceipt/blob/a2f83c0f78aebb24306b43a210e907b566842fc8/TPInAppReceipt/Source/Validation.swift#L51">.verifyBundleIdentifierAndVersion()</a> method :</p><!--kg-card-begin: code--><pre><code class="language-swift">// check if bundle identifier value of the receipt equal to the app ones
guard let bid = Bundle.main.bundleIdentifier, bid == bundleIdentifier else
{
    throw IARError.validationFailed(reason: .bundleIdentifierVefirication)
}

// check if bundle version value of the receipt equal to the app ones
guard let v = Bundle.main.infoDictionary?["CFBundleVersion"] as? String,
            v == appVersion else
{
    throw IARError.validationFailed(reason: .bundleVersionVefirication)
}
</code></pre><!--kg-card-end: code--><h3 id="guid-hash">GUID Hash</h3><p><strong>GUID</strong> (or UUID) is an acronym for 'Globally Unique Identifier' (or 'Universally Unique Identifier').</p><p>On iOS, this identifier is generated using <strong>UIDevice.current.identifierForVendor.uuid</strong>.</p><p>Here is the simplified diagram on how the hash comparison works :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/80-validate-local-receipt/hash_verification.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p>The receipt contains data of the app's bundle identifier, an opaque value (you can think of it like a salt before hashing), and the resulting SHA1 hash value, which you can compare with later.</p><p>We first generate the Device GUID using UIDevice's <strong>identifierForVendor.uuid</strong> , then concatenate the GUID value with the Opaque value, then concatenate with the Bundle Identifier.</p><p>Then we take the concatenated value and generate a SHA1 hash, then we compare this generated hash with the SHA1 hash value that is present in the receipt data. This two value should be equal, if they are not equal, this means that the receipt is not valid (which might be forged by the hacker).</p><h3 id="signature">Signature</h3><p>A legitimate receipt is signed by Apple, using their own <strong>private key</strong> (which only Apple have it).</p><p>The local receipt container (<strong>TPInAppReceipt.localReceipt()</strong>) contains the receipt data, signature and also a set of public certificates :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/80-validate-local-receipt/container.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p>The signature is generated by Apple, by using their iTunes App Store private key to encrypt the hash generated from Receipt data.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/80-validate-local-receipt/encrypt.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p>Only Apple holds the iTunes App Store private key, hence only them can generate the correct signature.</p><p>We then use the public certificate provided to decrypt the signature to generate the hash, and make sure this hash matches the receipt data hash. If it matches, it means the receipt indeed does comes from Apple.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/80-validate-local-receipt/decryption.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p>A determined hacker could forge a fake receipt with fake certificates and signature inside, which might bypass the validation process above. To ensure the legitimacy of the public certificates, we can download the trusted <a href="https://www.apple.com/certificateauthority/">root Apple CA certificate from Apple website</a>, and include it in our app, then use it to check if the iTunes App Store Certificate and WorldWide Developer (WWDR) Certificate is trusted.</p><p>Apple uses <a href="https://en.wikipedia.org/wiki/Chain_of_trust">Chain of Trust</a> method to grant signing privilege to other Non root certificate. When a certificate signs another certificate, it will generate a signature on the signed certificate, we then can trace back and use this signature to check if this certificate is actually signed by a trusted, higher authority certificate.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/80-validate-local-receipt/chain%20of%20trust.png" class="kg-image" alt="Read and validate in-app purchase receipt locally using TPInAppReceipt"></figure><!--kg-card-end: image--><p>TPInAppReceipt library has included the Apple Root CA certificate in the library, then it will use this certificate to check if the WorldWide Developer Certificate and iTunes App Store Certificate is within the chain of trust.</p><p>I have contributed the chain of trust validation and signature verification part for the TPInAppReceipt library, you can read more on the pull request here : <a href="https://github.com/tikhop/TPInAppReceipt/pull/37/files">https://github.com/tikhop/TPInAppReceipt/pull/37/files</a></p><p></p><!--kg-card-begin: markdown--><h3 id="noideaonhowtoimplementcodeforhandlinginapppurchase">No idea on how to implement code for handling in-app purchase?</h3>
<p>Get my preset IAPStore helper code, which you can use it to allow user to <strong>buy IAP</strong>, <strong>restore IAP</strong> and <strong>check if user has purchased IAP</strong> easily.</p>
<p><a href="https://gist.github.com/cupnoodle/63daf45c06aad525f81ddd195cb699fb">https://gist.github.com/cupnoodle/63daf45c06aad525f81ddd195cb699fb</a></p>
<!--kg-card-end: markdown--><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Use Xcode Previews with UIKit]]></title><description><![CDATA[<h1></h1><p><strong>Prerequisite</strong>: You will need macOS Catalina (10.15)+ and Xcode 11+ to run Xcode Previews.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/demo.gif" class="kg-image" alt="demo"></figure><!--kg-card-end: image--><p></p><p>Apple introduced Xcode Previews in WWDC 2019 alongside with SwiftUI, which allow us to view UI changes immediately after each change, instead of needing to recompile the app after each UI changes.</p><p>You might think</p>]]></description><link>https://fluffy.es/xcode-previews-uikit/</link><guid isPermaLink="false">5edcef8f2b012b0b9547f5be</guid><category><![CDATA[uikit]]></category><category><![CDATA[xcode]]></category><category><![CDATA[autolayout]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sun, 07 Jun 2020 15:20:51 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/06/previewuikit.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/06/previewuikit.png" alt="Use Xcode Previews with UIKit"><p><strong>Prerequisite</strong>: You will need macOS Catalina (10.15)+ and Xcode 11+ to run Xcode Previews.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/demo.gif" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p></p><p>Apple introduced Xcode Previews in WWDC 2019 alongside with SwiftUI, which allow us to view UI changes immediately after each change, instead of needing to recompile the app after each UI changes.</p><p>You might think that Xcode Previews only works for SwiftUI project, but it can work on UIKit's UIViewController and UIView too! This makes coding UI programmatically a lot more attractive now with (almost) instant preview, no need to keep build and run on each changes, and you don't have to deal with slow IBDesignables in Storyboard anymore.</p><p>This article assumes that your iOS app supports iOS 12 and older, you can remove the *<em>@available(iOS 13, <em>)</em></em> line if your app only supports iOS 13 and newer.</p><p></p><p>Table of contents:</p><ol><li><a href="#how">How Previews work</a></li><li><a href="#wrapping">Wrapping UIViewController inside SwiftUI View</a></li><li><a href="#extension">UIViewControllerRepresentable extension</a></li><li><a href="#multiple">Previewing multiple devices</a></li><li><a href="#live">Live Preview</a></li><li><a href="#objc">Objective-C</a></li></ol><p></p><!--kg-card-begin: html--><span id="how"></span><!--kg-card-end: html--><h2 id="how-previews-work">How Previews work</h2><p>To enable preview for a SwiftUI view, we need to create a struct which conform to the <strong>PreviewProvider</strong> protocol like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        // the SwiftUI View
        ContentView()
    }
}
</code></pre><!--kg-card-end: code--><p></p><p>When you create a new SwiftUI View, Xcode will auto generate this block at the bottom of the View code for you.</p><p>Since we are using UIKit's view, we need to wrap our UIKit View Controller inside a SwiftUI View to make it work, using <strong>UIViewControllerRepresentable</strong> protocol, or UIViewRepresentable if you are using UIView instead of UIViewController.</p><p></p><!--kg-card-begin: html--><span id="wrapping"></span><!--kg-card-end: html--><h2 id="wrapping-uiviewcontroller-inside-swiftui-view">Wrapping UIViewController inside SwiftUI View</h2><p>Say you have a View Controller like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// InfoViewController.swift

import UIKit

class InfoViewController: UIViewController {
  ...
}
</code></pre><!--kg-card-end: code--><p></p><p>We can create a struct which conform to <strong>UIViewControllerRepresentable</strong>, and return this view controller in the <strong>makeUIViewController(context: Context)</strong> method.  This will wrap the UIViewController in a SwiftUI View.</p><p>We can leave the updateUIViewController function empty, as we don't have SwiftUI state or binding that will update the view controller in this case.</p><p>Next, we will use this struct inside another struct which conforms <strong>PreviewProvider</strong> protocol, to generate the preview :</p><!--kg-card-begin: code--><pre><code class="language-swift">// InfoViewController.swift

import UIKit

class InfoViewController: UIViewController {
  ...
}

#if DEBUG
import SwiftUI

struct InfoVCRepresentable: UIViewControllerRepresentable {
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        // leave this empty
    }
    
    @available(iOS 13.0.0, *)
    func makeUIViewController(context: Context) -&gt; UIViewController {
        InfoViewController()
    }
}

@available(iOS 13.0, *)
struct InfoVCPreview: PreviewProvider {
    static var previews: some View {
       InfoVCRepresentable()
    }
}
#endif
</code></pre><!--kg-card-end: code--><p></p><p>With UIViewControllerRepresentable and PreviewProvider implemented, we now can view the preview. If the preview pane doesn't show, press <strong>option</strong> + <strong>command</strong> + <strong>enter</strong> (or from the top menu <strong>Editor</strong> &gt; <strong>Canvas</strong>) to open the preview pane.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/previewScreenshot.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p>Now when you make any changes to the view controller code, you can see it reflect (almost) immediately on the preview! 🚀</p><p></p><p>The #if DEBUG and #endif macro means that the code in between them will only be included in the compiled app if the app is built for debugging purpose (eg: when built from Xcode to simulator or your phone), as we don't need the preview code in our production app (in App Store).</p><p></p><p>It can be repetitive to implement the UIViewControllerRepresentable struct for each of the View Controllers, we can create an extension for this to eliminate this repeating work.</p><p></p><!--kg-card-begin: html--><span id="extension"></span><!--kg-card-end: html--><h2 id="uiviewcontrollerrepresentable-extension">UIViewControllerRepresentable extension</h2><p>Let's create new Swift File for this extension :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/new_file_1.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/new_file_2.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p>Name it as "<strong>UIViewController+Preview.swift</strong>".</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/new_file_3.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p>Then put in the extension code like below :</p><!--kg-card-begin: code--><pre><code class="language-swift">//UIViewController+Preview.swift

import UIKit

#if DEBUG
import SwiftUI

@available(iOS 13, *)
extension UIViewController {
    private struct Preview: UIViewControllerRepresentable {
        // this variable is used for injecting the current view controller
        let viewController: UIViewController

        func makeUIViewController(context: Context) -&gt; UIViewController {
            return viewController
        }

        func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        }
    }

    func toPreview() -&gt; some View {
        // inject self (the current view controller) for the preview
        Preview(viewController: self)
    }
}
#endif
</code></pre><!--kg-card-end: code--><p></p><p>With this extension, we just need to add the PreviewProvider confirming struct below each view controllers, and use <strong>ViewController.toPreview()</strong> for the preview view.</p><!--kg-card-begin: code--><pre><code class="language-swift">// InfoViewController.swift

import UIKit

class InfoViewController: UIViewController {
  ...
}

#if DEBUG
import SwiftUI

@available(iOS 13, *)
struct InfoVCPreview: PreviewProvider {
    
    static var previews: some View {
        // view controller using programmatic UI
        InfoViewController().toPreview()
    }
}
#endif

</code></pre><!--kg-card-end: code--><p></p><p>If you are using Storyboard for the view controller, you can instantiate it using the <strong>Storyboard ID</strong>.</p><p>Ensure that you have set a unique storyboard ID for your view controller :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/storyboard_id.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p>Then in the PreviewProvider, instantiate the view controller from the storyboard :</p><!--kg-card-begin: code--><pre><code class="language-swift">#if DEBUG
import SwiftUI

@available(iOS 13, *)
struct ProfileVCPreview: PreviewProvider {
    static var previews: some View {
        // Assuming your storyboard file name is "Main" 
        UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ProfileViewController").toPreview()
    }
}
#endif
</code></pre><!--kg-card-end: code--><p></p><!--kg-card-begin: html--><span id="multiple"></span><!--kg-card-end: html--><h2 id="previewing-multiple-devices">Previewing multiple devices</h2><p>It would be handy if we can preview how our UI looks like in multiple devices at once. We can specify an array of devices and create preview in each of the device like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">#if DEBUG
import SwiftUI

@available(iOS 13, *)
struct InfoVCPreview: PreviewProvider {
    static var devices = ["iPhone SE", "iPhone 11 Pro Max"]
    
    static var previews: some View {
        ForEach(devices, id: \.self) { deviceName in
            InfoViewController().toPreview().previewDevice(PreviewDevice(rawValue: deviceName))
            .previewDisplayName(deviceName)
        }
    }
}
#endif
</code></pre><!--kg-card-end: code--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/two_devices.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p></p><p>The <strong>ForEach</strong> loop will loop through each of the device specified in the <strong>devices</strong> array, <strong>deviceName</strong> is the value of the element used in the loop (eg: iPhone SE).</p><p><strong>.previewDevice()</strong> will show the UI in the Preview Device specified, which is created in the PreviewDevice(rawValue: ) function.</p><p><strong>.previewDisplayName()</strong> is the text that will be displayed below the device in the preview, we'll use the device name for this.</p><p>I usually use the smallest device (iPhone SE) and largest device (iPhone 11 Pro Max) for previewing UI. If the UI works on both smallest and largest devices, it most likely will look ok across all devices.</p><p>You can specify different devices like "iPhone 8", "iPhone 8 Plus", "iPad Pro (11-inch)" etc. Here is the list of supported device in Preview currently :</p><!--kg-card-begin: code--><pre><code class="language-swift">// The following values are supported:
//
//     "iPhone 8"
//     "iPhone 8 Plus"
//     "iPhone SE"
//     "iPhone 11"
//     "iPhone 11 Pro"
//     "iPhone 11 Pro Max"
//     "iPad mini 4"
//     "iPad Air 2"
//     "iPad Pro (9.7-inch)"
//     "iPad Pro (12.9-inch)"
//     "iPad (5th generation)"
//     "iPad Pro (12.9-inch) (2nd generation)"
//     "iPad Pro (10.5-inch)"
//     "iPad (6th generation)"
//     "iPad Pro (11-inch)"
//     "iPad Pro (12.9-inch) (3rd generation)"
//     "iPad mini (5th generation)"
//     "iPad Air (3rd generation)"
//     "Apple TV"
//     "Apple TV 4K"
//     "Apple TV 4K (at 1080p)"
//     "Apple Watch Series 2 - 38mm"
//     "Apple Watch Series 2 - 42mm"
//     "Apple Watch Series 3 - 38mm"
//     "Apple Watch Series 3 - 42mm"
//     "Apple Watch Series 4 - 40mm"
//     "Apple Watch Series 4 - 44mm"
</code></pre><!--kg-card-end: code--><p></p><!--kg-card-begin: html--><span id="live"></span><!--kg-card-end: html--><h2 id="live-preview">Live Preview</h2><p>Xcode Previews only loads the initial UI by default. If you want to interact with the view controller , execute code and update the UI like in simulator, you can utilize the Live Preview button :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/live_preview_button.png" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/live_preview.gif" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: html-->

<span id="objc"></span><!--kg-card-end: html--><h2 id="objective-c">Objective-C</h2><p>Xcode Previews works with Objective-C too! For simplicity, I prefer creating a new Swift file to generate the preview for the Objective-C view controller.</p><p>This section assumes you have created an extension for the ViewController UIViewControllerRepresentable as shown above (<strong>UIViewController+Preview.swift</strong>).</p><p>Create a new Swift file, I prefer to name it as "(ViewControllerName)Preview.swift", then fill in the PreviewProvider struct details :</p><!--kg-card-begin: code--><pre><code class="language-swift">//DerpViewControllerPreview.swift

// DerpViewController is an Objective-C View Controller

#if DEBUG
import SwiftUI

@available(iOS 13, *)
struct DerpVCPreview: PreviewProvider {
    static var devices = ["iPhone SE", "iPhone 11 Pro Max"]
    
    static var previews: some View {
        ForEach(devices, id: \.self) { deviceName in
            DerpViewController().toPreview().previewDevice(PreviewDevice(rawValue: deviceName))
            .previewDisplayName(deviceName)
        }
    }
}
#endif
</code></pre><!--kg-card-end: code--><p></p><p>Update bridging header file and import the view controller :</p><!--kg-card-begin: code--><pre><code class="language-swift">// ProjectName-Bridging-Header.h

//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "DerpViewController.h"
</code></pre><!--kg-card-end: code--><p></p><p>Open the Preview swift file you just created, a canvas should show up (press option + command + enter if it doesn't show), you can then open another tab for the Objective-C view controller to edit it.</p><p></p><p>And now you can make change on your Objective-C view controller and preview it!</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/79-xcode-previews-uikit/objc.gif" class="kg-image" alt="Use Xcode Previews with UIKit"></figure><!--kg-card-end: image-->]]></content:encoded></item><item><title><![CDATA[3 steps to speed up storyboard]]></title><description><![CDATA[1. Remove the use of IBDesignable if possible
2. Uncheck automatically refresh views
3. Use Storyboard reference and refactor]]></description><link>https://fluffy.es/3-steps-to-speed-up-storyboard/</link><guid isPermaLink="false">5ec8fd3a2b012b0b9547f593</guid><category><![CDATA[xcode]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sat, 23 May 2020 10:45:09 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/05/storyboard.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/05/storyboard.png" alt="3 steps to speed up storyboard"><p>When you are working on storyboard with many view controllers, especially with <a href="https://nshipster.com/ibinspectable-ibdesignable/">IBDesignable</a> UI, it can take quite a long while to open storyboard, and then you hear your Mac fan spins like it is about to take off 🚀 😂.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/78-speed-up-storyboard/hot.png" class="kg-image" alt="3 steps to speed up storyboard"></figure><!--kg-card-end: image--><p>Here's 3 steps you can take to speed up the storyboard.</p><p></p><h2 id="1-remove-the-use-of-ibdesignable-view-if-possible">1. Remove the use of IBDesignable View if possible</h2><p>IBDesignable is great for showing custom view attributes lilke cornerRadius, shadow on the storyboard without having to build and run the app. However Xcode seems to use a lot of resource on rendering IBDesignable, and sometimes it will automatically build when you are just trying to open Storyboard.</p><p>If possible, try to remove IBDesignable view on your storyboard's view controller, and initialize it in your code instead.</p><p></p><h2 id="2-uncheck-automatically-refresh-views">2. Uncheck automatically refresh views</h2><p>If it is not possible to remove IBDesignable View, and you find that Xcode keeps building automatically after each key press, you can try uncheck "Automatically Refresh Views".</p><p>Open your storyboard (make sure it is in the active tab), then <strong>uncheck</strong> Editor &gt; Automatically Refresh Views.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/78-speed-up-storyboard/uncheck.png" class="kg-image" alt="3 steps to speed up storyboard"></figure><!--kg-card-end: image--><p></p><p>This will stop Xcode from automatically building the storyboard every time you change your code.</p><p>You can enable it back as needed when you are done with your code, to see the updated visual preview.</p><p>Alternatively, you can keep it off, and click "Refresh All Views" in the Editor menu, to refresh the view manually.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/78-speed-up-storyboard/refreshAllViews.png" class="kg-image" alt="3 steps to speed up storyboard"></figure><!--kg-card-end: image--><h2 id="3-use-multiple-storyboard-files-and-storyboard-references">3. Use multiple storyboard files and storyboard references</h2><p>If you stuff all view controllers into one storyboard, it will get slow eventually. I suggest one storyboard should have less than 15 view controllers.</p><p>You can select a few related view controllers, usually inside the same navigation controller, then select <strong>Editor</strong> &gt; <strong>Refactor to Storyboard</strong> .</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/78-speed-up-storyboard/refactor.png" class="kg-image" alt="3 steps to speed up storyboard"></figure><!--kg-card-end: image--><p></p><p>Xcode will then create a new storyboard file for you, and also a storyboard reference from the previous controller. The storyboard reference will tell the previous controller to continue the segue in the newly created storyboard.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/78-speed-up-storyboard/reference.gif" class="kg-image" alt="3 steps to speed up storyboard"></figure><!--kg-card-end: image--><p></p><p>Usually I will refactor view controllers inside the same navigation controller or tab bar controller into a new storyboard, and name the storyboard based on the tab name or the flow name of the navigation controller (eg: user registration ).</p><p>With multiple storyboards, different dev in the team can work on different storyboard and avoid merge conflict when merging different feature branch.</p><p>Storyboard is just a XML file underneath, parsing XML files and generating layout from it can be time consuming as the XML file grows larger (with addition of more views). It is best to keep each storyboard files small.</p><p>Or alternatively you can say "screw it", and go for <a href="https://fluffy.es/intro-to-creating-ui-in-code-1/">coding the user interface programmatically</a> 😂, I recommend watching Brian's <a href="https://www.youtube.com/channel/UCuP2vJ6kRutQBfRmdcI92mA">Let's Build That App series on building UI programmatically</a>. For small personal projects I would still use Storyboard / XIBs as it is easier to visualize the design, and size classes.</p><p></p>]]></content:encoded></item><item><title><![CDATA[What is Delegate? Understand it by building your own]]></title><description><![CDATA[Delegate isn't some magical pattern, it is just a variable referencing a ViewController or other object, then you just call the method on that variable....]]></description><link>https://fluffy.es/swift-delegate/</link><guid isPermaLink="false">5ebbcb9a2b012b0b9547f551</guid><category><![CDATA[delegate]]></category><category><![CDATA[basics]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Wed, 13 May 2020 10:47:04 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/05/delegate.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/05/delegate.png" alt="What is Delegate? Understand it by building your own"><p>In this tutorial, we are going to replicate tableview and its dataSource and delegate properties using a vertical stackview like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/demo.gif" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>We will implement custom functions like numOfRows, textAtRow and buttonTappedAtRow for the stack view, which mimics tableview's numberOfRowsInSection, cellForRowAt, didSelectRowAt functions.</p><p>By the end of this tutorial, you will learn how delegate works, and have some understanding on how tableview datasource and delegate works.</p><p>Before moving on, I recommend you to <a href="https://github.com/fluffyes/delegate/archive/starter.zip">download the starter project</a>, which contain the Textfield, segmented control and the blank stack view prepared, then you can follow along with this tutorial easily.</p><p>Table of contents :</p><ol><li><a href="#intro">Introduction</a></li><li><a href="#protocol">Recap on Protocol</a></li><li><a href="#datasource">Implementing dataSource</a></li><li><a href="#delegate">Implementing delegate</a></li></ol><p></p><p></p><!--kg-card-begin: html--><span id="intro"></span><!--kg-card-end: html--><h2 id="introduction">Introduction</h2><p>If you haven't grasp the concept of Optionals (eg: ?, !) yet, I recommend you to read the <a href="https://fluffy.es/eli-5-optional/">Optionals article</a> first before reading this article. You will need to understand what does "?" mean for the paragraphs below.</p><p>Delegate pattern is very common in Apple's code, you can see it in tableview, collection view, URLSession etc.</p><p>For example in table view, you can set dataSource and delegate of table view to a view controller like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    tableView.dataSource = self
    tableView.delegate = self
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><!--kg-card-begin: html--><span id="protocol"></span><!--kg-card-end: html--><h2 id="recap-on-protocol">Recap on Protocol</h2><p>Before explaining delegate, we first need to understand the concept of <strong>protocol</strong> . In Swift, we can define a protocol like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">protocol Animal {
  func makeNoise()
}
</code></pre><!--kg-card-end: code--><p><br></p><p><strong>Protocol</strong> specifies what methods (and/or variables) must be implemented, in the <strong>Animal</strong> protocol, there's <strong>makeNoise()</strong> method. We can conform a class to a protocol like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// Example below means the class Cat conforms to the Animal
// protocol, not that Cat is a subclass of Animal
class Cat : Animal {
  func makeNoise(){
    print("meow")
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Here we have declared a class <strong>Cat</strong>, and make it conform to the <strong>Animal</strong> protocol (by putting ": Animal" after the class name). When a class conforms to a protocol, it must implement all the methods mentioned in the protocol, else we will get an error like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/protocolError.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>We can fix the error by implementing the <strong>makeNoise()</strong> method in the Cat class as shown previously.</p><p>This is the same when you conform your view controller to a <strong>UITableViewDataSource</strong> protocol, if you didn't implement <strong>numberOfRowsInSection</strong> and <strong>cellForRowAt indexpath</strong> method, you'll get the same error :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/dataSourceError.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>Here's a simplified view of UITableViewDataSource's protocol :</p><!--kg-card-begin: code--><pre><code class="language-swift">protocol UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell
  
  // ...
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Now you know that the basics of protocol, you might ask "whats the purpose of using protocol?" , "why do we have to force a class to implement some specified methods?" .</p><p>The purpose of protocol is abstraction, if a class conform to a certain protocol, we know that the class will surely implemented the methods specified in the protocol. This allows us to call those methods in the protocol from the class safely without knowing what exactly is the class type.</p><p>Say we have classes <strong>Cat</strong>, <strong>Dog</strong> and <strong>Fox</strong> which conform to Animal protocol :</p><!--kg-card-begin: code--><pre><code class="language-swift">class Cat : Animal {
  func makeNoise(){
    print("meow")
  }
}

class Dog : Animal {
  func makeNoise(){
    print("woof")
  }
}

class Fox : Animal {
  func makeNoise(){
    print("wa pow pow pow")
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>We can then safely call <strong>makeNoise()</strong> for all of them :</p><!--kg-card-begin: code--><pre><code class="language-swift">let cat = Cat()
let dog = Dog()
let fox = Fox()

cat.makeNoise() // outputs "meow"
dog.makeNoise() // outputs "woof"
fox.makeNoise() // outputs "wa pow pow pow"

// can even put them in an array and loop like this 

// an array of objects which conforms to Animal protocol
let animals: [Animal] = [cat, dog, fox]

for animal in animals {
    animal.makeNoise()
}
// outputs:
// "meow"
// "woof"
// "wa pow pow pow"
</code></pre><!--kg-card-end: code--><p><br></p><p>This is similar to when we use the UITableViewDataSource protocol on different UIViewController classes. You can have LoginViewController, ProductListViewController etc, as long as your view controller implement  the function numberOfRowsAtSection, the tableView can access it by calling <strong>yourViewController.numberOfRowsAtSection()</strong> .</p><p>Next, we will look into how to implement the dataSource part of table view into our custom stack view.</p><!--kg-card-begin: html--><span id="datasource"></span><!--kg-card-end: html--><h2 id="implementing-datasource">Implementing dataSource</h2><p>Tableview's dataSource is also using the same delegate concept, just different naming on the variable name.</p><p>Open the starter project, navigate to ViewController.swift, and then define a protocol for the data source, lets name it <strong>CustomStackViewDataSource</strong>, place it at the top of the file, like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift

protocol CustomStackViewDataSource : AnyObject {
    func textForRowAt(index: Int) -&gt; String
    func numberOfRows() -&gt; Int
}

class CustomStackView : UIStackView {
  // ...
}

class ViewController: UIViewController {
  // ...
}
</code></pre><!--kg-card-end: code--><p><br></p><p>We have also set the function <strong>textForRowAt</strong> and <strong>numberOfRows</strong> for the data source protocol, these function are used by the stack view to check how many rows to create and what text to use for different rows.</p><p>The <strong>AnyObject</strong> means that only a class can conform to this protocol, struct can't conform to this protocol.</p><p>Next, we add a <strong>dataSource</strong> variable to the Custom Stack View :</p><!--kg-card-begin: code--><pre><code class="language-swift">class CustomStackView : UIStackView {
  // always set dataSource / delegate to weak to avoid reference cycle
  weak var dataSource : CustomStackViewDataSource?
  
  // ...
}
</code></pre><!--kg-card-end: code--><p><br></p><p>This variable must conform to the CustomStackViewDataSource protocol, which means it must implement the two functions we defined in the protocol.</p><p>We have set the dataSource variable to weak, to <a href="https://krakendev.io/blog/weak-and-unowned-references-in-swift">avoid reference cycle</a>, and weak variable must be an optional since it can be nil.</p><p>Then in the View Controller's <strong>viewDidLoad()</strong> function, we set the <strong>dataSource</strong> of the stack view to <strong>self</strong>.</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController {
  // ...
  override func viewDidLoad() {
      super.viewDidLoad()
      stackView.spacing = 20.0
    
      // set the dataSource to self, which is ViewController
      stackView.dataSource = self
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Xcode will show you an error "<strong>Cannot assign value of type 'ViewController' to type 'CustomStackViewDataSource?</strong>'", because we have set the stack view dataSource to the view controller, but the view controller haven't conform to the <strong>CustomStackViewDataSource</strong> protocol yet.</p><p>Let's conform the ViewController class to the protocol like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController, CustomStackViewDataSource {
	// ...
  override func viewDidLoad() {
    //...
  }
  
  // MARK: CustomStackViewDataSource
    func textForRowAt(index: Int) -&gt; String {
        // if the textField is empty, return default text
        guard let text = textField.text, !text.isEmpty else {
            return "Default text"
        }
        
        // else return the text field text + the row number
        return text + " \(index)"
    }
    
    func numberOfRows() -&gt; Int {
        // return the selected index of the segmented control + 1
        // if user select first index, index = 0, 
        // num of rows = 0 + 1 = 1
        return self.numberOfRowsSegment.selectedSegmentIndex  + 1
    }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>We also set the value for textForRowAt and numberOfRows using the value from the TextField and selected index from the segmented control.</p><p>Next, we will implement the <strong>reloadData() function</strong> of the custom stack view, to make it behave similar to the tableView reloadData() function.</p><!--kg-card-begin: code--><pre><code class="language-swift">class CustomStackView : UIStackView {
  weak var dataSource : CustomStackViewDataSource?
  func reloadData() {
    guard let dataSource = dataSource else {
      return
    }
    
    // remove all the child view inside the stackview
    for subview in self.subviews {
      subview.removeFromSuperview()
    }
    
    // add buttons into the stack view, using the numberOfRows
    for i in 0...(dataSource.numberOfRows() - 1) {
      let button = UIButton()
      button.setTitle(dataSource.textForRowAt(index: i), for: .normal)
      button.setTitleColor(.systemBlue, for: .normal)
      button.setTitleColor(.systemPurple, for: .highlighted)
            
      self.addArrangedSubview(button)
    }
  }
</code></pre><!--kg-card-end: code--><p><br></p><p>Now build and run the app, type in some value in the textfield, and select a number from the segmented control, and tap "Update Stack View". You will see buttons will be added into the stack view :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/datasourcedemo.gif" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><h3 id="how-does-this-work">How does this work?</h3><p>How does the data source and protocol thingy work? You might ask.</p><p>When you set the stack view's dataSource variable to <strong>self</strong> in the view controller, it means that the <strong>stack view's dataSource variable is the view controller</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/ds_1.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p>Thus the <strong>dataSource</strong> of the stack view refers to <strong>ViewController</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/ds_2.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p>When calling the dataSource.numberOfRows()  inside the reloadData method, it is now referrring to <strong>ViewController.numberOfRows()</strong>, as dataSource is equal to ViewController.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/ds_3.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p>Here's the flow of execution :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/ds_flow.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>This is why the ViewController has to conform to CustomStackViewDataSource protocol, and implement the numberOfRows() method, as the <strong>numberOfRows()</strong> method will be called on it.</p><p>Similarly, <strong>textForRowAt()</strong> method has the same flow too.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/ds_flow_2.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>Table view datasource works similarly, when you call tableView.reloadData(),  table view will call the data source (usually the view controller) 's <strong>numberOfRowsInSection</strong> and <strong>cellForRowAt</strong> function to retrieve how many rows the table view should have and the cell content for the row.</p><!--kg-card-begin: html--><span id="delegate"></span><!--kg-card-end: html--><h2 id="implementing-delegate">Implementing delegate</h2><p>Next, we are going to implement delegate to detect action when the button in the stack view is tapped.</p><p>Similar to CustomStackViewDataSource, we will create another protocol for the delegate like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift

protocol CustomStackViewDataSource : AnyObject {
    func textForRowAt(index: Int) -&gt; String
    func numberOfRows() -&gt; Int
}

// add this new delegate protocol
protocol CustomStackViewDelegate : AnyObject {
    func buttonTappedAt(index: Int)
}

class CustomStackView : UIStackView {
  // ...
}

class ViewController: UIViewController {
  // ...
}
</code></pre><!--kg-card-end: code--><p><br></p><p>The <strong>buttonTappedAt(index:)</strong> function will be called when we tap on one of the buttons in the stack view, with the <strong>index</strong> parameter indicating which row of the button we have tapped.</p><p>Next, in the CustomStackView class, we add another variable to be the CustomStackViewDelegate :</p><!--kg-card-begin: code--><pre><code class="language-swift">class CustomStackView : UIStackView {
    
    weak var dataSource : CustomStackViewDataSource?
    weak var delegate : CustomStackViewDelegate?
  
   // ....
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Then inside the reloadData() function of the CustomStackView, we want to add action for the button we insert into the stack view. So that when they are tapped, the <strong>buttonTapped()</strong> function will be executed.</p><!--kg-card-begin: code--><pre><code class="language-swift">// inside class CustomStackView
func reloadData() {
    guard let dataSource = dataSource else {
      return
    }

    // remove all the child view inside the stackview
    for subview in self.subviews {
      subview.removeFromSuperview()
    }

    // add buttons into the stack view, using the numberOfRows
    for i in 0...(dataSource.numberOfRows() - 1) {

      let button = UIButton()

      button.setTitle(dataSource.textForRowAt(index: i), for: .normal)

      button.setTitleColor(.systemBlue, for: .normal)
      button.setTitleColor(.systemPurple, for: .highlighted)
      
      // execute the "buttonTapped" function declared below when the button is tapped.
      button.addTarget(self, action: #selector(buttonTapped(sender:)), for: .touchUpInside)

      self.addArrangedSubview(button)
    }
}

@objc func buttonTapped(sender: Any){

}
</code></pre><!--kg-card-end: code--><p><br></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/buttontapped.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>The current buttonTapped function doesn't do anything, we will come back to implement it later. Now,  we will set the stack view delegate to view controller, and implement the <strong>buttonTappedAt(index:)</strong> function.</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController {
  // ...
  override func viewDidLoad() {
      super.viewDidLoad()
        
      stackView.spacing = 20.0
      stackView.dataSource = self
    
      // set the delegate to self, which is ViewController
      stackView.delegate = self
  }
}
</code></pre><!--kg-card-end: code--><p></p><p>At this point, Xcode will show you an error because your view controller haven't conform to the CustomStackViewDelegate protocol yet. To solve this, we will conform the view controller to the protocol and implement the delegate function :</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController, CustomStackViewDataSource, CustomStackViewDelegate {
	// ...
  override func viewDidLoad() {
    //...
  }
  // MARK: CustomStackViewDataSource
  // ...

  // MARK: CustomStackViewDelegate
  func buttonTappedAt(index: Int) {
      print("tapped at \(index)")
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Now that we have implemented the buttonTappedAt protocol method in ViewController, let's move back to the buttonTapped() function the of CustomStackView, and implement the <strong>buttonTapped(sender:)</strong> function.</p><!--kg-card-begin: code--><pre><code class="language-swift">class CustomStackView : UIStackView {
  // ....
  @objc func buttonTapped(sender: Any){
        // ensure the sender (the UI that was tapped) is a button
        guard let button = sender as? UIButton else {
            return
        }
        
        // get the index of the button tapped from the stack view
        // buttons are subviews of the stack view, from top to bottom
        // we get the index of the button here
        if let index = self.subviews.firstIndex(of: button) {
            // and then call the buttonTappedAt with the index retrieved
            delegate?.buttonTappedAt(index: index)
        }
    }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>And here is how the flow works, when a button inside the stack view is tapped :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/delegateflow.png" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>Build and run the app, when you tap on the button, the delegate function will be executed, and you see the output on the console like this :<br></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/77-delegate/demo.gif" class="kg-image" alt="What is Delegate? Understand it by building your own"></figure><!--kg-card-end: image--><p></p><p>Table view delegate works similarly, when you tap a cell in table view,  table view will call the delegate (usually the view controller) 's <strong>didSelectRowAt</strong> function.</p><p>I hope you have a clearer picture on how delegate / dataSource works after reading this tutorial. Delegate isn't some magical pattern, it is just a variable referencing a ViewController or other object, then you just call the method on that variable.</p><p>You can download the <a href="https://github.com/fluffyes/delegate/archive/master.zip">completed sample delegate project here</a> for double checking.</p><!--kg-card-begin: html--><span id="optionaldelegate"></span><!--kg-card-end: html--><h2></h2>]]></content:encoded></item><item><title><![CDATA[How to transition from login screen to tab bar controller]]></title><description><![CDATA[<h1></h1><p>This article assumes your app will only have one scene all of the time. (ie. no multiple <a href="https://fluffy.es/open-app-in-specific-view-when-push-notification-is-tapped-ios-13/#scene-and-window">scenes</a> at the same time in iPad).</p><p></p><p>At the end of this post, you will be able to implement a login screen (a navigation controller which can push to registration view), that transition</p>]]></description><link>https://fluffy.es/how-to-transition-from-login-screen-to-tab-bar-controller/</link><guid isPermaLink="false">5eab29a62b012b0b9547f51a</guid><category><![CDATA[overview]]></category><category><![CDATA[view controller]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Thu, 30 Apr 2020 19:43:34 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/04/Artboard-1.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/04/Artboard-1.png" alt="How to transition from login screen to tab bar controller"><p>This article assumes your app will only have one scene all of the time. (ie. no multiple <a href="https://fluffy.es/open-app-in-specific-view-when-push-notification-is-tapped-ios-13/#scene-and-window">scenes</a> at the same time in iPad).</p><p></p><p>At the end of this post, you will be able to implement a login screen (a navigation controller which can push to registration view), that transition into the main tab controller like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/demo.gif" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p><p>Here are some common mistakes I saw other developers did (I did it too when first starting out) :</p><h2 id="common-mistakes">Common mistakes</h2><h3 id="1-presenting-the-main-view-from-login-view-on-viewdidappear-every-time">1. Presenting the main view from login view on viewDidAppear every time</h3><p>You might have attempted to present or push the main view controller on the login view controller when user has logged in previously, using code like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// LoginViewController.swift

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    // if user has logged in previously, present the main view controller
    
    if let loggedUsername = UserDefaults.standard.string(forKey: "username") {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let mainTabBarController = storyboard.instantiateViewController(identifier: "MainTabBarController")
        mainTabBarController.modalPresentationStyle = .fullScreen
        
        self.present(mainTabBarController, animated: true, completion: nil)
    }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>This works but user will have to see the present/push animation every time they launch the app, even if they have already logged in previously :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/slowmomodal.gif" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p>(I used slow animation so it is easier to notice).</p><h3 id="2-keep-presenting-view-controllers-without-dismissing">2. Keep presenting view controllers without dismissing</h3><p>When user taps login, the main view controller is pushed/presented on top, then when user logs out, the login view controller is pushed / presented on top, and keeps repeating. This might look like a seamless experience, but it will keep the view controllers stacked without ever being released from memory, which can cause the app to run out of memory and crash eventually if user keeps on login and logout (unlikely but still).</p><p>If you never dismiss the view controllers, they will keep stacking on top like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/stack.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p><p>And memory usage will keep increasing as more view controllers are added on top of the stack, notice the memory keeps increasing when I present a view controller :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/keeppushing.gif" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p><h2 id="changing-root-view-controller">Changing root view controller</h2><p>Instead of pushing/ presenting view controller endlessly, a better way to do this is to change the root view controller directly. <strong>Root view controller is the bottom-most view controller</strong> on the stack. If you are using storyboard, the root view controller is the initial view controller you have set in your storyboard :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/initialVC.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p>Instead of presenting the tab bar controller on top of login view controller:</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/present.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p><p>We are going to switch the bottom-most view controller (root view controller) from the login view to main tab view like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/switchroot.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><h2 id="step-1-show-the-correct-view-controller-on-app-launch">Step 1: Show the correct view controller on app launch</h2><p>If the user is logged in, we want to show the Main tab bar controller as the root view controller.</p><p>If the user is not logged in, we want to show the Navigation controller that contains the login and register view controller.</p><p>Assuming you have a storyboard with a flow similar to this, with navigation controller for the login/ register VC, and a tab bar controller once user has logged in  :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/storyboard.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p><p>In order to access the view controllers in code, we need to assign them a storyboard identifier. Go ahead and <strong>assign storyboard ID for the navigation controller and tab bar controller</strong> :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/loginNavID.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p>(Storyboard Identifier: "LoginNavigationController" for the navigation controller)</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/mainTabID.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p>(Storyboard Identifier: "MainTabBarController" for the tab bar controller)</p><p></p><p>After setting the storyboard identifier, head over to SceneDelegate.swift, and inside the <strong>willConnectTo session:</strong> function, add these lines :</p><!--kg-card-begin: code--><pre><code class="language-swift">// SceneDelegate.swift

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
    guard let _ = (scene as? UIWindowScene) else { return }
    
    // add these lines
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    
    // if user is logged in before
    if let loggedUsername = UserDefaults.standard.string(forKey: "username") {
        // instantiate the main tab bar controller and set it as root view controller
        // using the storyboard identifier we set earlier
        let mainTabBarController = storyboard.instantiateViewController(identifier: "MainTabBarController")
        window?.rootViewController = mainTabBarController
    } else {
        // if user isn't logged in
        // instantiate the navigation controller and set it as root view controller
        // using the storyboard identifier we set earlier
        let loginNavController = storyboard.instantiateViewController(identifier: "LoginNavigationController")
        window?.rootViewController = loginNavController
    }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Don't have a SceneDelegate.swift file in your project? Your project might be created using Xcode 10 or earlier, no worries, in this case, you can put the code in AppDelegate.swift, inside the <strong>didFinishLaunchingWithOptions</strong> method.</p><!--kg-card-begin: code--><pre><code class="language-swift">// AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&gt; Bool {
    // Override point for customization after application launch.
    // add these lines
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    
    // if user is logged in before
    if let loggedUsername = UserDefaults.standard.string(forKey: "username") {
        // instantiate the main tab bar controller and set it as root view controller
        // using the storyboard identifier we set earlier
        let mainTabBarController = storyboard.instantiateViewController(identifier: "MainTabBarController")
        window?.rootViewController = mainTabBarController
    } else {
        // if user isn't logged in
        // instantiate the navigation controller and set it as root view controller
        // using the storyboard identifier we set earlier
        let loginNavController = storyboard.instantiateViewController(identifier: "LoginNavigationController")
        window?.rootViewController = loginNavController
    }
  
    return true
}

</code></pre><!--kg-card-end: code--><p><br></p><h2 id="step-2-change-the-root-view-controller-to-main-tab-bar-controller-after-login">Step 2 : Change the root view controller to main tab bar controller after login</h2><p>So now instead of presenting the main tab bar controller, we will change the root view controller of the app directly after login.</p><p>Let's add a function to switch root view controller in the SceneDelegate.swift file, then we can use this function in all view controllers.</p><!--kg-card-begin: code--><pre><code class="language-swift">// SceneDelegate.swift

func changeRootViewController(_ vc: UIViewController, animated: Bool = true) {
    guard let window = self.window else {
        return
    }
    
    // change the root view controller to your specific view controller
    window.rootViewController = vc
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Then in your login view controller, you can call this function after user successfully logs in :</p><!--kg-card-begin: code--><pre><code class="language-swift">// LoginViewController.swift
@IBAction func loginTapped(_ sender: UIButton) {
    // ...
    // after login is done, maybe put this in the login web service completion block

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let mainTabBarController = storyboard.instantiateViewController(identifier: "MainTabBarController")
    
    // This is to get the SceneDelegate object from your view controller
    // then call the change root view controller function to change to main tab bar
    (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(mainTabBarController)
}
</code></pre><!--kg-card-end: code--><p><br></p><p><strong>UIApplication.shared.connectedScenes.first</strong> is used to get the first scene connected to your app, this article assumes your app has only one scene (ie. user cannot open more than one scene of your app in iPad multitasking mode). As your app has only one scene, the <strong>.first</strong> will get the one and only scene of your app.</p><p>Then the <strong>delegate</strong> is an object (which usually is the SceneDelegate) conforming to the UISceneDelegate protocol, we can cast it back to SceneDelegate object using <strong>as? SceneDelegate</strong> , then we can access the <strong>changeRootViewController</strong> method</p><p></p><p>If your <strong>changeRootViewController()</strong> function is located in <strong>AppDelegate.swift</strong>, you can call it using :</p><!--kg-card-begin: code--><pre><code class="language-swift">(UIApplication.shared.delegate as? AppDelegate)?.changeRootViewController(mainTabBarController)
</code></pre><!--kg-card-end: code--><p><br></p><p>Now when user logs in, the root view controller will switch like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/switchlogin.gif" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p><p>This transition feels abrupt, we can add some animation in the <strong>changeRootViewController</strong> to make the transition looks smoother :</p><!--kg-card-begin: code--><pre><code class="language-swift">// SceneDelegate.swift or AppDelegate.swift

func changeRootViewController(_ vc: UIViewController, animated: Bool = true) {
    guard let window = self.window else {
        return
    }

    window.rootViewController = vc

    // add animation
    UIView.transition(with: window,
                      duration: 0.5,
                      options: [.transitionFlipFromLeft],
                      animations: nil,
                      completion: nil)

}
</code></pre><!--kg-card-end: code--><p><br></p><p>You can choose from a list of preset transition :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/transition.png" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p>For the code above, we will have a flip animation.</p><h2 id="step-3-change-the-root-view-controller-to-login-navigation-controller-after-logout">Step 3 :  Change the root view controller to login navigation controller after logout</h2><p>Similar to previous step, we change the root view controller to the login navigation controller after user logs out.</p><!--kg-card-begin: code--><pre><code class="language-swift">// ProfileViewController.swift
@IBAction func logoutTapped(_ sender: UIButton) {
    // ...
		// after user has successfully logged out
  
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let loginNavController = storyboard.instantiateViewController(identifier: "LoginNavigationController")

    (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(loginNavController)
}
</code></pre><!--kg-card-end: code--><p><br></p><p>If your <strong>changeRootViewController()</strong> function is located in <strong>AppDelegate.swift</strong>, you can call it using :</p><!--kg-card-begin: code--><pre><code class="language-swift">(UIApplication.shared.delegate as? AppDelegate)?.changeRootViewController(mainTabBarController)
</code></pre><!--kg-card-end: code--><p><br></p><p>And now we have implemented the login / logout transition flow by changing root view controller! 🙌</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/76-login-before-tab-bar-controller/demo.gif" class="kg-image" alt="How to transition from login screen to tab bar controller"></figure><!--kg-card-end: image--><p></p>]]></content:encoded></item><item><title><![CDATA[How to solve invalid_client error in Sign in with Apple]]></title><description><![CDATA[Here's the usual suspect when invalid client error happens :
1. Are you using the correct client_id in your HTTP request?
2. Does your JWT header contains all the required parameters?
3. Does your JWT payload contains all the required parameters, correctly?
4. Is your JWT signature correct?
]]></description><link>https://fluffy.es/how-to-solve-invalid_client-error-in-sign-in-with-apple/</link><guid isPermaLink="false">5e9b12bc2b012b0b9547f4f0</guid><category><![CDATA[sign in with apple]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sat, 18 Apr 2020 14:50:06 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/04/invalidclientcover.png" medium="image"/><content:encoded><![CDATA[<h2></h2><img src="https://fluffy.es/content/images/2020/04/invalidclientcover.png" alt="How to solve invalid_client error in Sign in with Apple"><p>One of the major roadblock on implementing Sign in with Apple is generating the <strong>client_secret</strong> parameter, which is required when sending a <strong>HTTP POST</strong> request to Apple's token validation endpoint (<a href="https://appleid.apple.com/auth/token">https://appleid.apple.com/auth/token</a>), which exchange authorization code for an access token.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/error_client.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p><strong>client_secret</strong> is a JWT (JSON Web Token) string you generate to prove that the HTTP request indeed comes from you (or your code), not originated from possible attacker.</p><p></p><p>Here's the usual suspect when invalid client error happens :</p><ol><li><a href="#client_id">Are you using the correct client_id in your HTTP request?</a></li><li><a href="#header">Does your JWT header contains all the required parameters?</a></li><li><a href="#payload">Does your JWT payload contains all the required parameters, correctly?</a></li><li><a href="#signature">Is your JWT signature correct?</a></li></ol><p></p><p>We will walk through each of these below and how to fix them. If you are confident that your JWT payloads and HTTP request are correct, you can jump to section 4 directly. We will be using this online JWT debugger (<a href="https://jwt.io/#debugger">https://jwt.io/#debugger</a>) to debug and verify JWT.</p><!--kg-card-begin: html--><span id="client_id"></span><!--kg-card-end: html--><h2 id="are-you-using-the-correct-client_id-in-your-http-request">Are you using the correct client_id in your HTTP request?</h2><p>If the authorization code comes from your <strong>iOS app</strong>, the <strong>client_id</strong> should be your <strong>iOS app bundle identifier</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/app_client_id.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>If the authorization codes comes from your <strong>website / Android app</strong> (Apple redirect URI), the <strong>client_id</strong> should be your <strong>Services ID identifier</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/web_client_id.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><!--kg-card-begin: html--><span id="header"></span><!--kg-card-end: html--><h2 id="does-your-jwt-header-contains-all-the-required-parameters">Does your JWT header contains all the required parameters?</h2><p>Paste in your JWT string into the "encoded" section of this JWT debugger (<a href="https://jwt.io/#debugger">https://jwt.io/#debugger</a>)</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/header.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>Your JWT header should contain "<strong>kid</strong>" and "<strong>alg</strong>" field.</p><p>The "<strong>kid</strong>" value should equal to your <strong>key ID</strong>, which is the .p8 key file generated in the Apple developer portal, with Sign in with Apple capability. If you don't have access to Apple developer portal, your .p8 key file should have the filename like "AuthKey_<strong>ABCDEF</strong>.p8",  the <strong>ABCDEF</strong> part is your Key ID.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/key_id.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>The "<strong>alg</strong>" value should equal to "<strong>ES256</strong>", as Apple's server expect your JWT to be signed using <a href="https://ldapwiki.com/wiki/ES256">Elliptive Curve Digital Signature Algorithm</a> using P-256 and SHA-256.</p><!--kg-card-begin: html--><span id="payload"></span><!--kg-card-end: html--><h2 id="does-your-jwt-payload-contains-all-the-required-parameters-correctly">Does your JWT payload contains all the required parameters, correctly?</h2><p>Paste in your JWT string into the "encoded" section of this JWT debugger (<a href="https://jwt.io/#debugger">https://jwt.io/#debugger</a>)</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/payload.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>Your JWT payload should only contain "<strong>iss</strong>", "<strong>iat</strong>", "<strong>exp</strong>", "<strong>aud</strong>" and "<strong>sub</strong>" field.</p><p>"<strong>iss</strong>" means issuer of this JWT, which is you or your company, this value should equal to your <strong>Team ID</strong> as shown in the <a href="https://developer.apple.com/account/#/membership/">Apple developer portal membership section</a> :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/issuer.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p></p><p>"<strong>iat</strong>" means the time when this JWT was issued (created), this value should equal to the <strong>UNIX <a href="https://www.unixtimestamp.com">timestamp</a> in seconds</strong> (not milliseconds) when your server generated the JWT. In Ruby, you can use <strong>Time.now.to_i</strong> to get this. If you are using Java, remember to convert this to seconds (instead of using milliseconds). This should be in <strong>number value</strong>, not enclosed in String, (ie: <strong>exp: 1587204602</strong>, instead of exp: "1587204602")</p><p></p><p>"<strong>exp</strong>" means the expiry time for this JWT, which the JWT will become invalid after this time. You should set a future time in <strong>UNIX <a href="https://www.unixtimestamp.com">timestamp</a> in seconds</strong> (not milliseconds) for this field. The maximum acceptable value for this field is current time's timestamp + 15777000 seconds (6 months in the future) , usually I set it to 10 minutes from current time's timestamp ( eg: Time.now.to_i + 600 seconds ). This should be in <strong>number value</strong>, not enclosed in String, (ie: <strong>exp: 1587204602</strong>, instead of exp: "1587204602")</p><p></p><p>"<strong>aud</strong>" means the intended audience for this JWT, as we are sending this JWT to Apple's AppleID server, the value of this should always equal to "<strong><a href="https://appleid.apple.com">https://appleid.apple.com</a></strong>"</p><p></p><p>"<strong>sub</strong>" means the subject for this JWT. This should equal to the <strong>client_id</strong> value you used in the HTTP POST request. If you are using authorization code or access token gotten from iOS app, this field should equal to your iOS app bundle ID. If you are using authorization code or access token gotten from Apple's auth website redirect, this field should equal to your Services ID's identifier.</p><p></p><!--kg-card-begin: markdown--><h3 id="donthaveaclearpictureonhowtoimplementsigninwithapple">Don't have a clear picture on how to implement Sign in with Apple?</h3>
<p>Get <strong>sample chapters</strong> of Practical Sign in with Apple.</p>
<p>Follow a complete step by step guide with code samples on implementing Sign in with Apple.  (Ruby, PHP, Python and NodeJS)</p>
<p><a href="https://drive.google.com/file/d/1G7r8W_gX_eEGVadsORLmCW1yH-3BoWE3/view?usp=sharing">Download Practical Sign in with Apple sample chapter (pdf)</a></p>
<!--kg-card-end: markdown--><!--kg-card-begin: html-->
<span id="signature"></span><!--kg-card-end: html--><h2 id="is-your-jwt-signature-correct">Is your JWT signature correct?</h2><p>Your JWT signature is the last part of the JWT string :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/signature.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>Here's a simplified diagram on how the signature is generated :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/signature_generation.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>To check if the signature generated is correct, we can generate a public key from the .p8 private key, and decrypt the signature using the public key, and check if the decrypted SHA-256 hash matches the SHA-256 hash generated from hashing the header and payload.</p><p></p><p>Fortunately <a href="https://jwt.io/#debugger">https://jwt.io/#debugger</a> has built in signature verification function, we can just paste the public key in and it will verify it for us, so we only need to generate the public key.</p><p></p><p>To generate a public key from the .p8 private key, open Terminal app, and navigate (cd) to the directory containing your .p8 private key.</p><p>And run this command :</p><!--kg-card-begin: code--><pre><code class="language-bash">openssl ec -in AuthKey_123ABC456.p8 -pubout -out AuthKey_123ABC456_public.p8
</code></pre><!--kg-card-end: code--><p></p><p>Replace the "AuthKey_123ABC456.p8" with your private key file name, and replace "AuthKey_123ABC456_public" with the file name you want to use for the exported public key.</p><p></p><p>Running this command will generate a public key in the same folder. The public key file (.p8) file will look like this if you open it with text editor :</p><!--kg-card-begin: code--><pre><code class="language-bash">-----BEGIN PUBLIC KEY-----
ABCDEFGHIJKLMNOP......
-----END PUBLIC KEY-----
</code></pre><!--kg-card-end: code--><p></p><p>Go to <a href="https://jwt.io/#debugger">JWT.io debugger</a>, select ‘<strong>ES256</strong>’  as the algorithm and paste your client secret JWT string, then at the bottom right section, paste the public key text (including the “—BEGIN PUBLIC KEY—-“ and “—END PUBLIC KEY—” lines) into the "verify signature" -&gt; public key box.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/paste_public_key.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p></p><p>You should see a  “Signature Verified” status at the bottom left if the JWT is signed correctly :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/75-invalid-client-sign-in-with-apple/signature_verified.png" class="kg-image" alt="How to solve invalid_client error in Sign in with Apple"></figure><!--kg-card-end: image--><p>If you are seeing “Invalid Signature”, it means that you are  either using the wrong private key to sign it, or there’s something wrong with the signing step.</p><p></p><p>Make sure the JWT library you used supports ES256 elliptic curve encryption.</p><p></p><p>If your own code or the library you used is using <strong>openssl_sign</strong> function to generate the signature for ES256, it will generate a signature which uses a DER-encoded ASN.1 structure (with size &gt; 64).</p><p></p><p>The correct digital signature is the concatenation of two unsigned integers, denoted as R and S, which are the result of the Elliptic Curve (EC) algorithm. The length of R || S is 64.</p><p></p><p>The way to fix this is to convert the DER-encoded signature into a raw concatenation of the R and S values, as explained further in this <a href="https://stackoverflow.com/questions/59737488/apple-sign-in-invalid-client-sign-jwt-for-authentication-using-php-and-openss">StackOverflow answer</a>.</p><p></p><p>…..or you can use the libraries I listed below for generating / signing JWT, I have tested each of them myself and they produce the correct signature for ES256.</p><ol><li>Python - [<a href="https://github.com/mpdavis/python-jose">https://github.com/mpdavis/python-jose</a>]</li><li>Node.js - [<a href="https://github.com/panva/jose">https://github.com/panva/jose</a>]</li><li>PHP - [<a href="https://github.com/firebase/php-jwt">https://github.com/firebase/php-jwt</a>]</li><li>Ruby - [<a href="https://github.com/jwt/ruby-jwt">https://github.com/jwt/ruby-jwt</a>]</li></ol><p></p><p>This article is an excerpt from the book <a href="http://siwa.fluffy.es/?ref=invalidclient">Practical Sign in with Apple</a> , if you want a complete step by step guide on implementing Sign in with Apple (from iOS client side to backend communication with Apple to adding SIWA support on your website), with code samples which you can plug in directly (Ruby, PHP, Python and NodeJS), give the sample chapters a try!</p><!--kg-card-begin: markdown--><p><a href="https://drive.google.com/file/d/1G7r8W_gX_eEGVadsORLmCW1yH-3BoWE3/view?usp=sharing">Download Practical Sign in with Apple sample chapter (pdf)</a></p>
<!--kg-card-end: markdown--><p></p>]]></content:encoded></item><item><title><![CDATA[Open app in specific view when push notification is tapped (iOS 13+)]]></title><description><![CDATA[<h1></h1><p>Say you have an app and want to redirect user to a specific view when a push notification is tapped, eg: going to a specific chat room in Telegram after tapping push notification of that message. How do we proceed to implement this? 🤔</p><p><strong>This tutorial assumes your app have only</strong></p>]]></description><link>https://fluffy.es/open-app-in-specific-view-when-push-notification-is-tapped-ios-13/</link><guid isPermaLink="false">5e9840442b012b0b9547f4c5</guid><category><![CDATA[push-notification]]></category><category><![CDATA[view controller]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Thu, 16 Apr 2020 11:29:34 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/04/Artboard.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/04/Artboard.png" alt="Open app in specific view when push notification is tapped (iOS 13+)"><p>Say you have an app and want to redirect user to a specific view when a push notification is tapped, eg: going to a specific chat room in Telegram after tapping push notification of that message. How do we proceed to implement this? 🤔</p><p><strong>This tutorial assumes your app have only one scene</strong>.</p><p>If you app project file doesn't have SceneDelegate.swift file (created on Xcode 10 or earlier),<a href="https://fluffy.es/open-specific-view-push-notification-tapped/"> check this tutorial instead</a>.</p><p><a href="#tldr">Just want the code already? Click here</a></p><p></p><p>At the end of this tutorial, you will be able to implement this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/50-open-specific-app-push/tapAndMove.gif" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p></p><p>(Notice that after tapping the push notification, the app moves to the conversation with Sans).</p><p>In the previous article on <a href="https://fluffy.es/perform-action-notification-tap/">performing action when user tap on notification</a>, we know that to perform an action when user tap on notification, we need to use the <strong>didReceive response:</strong> method from UNUserNotificationCenterDelegate.</p><!--kg-card-begin: code--><pre><code class="language-swift">// AppDelegate.swift

// remember to set delegate for notification center 
// UNUserNotificationCenter.current().delegate = self

extension AppDelegate: UNUserNotificationCenterDelegate{
    
  // This function will be called right after user tap on the notification
  func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -&gt; Void) {
      
    // tell the app that we have finished processing the user’s action / response
    completionHandler()
  }
}
</code></pre><!--kg-card-end: code--><!--kg-card-begin: html--><br><!--kg-card-end: html--><p>Now that we can process the notification tap, how do we access the view controllers from AppDelegate?</p><h2 id="scene-and-window">Scene and Window</h2><p>Since iOS 13, Apple has introduced a new concept called 'Scene' , as in you can open more than one scene of the same app.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/74-open-specific-app-push-ios13/scenes.png" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p>And when you create a new iOS app project using Xcode 11 and above, Xcode will create a SceneDelegate.swift for you. Prior to iOS 13, the top of the view hierachy is the UIWindow, and we can access the root view controller of a window by using <strong>window.rootViewController</strong> in the AppDelegate.</p><p>But since iOS 13, the top  of the view hierachy is not UIWindow anymore, it's UIScene , and the UIWindow is one level below it.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/74-open-specific-app-push-ios13/rootviewvc.png" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p>The window variable is not available anymore in AppDelegate, which is moved to SceneDelegate instead.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/74-open-specific-app-push-ios13/whereiswindow.png" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p>But the function that will be called when push notification is tapped is still in App Delegate, because push notification tap is app-wide, which is not limited to just one scene, how can we change the view controllers being shown inside the App Delegate code?</p><p>The main goal is to access the <strong>window.rootViewController</strong> from AppDelegate so we can change the structure of current view controllers presented to user.</p><p>This is how we can access the root view controller from the first scene (assuming your app has only one scene, if your app has multiple scene, you need to write code to decide which scene to use).</p><!--kg-card-begin: code--><pre><code class="language-swift">(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController 
</code></pre><!--kg-card-end: code--><p></p><p><strong>UIApplication.shared.connectedScenes</strong> is a set (unordered array) that represents the current scenes of the app shown on the iOS device.</p><p>We then get the first scene using <strong>.first</strong> , and get its <strong>delegate</strong> (UISceneDelegate protocol type).</p><p>In order to access the <strong>window</strong> property as defined in SceneDelegate.swift file, we need to cast it to SceneDelegate using <strong>as? SceneDelegate</strong> .</p><p>Lastly, we access the rootViewController through the window property, viola!</p><p>You can then change the root view controller in the AppDelegate.swift like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// when user tap on the push notification
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -&gt; Void) {
        
    guard var rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
        return
    }

    // change your view controller here
    rootViewController = UIViewController()
}
</code></pre><!--kg-card-end: code--><h2 id="example-with-tab-bar-controllers-and-navigation-controllers">Example with tab bar controllers and navigation controllers</h2><p>Say your app flow is like this, a tab bar controller as the initial view controller and each tab is embed inside navigation controller :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/74-open-specific-app-push-ios13/viewflow.png" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p>The root view controller is our entry point to access view controllers from AppDelegate. One naive approach would be changing the root view controller to the view controller we want to show, when the user tap on the push notification.</p><p>Say we want to show the ConversationViewController when user tap on push notification :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/50-open-specific-app-push/storyboardIDVC.png" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p>We can change the root view controller like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -&gt; Void) {
      
		// the root view controller
		guard var rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
        return
    }
    
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    // instantiate the view controller from storyboard
    if  let conversationVC = storyboard.instantiateViewController(withIdentifier: "ConversationViewController") as? ConversationViewController {

        // set the view controller as root
        rootViewController = conversationVC
    }
    
    // tell the app that we have finished processing the user’s action / response
    completionHandler()
}
</code></pre><!--kg-card-end: code--><p><br></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/50-open-specific-app-push/dontworry.gif" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p>The problem with this approach is that by replacing the root view controller with the new view controller, we have removed the whole current stack including tab bar controller and also the navigation controller, making the user unable to go back to the previous view controller! 😱</p><p>A better solution would to be pushing the new view controller into the current navigation controller (assuming every tab has its own navigation controller).</p><h2 id="pushing-new-view-controller-into-the-current-navigation-controller">Pushing new view controller into the current navigation controller</h2><p>A better approach would be pushing the new view controller to the existing navigation controller.</p><p>This section assume that your app uses a <strong>navigation controller</strong> to move around view controllers. All tabs in the tab bar controller contain a navigation controller.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/50-open-specific-app-push/storyboard.png" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><p></p><p>As the tab bar controller is the root view controller, we can traverse to the navigation controller, and push the new view controller into it like this :</p><!--kg-card-begin: html--><span id="tldr"></span><!--kg-card-end: html--><!--kg-card-begin: code--><pre><code class="language-swift">func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -&gt; Void) {
    
    // retrieve the root view controller (which is a tab bar controller)
    guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
        return
    }
  
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    // instantiate the view controller we want to show from storyboard
    // root view controller is tab bar controller
    // the selected tab is a navigation controller
    // then we push the new view controller to it
    if  let conversationVC = storyboard.instantiateViewController(withIdentifier: "ConversationViewController") as? ConversationViewController,
        let tabBarController = rootViewController as? UITabBarController,
        let navController = tabBarController.selectedViewController as? UINavigationController {

            // we can modify variable of the new view controller using notification data
            // (eg: title of notification)
            conversationVC.senderDisplayName = response.notification.request.content.title
            // you can access custom data of the push notification by using userInfo property
            // response.notification.request.content.userInfo
            navController.pushViewController(conversationVC, animated: true)
    }
    
    // tell the app that we have finished processing the user’s action / response
    completionHandler()
}
</code></pre><!--kg-card-end: code--><p></p><p>This code will produce the following result :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/50-open-specific-app-push/tapAndMove.gif" class="kg-image" alt="Open app in specific view when push notification is tapped (iOS 13+)"></figure><!--kg-card-end: image--><!--kg-card-begin: markdown--><h3 id="wanttotryoutthesamplepushnotificationappproject">Want to try out the sample push notification app project?</h3>
<p>Get sample Xcode project containing code of showing specific view controller when push notification is tapped, try it out!</p>
<p><a href="https://github.com/fluffyes/tapPushOpenView/archive/master.zip">https://github.com/fluffyes/tapPushOpenView/archive/master.zip</a></p>
<!--kg-card-end: markdown--><h2 id="presenting-view-controller-modally">Presenting view controller modally</h2><p>What if the current showing view controller of your app isn't contained inside a navigation controller? You can choose to present it modally (remember to check if there is any other view controller is being presented before presenting it), but you would have to traverse from the root view controller to the current showing view controller manually in AppDelegate, and then call <strong>.present()</strong> on the current view controller.</p><!--kg-card-begin: code--><pre><code class="language-swift">if  let conversationVC = storyboard.instantiateViewController(withIdentifier: "ConversationViewController") as? ConversationViewController,
    let tabBarVC = rootViewController as? UITabBarController {
    
    tabBarVC.selectedViewController.present(conversationVC, animated: true, completion: nil)
}
</code></pre><!--kg-card-end: code-->]]></content:encoded></item><item><title><![CDATA[Inspect app folder in simulators and real device]]></title><description><![CDATA[<h1></h1><p>Your app might store some data or files in the Documents folder, and sometimes you might want to check if the data / files are stored correctly by inspecting them. How do we find the app folder containing the data?</p><p>You can also <a href="https://fluffy.es/persist-data/#userdefaults">inspect the UserDefaults data</a> by going to <strong>AppFolder/</strong></p>]]></description><link>https://fluffy.es/inspect-app-folder-in-simulators-and-real-device/</link><guid isPermaLink="false">5e85fb172b012b0b9547f4a2</guid><category><![CDATA[tips]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Thu, 02 Apr 2020 14:56:02 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/04/inspect.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/04/inspect.png" alt="Inspect app folder in simulators and real device"><p>Your app might store some data or files in the Documents folder, and sometimes you might want to check if the data / files are stored correctly by inspecting them. How do we find the app folder containing the data?</p><p>You can also <a href="https://fluffy.es/persist-data/#userdefaults">inspect the UserDefaults data</a> by going to <strong>AppFolder/Library/Preferences/Your_app_bundle_id.plist</strong> .</p><h2 id="simulators">Simulators</h2><p>For simulator, the app folder is stored in your Mac. You can get the path of this folder by printing  <strong>NSHomeDirectory()</strong></p><!--kg-card-begin: code--><pre><code class="language-swift">print("app folder path is \(NSHomeDirectory())")
</code></pre><!--kg-card-end: code--><!--kg-card-begin: html--><br><!--kg-card-end: html--><p>You can put this line in viewDidLoad function, and get the path in the Xcode console.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/folderPath.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><p>Copy the path, open Finder, then select "<strong>Go</strong>" -&gt; "<strong>Go to Folder</strong>" (shortcut key Command + Shift + G), then paste in the path and press Enter.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/gotofolder.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/pastePath.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/appFolder.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><p>If you don't want to modify your code, there's another way to locate the app folder. </p><p>Launch the simulator, then open Terminal app, type in the code below, replace the bundle ID with your app bundle ID :</p><!--kg-card-begin: markdown--><blockquote>
<p>open `xcrun simctl get_app_container booted BUNDLE_ID_OF_THE_APP data`</p>
</blockquote>
<!--kg-card-end: markdown--><p>This will open the folder automatically.</p><p></p><h2 id="real-device">Real device</h2><p>For real device, you will need to download the whole app container to your Mac to inspect them.</p><p>Open Xcode, Select  <strong>Window</strong> &gt; <strong>Devices and Simulators</strong></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/devices.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><p>Select your device, then choose the app you want to inspect, then select "<strong>Download container</strong>" to download the app folder to your Mac.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/downloadContainer.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><p>The app container will be in .xcappdata format, you can inspect it by right clicking it -&gt; "<strong>Show package contents</strong>"</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/showPackage.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/73-inspect-app-folder/yay.png" class="kg-image" alt="Inspect app folder in simulators and real device"></figure><!--kg-card-end: image--><p></p>]]></content:encoded></item><item><title><![CDATA[Introduction to Localization (add additional language support to your app)]]></title><description><![CDATA[<h1></h1><p>Localization means making your app support additional languages. Usually we will start with english as the base language as default, then slowly adding more language support on top of it.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/translate.png" class="kg-image" alt="translation demo"></figure><!--kg-card-end: image--><p></p><p>Table of Contents :</p><ol><li><a href="#addnewlanguage">Adding new language</a></li><li><a href="#addtext">Adding and using translated text</a></li><li><a href="#parameters">Localizing word with parameters</a></li><li><a href="#changelanguage">Preferred language and how to</a></li></ol>]]></description><link>https://fluffy.es/introduction-to-localization/</link><guid isPermaLink="false">5e64d81b2b012b0b9547f451</guid><category><![CDATA[overview]]></category><category><![CDATA[xcode]]></category><category><![CDATA[localization]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sun, 08 Mar 2020 11:40:41 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/03/introtolocal.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/03/introtolocal.png" alt="Introduction to Localization (add additional language support to your app)"><p>Localization means making your app support additional languages. Usually we will start with english as the base language as default, then slowly adding more language support on top of it.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/translate.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Table of Contents :</p><ol><li><a href="#addnewlanguage">Adding new language</a></li><li><a href="#addtext">Adding and using translated text</a></li><li><a href="#parameters">Localizing word with parameters</a></li><li><a href="#changelanguage">Preferred language and how to change them</a></li><li><a href="#currentapplanguage">Detecting the current app language</a></li><li><a href="#system">Localizing App Name and system's message</a></li><li><a href="#change">Direct user to change language</a></li></ol><p></p><p>Before starting localization of your app, make sure the project has "Use Base Internationalization" checked. This is checked by default when you start a new project in Xcode 11.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/useBaseInternationalization.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><h2 id="what-does-use-base-internationalization-mean">What does "Use Base Internationalization" mean?</h2><p>When you create a new iOS project in Xcode 11, Xcode will auto generate the resources for the default language (which is the "base" language), like Main.storyboard and LaunchScreen.storyboard, and place them into the "Base.lproj" folder. The "base" means other language added later on will use this as base to refer to.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/whatIsBase.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><!--kg-card-begin: html--><span id="addnewlanguage"></span><!--kg-card-end: html--><h2 id="adding-new-language">Adding new language</h2><p>Now that we have base localization set, we can proceed to add more language by clicking the "+" button below :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/addNewLanguage.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>After selecting a language, Xcode will ask if you want to create localization for current resource (storyboard / XIBs) :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/createLocalization.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>After you click 'Finish' , Xcode will create a Strings file for each of the resources.</p><p>For example, say your Main.storyboard has a view controller with two labels like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/storyboardLabel.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>If you have included Main.storyboard (checked in the dialog above) and click 'Finish', Xcode will create a 'Main.strings' file (spanish) for translating the labels shown in the storyboard.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/storyboardStrings.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>You can change the text of "Good morning" to "Buenos días" in Main.strings, then when you run the app in a device which primary language is set to Spanish, it will show "Buenos días".</p><p>I prefer to create a separate Strings file which contain all the text that need to be localized, instead of separating the text based on storyboard files. In this tutorial we will ignore the auto-generated Main.strings file, and creating our own Strings file.</p><p>Right click your project folder, select '<strong>New File</strong>' , then scroll down to find '<strong>Strings</strong>', select it and click '<strong>Next</strong>'.</p><p>Then name the file as '<strong>Localizable.strings</strong>' and click 'Create'. Older Xcode versions will automatically create this file for you when you localize the project, recent Xcode version doesn't do this automatically anymore, hence we need to create it manually.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/newFile.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/newStringsFile.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/Localizable.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p></p><p>Now you have a 'Localizable.strings' created for the base language. We can then add a Spanish version (and other languages) of this file, by clicking the '<strong>Localize</strong>' button on the file :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/localizeFile.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>After clicking 'Localize', Xcode will ask if you want to localize the file, and select a default language for it. Usually I use 'English' as the default language and gradually add more language support to it.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/englishLocalize.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Now on the 'Localization' section of the File inspector, we can see the language available for this project (You can add more language by going back to the project settings and click '+'). Check the 'Spanish' (or the language you have added) to add localization support.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/addSpanish.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>After checking the additional language, you can expand the "Localizable.strings" and see there's multiple language version :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/stringLocalize.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>When localizing a file, Xcode will create separate language folder for that file. Eg: "<strong>es.lproj</strong>" for spanish ("es"), and "<strong>en.lproj</strong>" for english, etc. The localized file is then put into these folders.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/folderStructure.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/folderStructure2.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: html--><span id="addtext"></span><!--kg-card-end: html--><h2 id="adding-and-using-translated-text">Adding and using translated text</h2><p>To add translation text, we need to input key value pair of each translation in this format:</p><p><strong>"key" = "value";</strong></p><p>For example, the english Localizable.strings file looks like this</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/englishText.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Then in the spanish Localizable.strings file :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/spanishText.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Notice that we uses the same key for the same text translation in both file, and each translation is separated by a semicolon (<strong>;</strong>).</p><p>To use the translation text, we can call <strong>Bundle.main.localizedString(forKey:, value: , table: )</strong> :</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController {
    
    @IBOutlet weak var helloLabel: UILabel!
    @IBOutlet weak var goodMorningLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // return 'Hello' for english, 'Hola' for spanish
        helloLabel.text = Bundle.main.localizedString(forKey: "Hello", value: "Hello", table: "Localizable")
        
        // return 'Good Morning' for english, 'Buenos días' for spanish
        goodMorningLabel.text = Bundle.main.localizedString(forKey: "Good Morning", value: "Good Morning", table: "Localizable")
    }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>The <a href="https://developer.apple.com/documentation/foundation/bundle/1417694-localizedstring"><strong>Bundle.main.localizedString</strong></a> function will search for the value of the specified key in <strong>forKey</strong> parameter, and return it if it exist. If no such key exist in the strings file, the function will return the string passed into the <strong>value</strong> parameter.</p><p>The <strong>table</strong> parameter specifies the filename of the strings file to search, in the example above, it will search the key/value pair in "Localizable.strings" file. If you pass in <strong>nil</strong> for the <strong>table</strong> parameter, it will look for "Localizable.strings" by default.</p><p>Now if you run the app in spanish language, you will see the following output :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/spanishLabel.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>You might have seen <a href="https://developer.apple.com/documentation/foundation/nslocalizedstring"><strong>NSLocalizedString()</strong></a> function before, it's a shortcut function Apple has provided us. NSLocalizedString take in two parameters, the key and comment, and then it will call Bundle.main.localizedString() by passing the key to <strong>forKey</strong> and <strong>value</strong> parameters, and set the <strong>table</strong> to <strong>nil</strong> , which will search for "Localizable.strings" file by default.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/nslocalizedstring.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>The comment parameter is for your own reference, it doesn't do anything, you can think of it like the comment you place in your code.</p><p>We can then replace the Bundle.main.localizedString with the shorter NSLocalizedString like this :</p><!--kg-card-begin: code--><pre><code class="language-swift"> override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.

    helloLabel.text = NSLocalizedString("Hello", comment: "")
    goodMorningLabel.text = NSLocalizedString("Good Morning", comment: "")
}
</code></pre><!--kg-card-end: code--><!--kg-card-begin: html--><span id="parameters"></span><!--kg-card-end: html--><h2 id="localizing-word-with-parameters">Localizing word with parameters</h2><p>Sometimes we might want to localize text with some parameter, eg: "Hello (username)" , which the username is an input from the user.  To interpolate the parameter, we can use the String format function <strong>String(format: , args)</strong>. According to <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html">Apple's string programming guide</a>, we can use "%@" to interpolate string, "%d" for integer etc.</p><p>We can use "%@" inside the translated text as placeholder, then replace it with the actual username using String(format:, args). Here's an illustration on how it works :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/parameters.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>You can use as many parameters as you like,  like this : String(format: NSLocalizedString("Hello first middle last name", comment: ""), "First name", "Middle name", "Last name"). As the String(format:, args:) accepts multiple parameters for args.</p><!--kg-card-begin: html--><span id="changelanguage"></span><!--kg-card-end: html--><h2 id="preferred-language-and-how-to-change-them">Preferred language and how to change them</h2><p>When user launch your app for the first time, iOS will decide which language to use based on their <strong>preferred language order</strong>, not the iPhone language.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/preferredLanguageOrder.jpg" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Your user might set iPhone language to English, but using another language as the most preferred language (the top row of preferred language order).</p><p>iOS will choose the <strong>first matching language</strong> from top to bottom in the preferred language list to launch your app. If your app doesn't support all the languages listed in the preferred languages , iOS will try to make guesses to choose the nearest language possible. (eg: your app support Portuguese (Portugal) but the user only have Portuguese(Brazil) and English, then iOS will choose Portuguese (Portugal)).</p><p>Source: <a href="https://developer.apple.com/videos/play/wwdc2016/201/">https://developer.apple.com/videos/play/wwdc2016/201/</a> , timestamp: 11:02</p><p>App-specific preferred language is introduced in iOS 13, this means that user can change preferred language for that specific app. In the Settings app, user can scroll to your app, and change the preferred language for just your app without changing the device preferred languages.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/appSpecificLanguage.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>After user change the preferred language for your app and switch back to your app, your app will be relaunched and NSLocalizedString will use the updated language.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/switchLanguage.gif" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>It can be quite troublesome to go to the Settings app, and change language just to test your localization every time you update a text.</p><p>To shortcut this process, we can set the app to always launch in a language during debug session. To do this, in Xcode, select <strong>Product</strong> &gt; <strong>Scheme</strong> &gt; <strong>Manage Schemes ...</strong></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/manageScheme.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>In the scheme window , double click your app scheme :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/selectAppScheme.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Then in the Run/debug tab, change the "Application Language" to your desired language.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/selectSchemeLanguage.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p>And now every time you build and run your app from Xcode, it will use that language as the preferred language.</p><p></p><!--kg-card-begin: html--><span id="currentapplanguage"></span><!--kg-card-end: html--><h2 id="detecting-the-current-app-language">Detecting the current app language</h2><p>Say you want to show / hide some UI based on the current app language (not the device language), you can get the current selected app language with <strong>Bundle.main.preferredLocalizations.first</strong> .</p><!--kg-card-begin: code--><pre><code class="language-swift">// if the app language is japanese
if(Bundle.main.preferredLocalizations.first == "ja"){
    print("the app is using japanese language")
}
</code></pre><!--kg-card-end: code--><p><br></p><p>From <a href="https://developer.apple.com/documentation/foundation/bundle/1413220-preferredlocalizations">Apple's documentation</a>, preferredLocalizations will return an array of strings,  containing language IDs for localizations in the bundle. The strings are ordered according to the user's language preferences and <strong>available localizations</strong>. We use the <strong>.first</strong> to get the most preferred language of the app (which the app is using).</p><p>You might see some StackOverflow answer suggesting to use <strong>Locale.preferredLanguage.first</strong> for getting the current language, keep in mind that <strong>this will get the first preferred language of the device, not the app</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/difference.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: html--><span id="system"></span><!--kg-card-end: html--><h2 id="localizing-app-name-and-system-s-message">Localizing App Name and system's message</h2><p>To localize app name and other text that are defined in the Info.plist file (eg: Camera usage description), we will need to create a <strong>InfoPlist.strings</strong> file. Right click your project folder, select 'New File...' &gt; 'Strings File' , and name it as <strong>InfoPlist.strings</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/newFile.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/newStringsFile.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/infopliststrings.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Localize it by clicking the 'Localize' button,</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/localizeFile2.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Then check all the language you want to localize in the "<strong>Localization</strong>" section.</p><p>For app name, the key is "<strong>CFBundleDisplayName</strong>".</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/localizeinfoplist.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>For the other system permission messages like camera usage description, you can get the key of it from <strong>Info.plist</strong>.</p><p>Normally when you open Info.plist, Xcode will show it as a list like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/infoplistcamera.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>To view the raw key value, we can right click "Info.plist", select "Open As" &gt; "Source Code" :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/infoplistsourcecode.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Then you can find the key for the Camera Usage Description, which is enclosed between <code>&lt;key&gt;</code> and <code>&lt;/key&gt;</code> tag :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/camerakey.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Then you can use this key for the translation text in <strong>InfoPlist.strings</strong> file.</p><p>Note that for app name and Info.plist related text localization, <strong>it will use the device preferred language</strong>, instead of the app's preferred langauge.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/localizedAppName.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/cameraDialogue.jpg" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: html--><span id="change"></span><!--kg-card-end: html--><h2 id="direct-user-to-change-language">Direct user to change language</h2><p>Since iOS 13, Apple recommend us to <strong>direct user to the Settings app to change language</strong>, instead of changing the language manually in the app (eg: using Hackish way like changing the AppleLanguages UserDefault key, or use some method swizzling to change the bundle), as there is a lot of work under the hood done by the OS.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/72-localization/bestPractice.png" class="kg-image" alt="Introduction to Localization (add additional language support to your app)"></figure><!--kg-card-end: image--><p></p><p>Source: <a href="https://developer.apple.com/videos/play/wwdc2019/403/">Creating Great Localized Experience with Xcode 11 WWDC video</a>, timestamp: 3:01 .</p><p>You can have a change language button in your app, and when tapped, open the Settings app &gt; your app, by using <strong>UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)</strong> .</p>]]></content:encoded></item><item><title><![CDATA[How to expand and contract height of a UITableView cell when tapped]]></title><description><![CDATA[<h1></h1><p>To avoid confusion, this article focuses on <strong>expanding and contracing the height of a single tableview cell when it is tapped</strong>. I googled around and found out many people used the same terms for inserting new cells under the tapped cell, which is not what this article is about, I</p>]]></description><link>https://fluffy.es/how-to-expand-tableview-cell/</link><guid isPermaLink="false">5e4040cc2b012b0b9547f41a</guid><category><![CDATA[autolayout]]></category><category><![CDATA[tableview]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sun, 09 Feb 2020 17:32:13 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/02/cover.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/02/cover.png" alt="How to expand and contract height of a UITableView cell when tapped"><p>To avoid confusion, this article focuses on <strong>expanding and contracing the height of a single tableview cell when it is tapped</strong>. I googled around and found out many people used the same terms for inserting new cells under the tapped cell, which is not what this article is about, I will write on this topic in the future.</p><p>At the end of this tutorial, you will be able to implement this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/71-expanding-cell/expanding.gif" class="kg-image" alt="How to expand and contract height of a UITableView cell when tapped"></figure><!--kg-card-end: image--><p></p><p>This tutorial assumes you know how to <a href="https://fluffy.es/dynamic-height-tableview-cell/">implement tableview cell with dynamic height</a> (Automatic Dimension), as in each cell can contain different content height, eg: lengths of label text, different image sizes.</p><p>Say we have a table view cell with image and label set like below :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/71-expanding-cell/constraint.png" class="kg-image" alt="How to expand and contract height of a UITableView cell when tapped"></figure><!--kg-card-end: image--><p></p><p>You can download the starter project here : <a href="https://drive.google.com/file/d/1AaDr1gZauEcpZtdCU9-rSUhgvMZbWDPT/view?usp=sharing">Expanding cell starter project</a></p><p>The image view (avatarImageView) and label (messageLabel) are initialized in the cellForRowAt function :</p><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift

extension VariableViewController : UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
        return 3
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
      
      // if cant dequeue cell, return a default cell
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? VariableTableViewCell else {
            print("failed to get cell")
            return UITableViewCell()
        }
        
        cell.avatarImageView.image = UIImage(named: avatars[indexPath.row])
        cell.messageLabel.text = messages[indexPath.row]
        cell.messageLabel.numberOfLines = 0
      
        return cell
    }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Before we implement the expanding / contracting animation, we would need to declare an IndexSet to keep track of which cell have been expanded, by storing the <strong>row index into the IndexSet</strong>, so that when we tap on the expanded cell, it would contract instead of expanding.</p><p></p><h2 id="use-indexset-to-keep-track-of-which-cell-has-been-expanded">Use IndexSet to keep track of which cell has been expanded</h2><p>From <a href="https://developer.apple.com/documentation/foundation/indexset">Apple documentation on IndexSet</a>,</p><p>A collection of unique integer values that represent the indexes of elements in another collection.</p><p>You can think of IndexSet like an array, it can hold multiple integers, but non of the integers are repeating. This way, we won't need to worry about adding repeating row index into the array.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/71-expanding-cell/indexSet.png" class="kg-image" alt="How to expand and contract height of a UITableView cell when tapped"></figure><!--kg-card-end: image--><p></p><p>"Why can't we just use an array of boolean to track which cell has been expanded?" , I heard you, this solution works too, say you can check if a cell has been expanded  like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// 10 cells
var expandedCells : [Boolean] = Array(repeating: false, count: 10)

if(expandedCells[index] == true)
{
  // ... show the cell expanded
}
</code></pre><!--kg-card-end: code--><p><br></p><p>This works well if you have a fixed amount of cells in the table view, but if you have dynamic data that will be added from external API, say like Facebook timeline which keep adding more statuses as you scroll, you will need to adjust the size of the expandedCells array as more cells are being added, this process can easily be error prone!</p><p>By using IndexSet, we won't need to worry about the size for the dataSource, we just need to add or remove index of the cell that is expanded.</p><p>We can define an IndexSet to keep track of the index of cell expanded in the view controller like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">class ViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    
    // this will contain the index of the row (integer) that is expanded
    var expandedIndexSet : IndexSet = []
  
    // ...
}
</code></pre><!--kg-card-end: code--><p><br></p><h2 id="animating-the-cell-to-expand-and-contract">Animating the cell to expand and contract</h2><p>Now that we have an IndexSet to keep track of which cell has been expanded, we can layout the cell expanded / contracted state in <strong>cellForRowAt</strong> like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">extension VariableViewController : UITableViewDataSource {
  // ...

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? VariableTableViewCell else {
      print("failed to get cell")
      return UITableViewCell()
    }

    cell.avatarImageView.image = UIImage(named: avatars[indexPath.row])
    cell.messageLabel.text = messages[indexPath.row]

    // if the cell is expanded
    if expandedIndexSet.contains(indexPath.row) {
      // the label can take as many lines it need to display all text
      cell.messageLabel.numberOfLines = 0
    } else {
      // if the cell is contracted
      // only show first 3 lines
      cell.messageLabel.numberOfLines = 3
    }

    return cell
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>As we want the cell to be expanded / contracted on tap, we then implement the didSelectAtRow delegate function :</p><!--kg-card-begin: code--><pre><code class="language-swift">extension ViewController : UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        // if the cell is already expanded, remove it from the indexset to contract it
        if(expandedIndexSet.contains(indexPath.row)){
            expandedIndexSet.remove(indexPath.row)
        } else {
            // if the cell is not expanded, add it to the indexset to expand it
            expandedIndexSet.insert(indexPath.row)
        }
        
        // the animation magic happens here
        // this will call cellForRow for the indexPath you supplied, and animate the changes
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }
}

</code></pre><!--kg-card-end: code--><p><br></p><p>The key for the animation is calling <strong>tableView.reloadRows(at:, with:)</strong> . You supply an array of rows (indexpath) that needs to be reloaded, then the tableview will reload them by calling <strong>cellForRowAt:</strong> on these cell to layout them again, and animate the transition with the RowAnimation you passed into the <strong>with:</strong> parameter.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/71-expanding-cell/cellForRowAt.gif" class="kg-image" alt="How to expand and contract height of a UITableView cell when tapped"></figure><!--kg-card-end: image--><p></p><p>Here's a list of available <a href="https://developer.apple.com/documentation/uikit/uitableview/rowanimation">RowAnimation</a> we can use to animate the cell row changes, but mostly we will use the .automatic or .fade effect as it looks better.</p><p></p><p>Implementing the cell animation was easier than I originally thought! Thanks <strong>reloadRows</strong> for the heavy lifting.</p><p></p><p>If you want to check if an UILabel is truncated (with "..." at the end of text because of clipping), I have written a <a href="https://gist.github.com/cupnoodle/75fd46d4fd8af309027a3b67dbdd5b74">short extension here</a>, this might be useful if you are checking whether to show a "read more" button on the tableview cell depending if the label is being truncated. eg. If the the label text is short and didn't get truncated, don't show the "read more" button.</p><p></p><!--kg-card-begin: markdown--><p><strong>Download the sample expanding cell project</strong></p>
<p>Not sure if you are doing it right on the expanding cell?</p>
<p>Download the sample project and compare it yourself!</p>
<p><a href="https://github.com/fluffyes/expandingCell">https://github.com/fluffyes/expandingCell</a></p>
<p><img src="https://github.com/fluffyes/expandingCell/raw/master/expanding.gif" alt="How to expand and contract height of a UITableView cell when tapped"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How to implement dynamic height table view cell (self sizing)]]></title><description><![CDATA[To let Auto Layout know how to calculate the height of the cell, we need to create sufficient constraints from top of the cell's content view to the bottom.]]></description><link>https://fluffy.es/dynamic-height-tableview-cell/</link><guid isPermaLink="false">5e2978a82b012b0b9547f3f5</guid><category><![CDATA[autolayout]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Thu, 23 Jan 2020 10:49:04 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/01/cover-1.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/01/cover-1.png" alt="How to implement dynamic height table view cell (self sizing)"><p>It's common to implement table view which contain cells with different length of text, as the content are retrieved from web APIs.</p><p></p><p>When implementing a dynamic height tableview cell, one common issue we get is that the text either got clipped or two elements get overlapped :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/clipped.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p>A quick search on google on 'how to make a dynamic height table view cell' will tell you to put in this two line :</p><!--kg-card-begin: code--><pre><code class="language-swift">tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 80
</code></pre><!--kg-card-end: code--><p></p><p>Putting these two lines seems easy, but somehow the cell refuse to expand to the right height even when the label exceed one line, and before you know it you have already spent two hours wrangling with the constraints inside the cell and still not working.</p><p></p><p>The key to making this work is actually setting enough constraints for the elements inside the cell so that <strong>Auto layout knows how to calculate the height of the cell</strong>.</p><p></p><p>To let Auto Layout know how to calculate the height of the cell, we need to <strong>create sufficient constraints from top of the cell's content view to the bottom</strong>.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/heightConstraint.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p>For the cell above with two labels, we need to define the vertical spacing constraint from author name label to the cell content view top (<strong>A</strong>), the vertical spacing between author name label and quote label (<strong>C</strong>) and bottom constraint from quote label to the cell content view bottom (<strong>E</strong>).</p><p></p><p><strong>A</strong> :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/constraintContentView.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/constraintContentView2.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p><strong>C</strong> :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/constraintContentView3.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/constraintContentView4.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p><strong>E</strong> :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/constraintContentView5.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/constraintContentView6.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p>As for the height of author name label (<strong>B</strong>) and height of quote label (<strong>D</strong>), we do not need to set a height constraint for them as UILabel has <a href="https://fluffy.es/what-is-intrinsic-content-size/">intrinsic content size</a>. UILabel has intrinsic content size, this means that its width and height can be derived from the text string of the label and the font type and font size used.</p><p></p><p>As long as Auto Layout is able to calculate the height of the cell from top to bottom, it will show dynamic height cell nicely.</p><p></p><p>The overlapping example at the start of this article happens because I didn't define the vertical spacing constraint between author name label and quote label (<strong>C</strong>) :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/heightConstraintIncomplete.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/clipped.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p>This makes Auto Layout unable to calculate the height of the cell.</p><p></p><p>When Auto Layout is unable to calculate the height of the cell due to insufficient constraint, it will use the default row height or the row height you have defined in Interface Builder, or the height derived from heightForRowAt function.</p><p></p><p>If we add another image view in the middle, then here's the vertical constraints we need to set (colored in Red, A, C, D, E and G) :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2020/70-self-sizing-cell/threeElements.png" class="kg-image" alt="How to implement dynamic height table view cell (self sizing)"></figure><!--kg-card-end: image--><p></p><p>Image view without an image selected has no intrinsic content size, hence we need to set an explicit height constraint for it.</p><p></p><p>The key to achieve dynamic height table view cell is to <strong>let Auto Layout know how to calculate the height of the cell</strong>.</p><p></p><p>This article is an excerpt from the book <a href="http://autolayout.fluffy.es/?ref=scroll">Making Sense of Auto Layout</a> , if you feel like you have no idea what you are doing when adding / removing / editing constraints,  give the sample chapter a try!</p><!--kg-card-begin: markdown--><p><strong>Download the sample dynamic cell height project</strong></p>
<p>Not sure if you are doing it right on the self sizing cell height?</p>
<p>Download the sample project and compare it yourself!</p>
<p><a href="https://github.com/fluffyes/dynamicHeightTable">https://github.com/fluffyes/dynamicHeightTable</a></p>
<p><img src="https://github.com/fluffyes/dynamicHeightTable/raw/master/demo.png" alt="How to implement dynamic height table view cell (self sizing)"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Move view when keyboard is shown (guide)]]></title><description><![CDATA[<h1></h1><p><a href="#tldr">Just want the code to move the view up? </a></p><p><a href="#tldrscroll">Just want the code to scroll the scrollview up?</a></p><p></p><p></p><p>Most likely you have worked on an app with multiple textfields, and when the keyboard show up, there's a chance that your text field will get covered by the keyboard! One solution</p>]]></description><link>https://fluffy.es/move-view-when-keyboard-is-shown/</link><guid isPermaLink="false">5e1a04352b012b0b9547f3b2</guid><category><![CDATA[autolayout]]></category><category><![CDATA[delegate]]></category><category><![CDATA[scrollview]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Sat, 11 Jan 2020 17:46:44 GMT</pubDate><media:content url="https://fluffy.es/content/images/2020/01/cover.png" medium="image"/><content:encoded><![CDATA[<h1></h1><img src="https://fluffy.es/content/images/2020/01/cover.png" alt="Move view when keyboard is shown (guide)"><p><a href="#tldr">Just want the code to move the view up? </a></p><p><a href="#tldrscroll">Just want the code to scroll the scrollview up?</a></p><p></p><p></p><p>Most likely you have worked on an app with multiple textfields, and when the keyboard show up, there's a chance that your text field will get covered by the keyboard! One solution for this is to move the view up when a keyboard is presented.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/keyboard.png" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>Right before a keyboard is shown on the screen, iOS will send a notification named <strong>UIResponder.keyboardWillShowNotification</strong> to your current active UIResponder (can be your view or view controller). The notification contains information of the keyboard such as the size of they keyboard, which you can use to calculate how much distance should the view move upward.</p><p></p><p>We can get the frame of the keyboard by accessing the key <strong>UIResponder.keyboardFrameEndUserInfoKey</strong> from the userInfo dictionary of the notification. I noticed some of the StackOverflow answers use the key <em>keyboardFrameBeginUserInfoKey</em> instead of keyboardFrameEndUserInfoKey , which might produce incorrect output as keyboardFrameBeginUserInfoKey contains the frame info of the keyboard <strong>before</strong> the keyboard animation starts (animation of keyboard move up from bottom of the screen).</p><p></p><p><a href="https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html">Apple documentation</a> recommends using keyboardFrameEndUserInfoKey as this contain the frame of the keyboard <strong>after</strong> the animation has ended.</p><!--kg-card-begin: code--><pre><code class="language-swift">// the frame of the keyboard
let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
</code></pre><!--kg-card-end: code--><p><br></p><p>We can then use this keyboardSize's height to move the root view Y origin upward like this :</p><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift

// in ViewDidLoad, observe the keyboardWillShow notification
override func viewDidLoad() {
    super.viewDidLoad()
  
    // call the 'keyboardWillShow' function when the view controller receive notification that keyboard is going to be shown
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}

@objc func keyboardWillShow(notification: NSNotification) {       
    guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
       // if keyboard size is not available for some reason, dont do anything
       return
    }
  
  // move the root view up by the distance of keyboard height
  self.view.frame.origin.y = 0 - keyboardSize.height
}
</code></pre><!--kg-card-end: code--><p><br></p><p>And when user dismiss the keyboard, another notification will be fired, <strong>UIResponder.keyboardWillHideNotification</strong>. We then observe for this notification and move back the root view frame origin to <strong>0</strong> (it's original origin is zero).</p><!--kg-card-begin: html--><span id="tldr"></span><!--kg-card-end: html--><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift

// in ViewDidLoad, observe the keyboardWillShow notification
override func viewDidLoad() {
    super.viewDidLoad()
  
    // call the 'keyboardWillShow' function when the view controller receive the notification that a keyboard is going to be shown
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
  
      // call the 'keyboardWillHide' function when the view controlelr receive notification that keyboard is going to be hidden
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}

@objc func keyboardWillShow(notification: NSNotification) {
        
    guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
       // if keyboard size is not available for some reason, dont do anything
       return
    }
  
  // move the root view up by the distance of keyboard height
  self.view.frame.origin.y = 0 - keyboardSize.height
}

@objc func keyboardWillHide(notification: NSNotification) {
  // move back the root view origin to zero
  self.view.frame.origin.y = 0
}
</code></pre><!--kg-card-end: code--><p><br></p><p>This will produce the output like below :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/vieworigin.png" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>If you just want to move up the view origin y when keyboard is shown, this should do the trick. However this simple approach might push some textfield too high of the screen :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/toohigh.gif" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>In this scenario, the username field is too high far off the screen and user can't view what they are typing! 😅</p><p></p><p>One of the solution for this scenario is to check if the text field will be covered by the keyboard when the keyboard appear, if it will be covered , then move the whole view up; if not, then just leave the view as it is and show the keyboard since the text field is still visible.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/shouldmove.png" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>How can we check if a textfield will get covered by the keyboard? First we get the <strong>bottomY value of the textfield</strong> (the bottom edge of the textfield), and compare it to the <strong>visible range</strong> (the screen area that is still visible after keyboard is presented). If the bottomY value is larger than the visible range (ie. the bottomY value of textfield is larger than the bottomY value of visible range), then only we move the view up.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/checkframe.png" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>As there are multiple textfields on the screen, how do we get the bottomY of the <strong>current selected textfield</strong> by the user?</p><p></p><p>We can declare a variable to keep track of the current active textfield, say <strong>activeTextField</strong>, and assign the selected textfield to this variable when user select a textfield.</p><p></p><p>We set the textfield delegate to self (to the view controller), and we listen to when user select a textfield in the UITextFieldDelegate function, <strong>textFieldDidBeginEditing</strong>.</p><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift
class ViewController: UIViewController {

  @IBOutlet weak var usernameTextField: UITextField!

  @IBOutlet weak var passwordTextField: UITextField!

  // ... other text field

  // to store the current active textfield
  var activeTextField : UITextField? = nil
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // ....
    
    // add delegate to all textfields to self (this view controller)
    usernameTextField.delegate = self
    passwordTextField.delegate = self
    emailTextField.delegate = self
  }
}

extension ViewController : UITextFieldDelegate {
  // when user select a textfield, this method will be called
  func textFieldDidBeginEditing(_ textField: UITextField) {
    // set the activeTextField to the selected textfield
    self.activeTextField = textField
  }
	
  // when user click 'done' or dismiss the keyboard
  func textFieldDidEndEditing(_ textField: UITextField) {
    self.activeTextField = nil
  }
}

</code></pre><!--kg-card-end: code--><p><br></p><p>And then we compare the activeTextField's maxY (bottomY) value to the visibleRange in the <strong>keyboardWillShow</strong> function :</p><!--kg-card-begin: code--><pre><code class="language-swift">@objc func keyboardWillShow(notification: NSNotification) {

  guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {

    // if keyboard size is not available for some reason, dont do anything
    return
  }

  var shouldMoveViewUp = false

  // if active text field is not nil
  if let activeTextField = activeTextField {

    let bottomOfTextField = activeTextField.convert(activeTextField.bounds, to: self.view).maxY;
    
    let topOfKeyboard = self.view.frame.height - keyboardSize.height

    // if the bottom of Textfield is below the top of keyboard, move up
    if bottomOfTextField &gt; topOfKeyboard {
      shouldMoveViewUp = true
    }
  }

  if(shouldMoveViewUp) {
    self.view.frame.origin.y = 0 - keyboardSize.height
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>Build and run your app, you should see the view only move up if the text field selected is located near the keyboard :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/moveifneeded.gif" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>If your view have more than 3 textfields, I recommend putting them inside a scrollview (<a href="https://fluffy.es/scrollview-storyboard-xcode-11/">You can check out how to place UI inside scrollview in Storyboard here</a>) and use some code to scroll up the scrollview when a keyboard is shown, we will explain more about this in the next section.</p><!--kg-card-begin: markdown--><p><strong>Download the sample keyboard move up project. (normal + scrollview)</strong></p>
<p>Not sure if you are doing it right on handling the view when keyboard shows?</p>
<p>Download the sample project and compare it yourself!</p>
<p><a href="https://github.com/fluffyes/keyboardmoveup/archive/master.zip">https://github.com/fluffyes/keyboardmoveup/archive/master.zip</a></p>
<!--kg-card-end: markdown--><p></p><h2 id="scrolling-up-scrollview-when-keyboard-is-shown">Scrolling up scrollview when keyboard is shown</h2><p>Assuming you have set up a scrollview with textfields inside it like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/scrollView.png" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>We can add bottom padding into the scrollview using its <strong>contentInset</strong> property, so the content inside will be moved up when a keyboard is shown.</p><p></p><p>You can think of contentInset as an additional padding extending from the content area edges.</p><p></p><p>Here's an illustration on how contentInset works, and how to use it to adapt for the keyboard.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/contentInset.png" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>When the keyboard will show / will hide notification is observed, we can modify the contentInsets of the scrollview :</p><!--kg-card-begin: html--><span id="tldrscroll"></span><!--kg-card-end: html--><!--kg-card-begin: code--><pre><code class="language-swift">// ViewController.swift
class ScrollViewController: UIViewController {

  @IBOutlet weak var scrollView: UIScrollView!
    
  override func viewDidLoad() {
    super.viewDidLoad()

      // Do any additional setup after loading the view.
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
    
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
  }
  
  @objc func keyboardWillShow(notification: NSNotification) {
    guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
    else {
      // if keyboard size is not available for some reason, dont do anything
      return
    }

    let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height , right: 0.0)
    scrollView.contentInset = contentInsets
    scrollView.scrollIndicatorInsets = contentInsets
  }

  @objc func keyboardWillHide(notification: NSNotification) {
    let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
		
    
    // reset back the content inset to zero after keyboard is gone
    scrollView.contentInset = contentInsets
    scrollView.scrollIndicatorInsets = contentInsets
  }
}
</code></pre><!--kg-card-end: code--><p><br></p><p>The result will look like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/69-move-view-when-keyboard-shown/scrollUp.gif" class="kg-image" alt="Move view when keyboard is shown (guide)"></figure><!--kg-card-end: image--><p></p><p>Pretty nifty!</p><!--kg-card-begin: markdown--><p><strong>Download the sample keyboard move up project. (normal + scrollview)</strong></p>
<p>Not sure if you are doing it right on handling the view when keyboard shows?</p>
<p>Download the sample project and compare it yourself!</p>
<p><a href="https://github.com/fluffyes/keyboardmoveup/archive/master.zip">https://github.com/fluffyes/keyboardmoveup/archive/master.zip</a></p>
<!--kg-card-end: markdown--><h2 id="further-reading">Further Reading</h2><p><a href="https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html">Apple official documentation on Keyboard management</a></p>]]></content:encoded></item><item><title><![CDATA[How to use scroll view in Interface Builder / Storyboard (Xcode 11)]]></title><description><![CDATA[<p>Table of contents:</p><p></p><ol><li><a href="#structure">Structure of scroll view</a></li><li><a href="#guide">Content Layout guide and Frame Layout guide</a></li><li><a href="#stepone">Step 1: Put a scroll view into the view controller and set constraints</a></li><li><a href="#steptwo">Step 2 : Put a view inside the scroll view and set constraints</a></li><li><a href="#stepthree">Step 3: Placing UI Elements inside Content view</a></li></ol><h1></h1><p>Before moving on</p>]]></description><link>https://fluffy.es/scrollview-storyboard-xcode-11/</link><guid isPermaLink="false">5e09dcee2b012b0b9547f346</guid><category><![CDATA[scrollview]]></category><category><![CDATA[autolayout]]></category><dc:creator><![CDATA[Axel Kee]]></dc:creator><pubDate>Mon, 30 Dec 2019 11:43:20 GMT</pubDate><media:content url="https://fluffy.es/content/images/2019/12/card.png" medium="image"/><content:encoded><![CDATA[<img src="https://fluffy.es/content/images/2019/12/card.png" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"><p>Table of contents:</p><p></p><ol><li><a href="#structure">Structure of scroll view</a></li><li><a href="#guide">Content Layout guide and Frame Layout guide</a></li><li><a href="#stepone">Step 1: Put a scroll view into the view controller and set constraints</a></li><li><a href="#steptwo">Step 2 : Put a view inside the scroll view and set constraints</a></li><li><a href="#stepthree">Step 3: Placing UI Elements inside Content view</a></li></ol><h1></h1><p>Before moving on to how to use a scrollview in storyboard / XIB, I think it's crucial to discuss the structure of scroll view to prevent getting "ambiguous scrollable content width / height" error message in Interface Builder.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/scrollviewAmbiguity.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><!--kg-card-begin: html--><span id="structure"></span><!--kg-card-end: html--><h2 id="structure-of-scrollview">Structure of scrollview</h2><p>Scrollview works by having a scrollable content area, like this:</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/scrollviewStructure.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>In order for scrollview to work in Auto Layout, <strong>scrollview must know its scrollable content</strong> (scrollview content) <strong>width</strong> and <strong>height</strong> , and also the <strong>frame (X, Y , Width, Height) of itself</strong>, ie. where should the superview place scrollview and what size.</p><p></p><p>Xcode will show you the error "ambiguous scrollable content width / height" if Auto Layout doesn't know what is the width and height for the <strong>scrollable content</strong> inside scrollview.</p><!--kg-card-begin: html--><span id="guide"></span><!--kg-card-end: html--><h2 id="content-layout-guide-and-frame-layout-guide">Content Layout guide and Frame Layout guide</h2><p>In Xcode 11, Apple has introduced <strong>Content Layout Guide</strong> and <strong>Frame Layout Guide</strong> for Scroll View in Interface Builder, which makes it easier to design UI inside Scroll View. This is enabled by default.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/contentLayoutGuideCheckbox.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>To layout a scrollview correctly, we will need two set of constraints :</p><ol><li><strong>Constraints that set the size and position of the scroll view relative to its superview</strong>. </li><li><strong>Constraints that set the size of the scrollable content area inside the scrollview</strong>, so the scroll view can know the scrollable content dimension and render correctly.</li></ol><p></p><p>The <strong>Frame Layout guide</strong> relates to the position (x, y) and size (width, height) of the scrollview itself, relative to the superview it was placed in.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/frameLayoutGuide.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>The <strong>Content Layout guide</strong> relates to the size of the scrollable content area inside the scroll view.</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/contentLayoutGuide.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>The challenging part is to define enough constraints to let Auto Layout calculate the scrollable content area (Content Layout) width and height, which we will go into in the next section.</p><p></p><!--kg-card-begin: markdown--><p><strong>Download the sample scroll view project</strong><br>
Not sure if you are doing it right on the scroll view layout?</p>
<p>Download the sample project and compare it yourself!<br>
<a href="https://github.com/fluffyes/scrolla/archive/master.zip">https://github.com/fluffyes/scrolla/archive/master.zip</a></p>
<!--kg-card-end: markdown--><p></p><!--kg-card-begin: html--><span id="stepone"></span><!--kg-card-end: html--><h2 id="step-1-put-a-scroll-view-into-the-view-controller-and-set-constraints">Step 1: Put a scroll view into the view controller and set constraints</h2><p>First, place a scroll view into the view controller, and set some constraints for it so <a href="https://fluffy.es/how-auto-layout-calculates-view-position-and-size/">Auto Layout can know how to position and size it</a>.</p><p></p><p>Usually I will pin it to the edges (top, leading, trailing and bottom) of the safe area like this :</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/scrollViewConstraints.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>After setting constraints, you will notice there's red lines and errors because Auto Layout don't know the scrollable content size of the scroll view yet. No worries on this as we are going to put a view inside it next.</p><p></p><!--kg-card-begin: html--><span id="steptwo"></span><!--kg-card-end: html--><h2 id="step-2-put-a-view-inside-the-scroll-view-and-set-constraints">Step 2 : Put a view inside the scroll view and set constraints</h2><p></p><p>To simplify the height calculation of the scroll content area, add a view <strong>inside</strong> the scrollview , and add four constraints (leading, trailing, top and bottom, set it to 0) from the view to the <strong>scrollview's content layout guide</strong>.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/normalView.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p>Select the view inside scrollview, press and hold <strong>control</strong> and drag it to the <strong>content layout guide</strong>.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/controlDragViewToCLG.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>Then in the constraints selection popup, hold <strong>shift</strong> to select multiple constraints, select leading / top / trailing / bottom constraint.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/selectFourEdges.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>After creating these constraints, <strong>edit their constant values to 0</strong>, so that the view will be pinned to the leading/top/trailing/bottom edges of the content layout guide :</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/editConstraintToZero.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>Let's name this view as '<strong>Content View</strong>' as this view contains UI elements (content) for the scrollview. Auto Layout will use the <strong>content view</strong>'s' width and height to calculate the scrollable area width / height for the scrollview.</p><p></p><p>After changing the four constraints value to 0 , you can notice that the red lines are still there. This is because despite pinning the edge of the view to content layout guide, Auto Layout still doesn't know what is the width and height of the view.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/contentViewRedLines.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>As we want to make the scrollview scroll only vertically (it does not scroll horizontally), the scrollable content area width should be equal or lesser than the scrollview size width. Next, we will create an <strong>equal width constraint</strong> between the <strong>view</strong> and the <strong>scrollview's frame layout guide</strong>, so the scrollable content area has the same width with the scrollview.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/viewToFLG.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/equalWidthSelection.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>Xcode might create a proportional width constraint for you, if thats the case, edit the 'proportional width' constraint multiplier value to "1" so that it will be equal width.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/equalOne.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>Now that we have defined the width for the scrollable content area (content layout guide), what's left now is the height of the scrollable content area.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/redLineHeight.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>We will set an explicit height constraint value to the content view for now. We will remove it later once we have finished placing elements in it as we want to make the scrollview have dynamic height, based on the content inside it (retrieved from API etc).</p><p></p><p>Let's set the height for the content view (the view inside scrollview) to 1000 :</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/1000height.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>Now we have set the width and height for the view, and Auto Layout can calculate the scrollable content area size, yay! We will remove the explicit height constraint (1000 pt) after finish adding UI elements.</p><p></p><p>You can scroll the scrollview in the Storyboard / Interface builder! Select the view inside scrollview in Document Outline, then scroll using your mouse or trackpad, you can see the view move.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/scrollIt.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>If you don't want to keep scrolling and want to see the whole scrollable content area, you can elongate the view controller height by setting it to freeform with bigger height like this :</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/freeformHeight.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><ol><li>Select the view controller</li><li>Select "Size Inspector"</li><li>For "Simulated Size", select "Freeform"</li><li>Set "1100" as its height</li></ol><p></p><p>You should see the view controller elongated to 1100 pt height now. Now you can start placing labels, images etc inside the content view (the view inside scrollview). After placing, be sure to create constraints from the top element to the bottom. You can increase the height of view controller in simulated size and height constraint of the content view as you need.</p><p>We will go into detail on how to create constraints from top to bottom inside the content view in the next section.</p><p></p><!--kg-card-begin: html--><span id="stepthree"></span><!--kg-card-end: html--><h2 id="step-3-placing-ui-elements-inside-content-view">Step 3: Placing UI Elements inside Content view</h2><p></p><p>Here's how it looks like after placing labels and image inside the content view in scrollview:</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/scrollViewInterfaceBuilder.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>You can start placing elements inside the content view like label, images ,etc. Then add constraints for them so that Auto Layout can calculate their X position, Y position, width and height (can skip width and height if they have <a href="https://fluffy.es/what-is-intrinsic-content-size/">intrinsic content size</a>). Remember that the constraints should be between each UI elements and the content view (ie. the view inside scrollview), not with the scrollview.</p><p>Your constraints might look like this:</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/constraints.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>Remember to set enough constraints so that Auto Layout can calculate the scrollable content height after removing the explicit height.</p><p></p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://iosimage.s3.amazonaws.com/2019/68-scrollview-xcode11/constraintExplanation.png" class="kg-image" alt="How to use scroll view in Interface Builder / Storyboard (Xcode 11)"></figure><!--kg-card-end: image--><p></p><p>The important note is that the <strong>vertical constraints must be connected from the top edge of content view to the bottom edge of content view</strong>, this is to ensure Auto Layout can calculate the scrollable content area height.</p><p></p><p>Now remove the explicit height constraint set on the content view, you should get all blue lines if you have set the constraints correctly.</p><p></p><p>After setting up constraints, you can create outlet for the labels and set the text value to the JSON value gotten from web API. Your scrollable content area height will expand based on the intrinsic content size of the labels. </p><p></p><p>This article is an excerpt from the book <a href="http://autolayout.fluffy.es?ref=scroll">Making Sense of Auto Layout</a> , if you feel like you have no idea what you are doing when adding / removing / editing constraints,  give the sample chapter a try!</p><!--kg-card-begin: markdown--><p><strong>Download the sample scroll view project</strong><br>
Not sure if you are doing it right on the scroll view layout?</p>
<p>Download the sample project and compare it yourself!<br>
<a href="https://github.com/fluffyes/scrolla/archive/master.zip">https://github.com/fluffyes/scrolla/archive/master.zip</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>