<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Artem Poluektov</title>
    <description>The latest articles on DEV Community by Artem Poluektov (@artem_poluektov).</description>
    <link>https://dev.to/artem_poluektov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1048055%2Fc2b799ce-2aa5-4e4c-96a8-29f8069bc4ec.png</url>
      <title>DEV Community: Artem Poluektov</title>
      <link>https://dev.to/artem_poluektov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/artem_poluektov"/>
    <language>en</language>
    <item>
      <title>Launching new mobile app in 2 months with Agile transformation</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Fri, 24 Mar 2023 12:01:09 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/launching-new-mobile-app-in-2-months-with-agile-transformation-4bl</link>
      <guid>https://dev.to/artem_poluektov/launching-new-mobile-app-in-2-months-with-agile-transformation-4bl</guid>
      <description>&lt;p&gt;I’m going to tell you a story of how my team launched iOS &amp;amp; Android app for ticketing service in just 2 months, performing the transition from Waterfall to Agile without a lot of pain. I’ll start by describing the initial point of the project, how software development processes worked before and will to highlight key activities which helped us to succeed. This article is not intended to replace Agile manifest or &lt;a href="https://www.amazon.com/Scrum-Doing-Twice-Work-Half/dp/038534645X"&gt;The Book of Scrum&lt;/a&gt; by Jeff Sutherland. Instead, I’ll tell you how we changed our work process to deliver product faster without hiring an army of Agile coaches &amp;amp; scrum masters. This article would also be useful for those of you, guys, who thought about starting your own home project. I’ll show you what is worth focusing on at the specific moment of time and what isn’t. So, let’s begin :)&lt;/p&gt;

&lt;h2&gt;
  
  
  How it all started
&lt;/h2&gt;

&lt;p&gt;It all started about 8 years ago. I’d just joined a huge media company in Russia which had a lot of different products: few online newspapers, entertainment services, sports portal, etc. And we also had this ticketing service I’m telling you about. The main feature of the service was selling e-tickets for cinemas, theatres, sports events online providing users and ability to book the best seats with no need visiting cash deck in advance.&lt;/p&gt;

&lt;p&gt;Fortunately, we already had desktop and mobile websites running. And the service was pretty successful. As always, with some limitations. Not going to bother you with numbers, will just say that mobile website didn’t perform as expected. The primary reasons for that were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outdated UI design&lt;/li&gt;
&lt;li&gt;Usability issues&lt;/li&gt;
&lt;li&gt;Inability to view a seat map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sure, it was possible to fix first &amp;amp; second issues but providing a fully-functional seat map and making offline tickets storage definitely required a mobile app. You should ask here how are users purchasing tickets without viewing a seat map on the mobile website. And that’s a great question. Our colleagues from backend team developed an algorithm which automatically selected seats as close to center as possible. Sometimes it worked great, sometimes not, sometimes customer wanted to book seats on last “kissing” row and was unable to do so. But benefits of the apps comparing to websites is an excellent idea for another article, so, here I’ll just say we were pretty sure that we need to launch the app.&lt;/p&gt;

&lt;p&gt;In addition to websites, we had an XML API for partners’ sites letting them get a list of places, events &amp;amp; schedules with an ability to purchase tickets in the pop-up window. Now you should understand the initial point of our project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimum Valuable Product
&lt;/h2&gt;

&lt;p&gt;As I mentioned above, we widely used Waterfall approach in our work. We had separated iOS, Android, design, QA, backend teams and an army of project managers. So, when my project manager first approached me with this exciting new project, I was more than happy to start my very first product in this company from scratch. But all my happiness was gone after the initial meeting. What happened? The primary purpose for this meeting was to estimate release date based on very few UI designs we had. We found out that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UI design wasn't complete, a lot of screens were missing&lt;/li&gt;
&lt;li&gt;Those UIs we got were to complex, featuring a lot of complex gestures &amp;amp; animations, being too hard for users to understand and use and very hard to implement&lt;/li&gt;
&lt;li&gt;Technical requirements weren't even close to being complete also&lt;/li&gt;
&lt;li&gt;Backend team was busy for the next few months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why we found we can’t make estimations to put a resource reservation on mobile developers for this project. To be honest, we actually were unable to predict a release date. Sure, we could possibly say that it will take approximately 6 months. But when you have no idea on overall volume and complexity of such a huge app, your estimations would be pretty inaccurate. And that’s a pretty usual situation for Waterfall teams. To fix this, I proposed launching Minimum Valuable Product (MVP) first with our own UI design and a small number of critical features in the first release, leaving all the others for the next versions of our app. The idea was looking pretty fresh &amp;amp; exciting, we decided to give it a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Focus on what’s really important
&lt;/h2&gt;

&lt;p&gt;There were a lot of features in technical requirements and those UI design screens we’d got. We clearly understood that we just can’t develop them all maintaining the highest possible quality. And here comes Agile!&lt;/p&gt;

&lt;p&gt;We wrote down a list of all features and asked ourselves: “Which one is the most important? Without what features our app just wouldn’t work?”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uGuStwKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvq61b4w6m65ffam823k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uGuStwKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvq61b4w6m65ffam823k.png" alt="Image description" width="880" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the list of the critical features defining for our product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List of cinemas&lt;/li&gt;
&lt;li&gt;List of movies&lt;/li&gt;
&lt;li&gt;Schedule&lt;/li&gt;
&lt;li&gt;Seat map&lt;/li&gt;
&lt;li&gt;Making a purchase&lt;/li&gt;
&lt;li&gt;Purchased tickets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That easy. Just 6 screens. We tried to reduce the number of screens as much as possible to make purchase process simpler &amp;amp; faster, finally, we were able to cut it to just 4 steps: pick up the cinema or movie, select the suitable time from a schedule screen, choose seats on a seat map and make a purchase. Our app just wouldn’t work without any of these features. No beautiful animations, no complex gestures, no custom controls. Instead, system-provided tabs-based navigation, standard UI controls with minimum customizations. &lt;strong&gt;We were focused on providing our customers with content instead of an interface.&lt;/strong&gt; And that’s how we actually build a great user interface which received &lt;strong&gt;A+ score at WWDC UI design labs session&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We discussed a lot of other great ideas during our work with my team, for example, we were pretty sure that storing user’s credit card data for future purchases would significantly increase a conversion rate. But it will take a lot of development hours, PCI DSS certification process isn’t easy. Instead, we decided to use WebView for payments, the same WebView on our website. So, this is one of those many features that should be held on the backlog for future releases.&lt;/p&gt;

&lt;p&gt;To estimate the overall amount of work to do and track our progress we created a screen map. It is a huge image file containing all the screens of our apps connected by arrows representing screen-to-screen transitions. Screen maps are a handy tool for building mobile apps and even desktop websites.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Focus on crucial features defining your product. And put all the others to a backlog for next releases. Remember, you’re providing your customers with content not interface.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication is the key
&lt;/h2&gt;

&lt;p&gt;Now, when we got a screen map, we were able to understand what APIs we do need. But the backend team was extremely busy, and we had a bottleneck here. Fortunately, we were able to estimate the amount of work needs to be done and [miracle happened here] we arranged to get 4 weeks of backend developer’s time.&lt;/p&gt;

&lt;p&gt;How did we work with other teams before? When you need something to be done by another team you should ask your project manager, he or she goes to that team’s manager bringing the task. It was a too long way for tasks when we had just 4 weeks. So, instead, we created a new small team solely for this project and put this our developers inside. We had our own project in JIRA, our own backlog and didn’t accept any incoming tasks from other projects to be completely focused on our app. Our team was consisted of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Me as a Team Leader creating tasks, providing communication between team members and pushing forward all the development process&lt;/li&gt;
&lt;li&gt;2 iOS developers&lt;/li&gt;
&lt;li&gt;2 Android developers&lt;/li&gt;
&lt;li&gt;Backend developer&lt;/li&gt;
&lt;li&gt;Designer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now when we had a team and knew essential features we need to develop, we needed to make sure that we’re all doing the right tasks. But what is the proper task? It’s the one which provides features to our customers in the shortest possible way. The excellent example of that approach is UI design. I asked our designer to create UIs as close to native as possible because it is familiar to users and easy to implement by developers. So, &lt;strong&gt;we made a short workshop for our designers telling them what standard UI controls &amp;amp; animations we have and how we can customize it&lt;/strong&gt;. We launched “design review” process where we discussed each screen together inside my team: designer tried to make it look better and developers — easier to implement. We were able to develop and maintain this communication process inside our team, and it brought us to success. We’d done almost the same developing APIs: backend and mobile developers were looking at UI screens and writing an API protocol convenient to both sides.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Make your teammates communicate with each other discussing the best way to implement the task. That will help them deliver better solutions faster.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Listen your teammates
&lt;/h2&gt;

&lt;p&gt;Another essential key to our success was letting our team making decisions independently without hours of meetings reducing communications chain. In the previous paragraph, I mentioned that in the process of app development you should focus on critical features trying to deliver them as soon as possible and only then move to another feature. Here I’ll show you a few examples of small features we’d made in the process on main features development. These weren’t planned but were so easy to implement so we decided to include them in our first release. And all of them dramatically improved user experience and conversion rate.&lt;/p&gt;

&lt;p&gt;The first feature was offline search. We had a search field on our website, but it was full-text search providing all results across all categories (i.e., one search field for movies &amp;amp; places). We had this feature in our backlog for the second release, but my team came to me complaining that it takes a too long time to find a test entity in cinemas list. This actually slowed down all the development. We discussed this issue and found out that we already had a list of all cinemas in our app and now could easily add a system-provided search bar on top of the list. It took about 2 hours for each platform without any changes to the backend and dramatically improved the usability of the app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JBTK3AcZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0aizruw9eo3pqfpb5ir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JBTK3AcZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0aizruw9eo3pqfpb5ir.png" alt="Image description" width="880" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second feature again originates from my teammates’ ideas. Sometimes it took up to a minute for ticketing system to provide actual seat map. It gave a lot of pain to developers who tried to test what they’ve done. Depending on cinema, the ticketing system stopped selling tickets sometime before the movie start, or there could be no free seats left. And the user has to wait while seat map loading to get this information. We found a simple solution for that: to add a check on the backend and flag to API for each time in schedule whether or not it’s available for purchase. Price labels on schedule screen was another bright idea. Initially, we haven’t thought about that. But some guys preferred more affordable tickets on a different time, it was insight from real-life behavior. If the user can see the price on a schedule screen, he or she doesn’t have to wait while seat map is loading on pricy times and could go straight to cheaper tickets. By implementing these features, we not only improved usability but also reduced the number of requests to our backend. So, it was a double win.&lt;/p&gt;

&lt;p&gt;Another quick win was rounded seats on a seat map. Initially, our designer drew square seat icons with rounded corners which looked pretty good. But someday one of my guys found a cinema with rounded rows on a seat map. It seemed to look bad, very bad. So, we tried to create some complex equations to calculate seat rotation angle based on the radius of row rounding. Eventually, I found that we could make our seats round instead of square and we don’t need to rotate round seat because it’s round.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Your team is a very important focus group for your product. They are actually the first users of new features. Listen to them, give them the freedom of making decisions and they will bring you a lot of bright ideas!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  After the first release
&lt;/h2&gt;

&lt;p&gt;We had a lot of features in our backlog for the next release and it was extremely hard to find out which of them we should implement first. We decided to start studying users’ behavior. The entire purpose of our app is to help customers purchasing a ticket. So, the primary goal of our second release was to provide us with insights on how do customers looking for tickets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do they start with a cinema or movie selection?&lt;/li&gt;
&lt;li&gt;How much time do our customers spend on the specific screen?&lt;/li&gt;
&lt;li&gt;Why does the user close the payment screen?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After we’d collected some data, we were able to prioritize our backlog. For example, trailers, featured movies and frequently-visited cinemas were selected for our third release. Mostly because these features had a small estimated development time and some positive effect on conversion rate. Of course, storing credit card data would impact conversion much more, but all three mentioned features were completed in a week while it took more than 2 months to get PCI DSS certification necessary for card data storage. I don’t say that you should only do smaller tasks. Instead, it’s always better to find a balance between quick wins and major tasks promising long-term benefits.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I would recommend studying user’s behavior from the very first releases to be sure your team going in the right direction. And adjust your backlog [prioritization] based on those insights you would get from the data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Finally, we’d launched the app. After just 2 months. And it was extremely short timing comparing to other products. Yes, it wasn’t fully functional as stakeholders wanted it, but it was just the first step.&lt;/p&gt;

&lt;p&gt;Our new approach wasn’t near close to Scrum, it was more like Kanban, which is great when you need to launch the new product in a short amount of time. And this transition from Waterfall wasn’t any pain, we actually enjoyed the new way of working together, impacting app we built and getting the new results on a weekly basis.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>iOS development for beginners. Part 3: preparing for the interview</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Fri, 24 Mar 2023 11:53:02 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/ios-development-for-beginners-part-3-preparing-for-the-interview-2ic2</link>
      <guid>https://dev.to/artem_poluektov/ios-development-for-beginners-part-3-preparing-for-the-interview-2ic2</guid>
      <description>&lt;p&gt;In the final part of this tutorial, I’m going to guide you on how to prepare for your first interview for a junior developer position. As I mentioned in this article, I attended hundreds of interviews over my career on both roles. I started my career of iOS developer about 10 years ago, for sure, requirements are a bit higher today, but if you would prepare well, you’ll make it. Few times I even hired guys with minimal experience when I had only middle-level positions. How did it happen? They proved that they’re smart and would get things done. Check this article to get to know what to do and what to expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The necessary knowledges
&lt;/h2&gt;

&lt;p&gt;Before attending an interview, make sure that you’re ready. Below I would provide you with a short checklist of most important areas of usual discussion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swift language features
&lt;/h2&gt;

&lt;p&gt;As I mentioned in the first part of this tutorial, Swift is #1 choice for iOS App Development. And you need to know how to use it. Here is a list of the most important things you should be able to write even on a paper:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a variable, understanding let/var difference&lt;/li&gt;
&lt;li&gt;Understanding how Optionals work, if let condition&lt;/li&gt;
&lt;li&gt;Weak references, some basic memory management knowledge would be a good addition also&lt;/li&gt;
&lt;li&gt;Working with arrays and dictionaries (create, count, insert, delete)&lt;/li&gt;
&lt;li&gt;Working with strings (create, count symbols, add substring)&lt;/li&gt;
&lt;li&gt;Creating your own structs, classes, and methods&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;If..else&lt;/code&gt; and &lt;code&gt;switch..case&lt;/code&gt; conditions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;For&lt;/code&gt; cycles&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Xcode
&lt;/h2&gt;

&lt;p&gt;Xcode provides you a very user-friendly interface, so, it’s easy to learn how to use it. You should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new project and add a third-part dependency&lt;/li&gt;
&lt;li&gt;Run your app on iOS simulator and on device&lt;/li&gt;
&lt;li&gt;Debug your app (understand what’s wrong using console output)&lt;/li&gt;
&lt;li&gt;Submit your app to the App Store&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  UIKit
&lt;/h2&gt;

&lt;p&gt;UIKit framework is a core part of iOS SDK. It contains UI components and controls. You should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create navigation model for your app (&lt;code&gt;UINavigationController&lt;/code&gt;, &lt;code&gt;UITabBarController&lt;/code&gt;, &lt;code&gt;UISplitViewController&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Understand the difference between modal presentation of new &lt;code&gt;ViewController&lt;/code&gt; and &lt;code&gt;UINavigationController&lt;/code&gt; push/pop mechanics&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;ViewController&lt;/code&gt; both in code and in Storyboard Editor&lt;/li&gt;
&lt;li&gt;Understand the difference between &lt;code&gt;UITableViewController&lt;/code&gt;, &lt;code&gt;UICollectionViewController&lt;/code&gt; and &lt;code&gt;UIViewController&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Understand reusable cells mechanics of &lt;code&gt;UITableViewController&lt;/code&gt; and &lt;code&gt;UICollectionViewController&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use Auto-Layout constraints to build a user interface&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;UIAppearance&lt;/code&gt; proxy to customize colors of your app (in code)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  iOS SDK
&lt;/h2&gt;

&lt;p&gt;iOS SDK consists of dozens of frameworks with thousands of methods. Nobody can know them all. And you don’t need it too. All you need is to get and experience using a few of them and know where to find documentation for others. Here’s a list of most commonly used frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Foundation (base layer of functionality, networking, data structures such as arrays, dictionaries, strings, etc.)&lt;/li&gt;
&lt;li&gt;UIKit (user interface)&lt;/li&gt;
&lt;li&gt;MapKit&lt;/li&gt;
&lt;li&gt;CoreLocation (GPS &amp;amp; accessing user’s location)&lt;/li&gt;
&lt;li&gt;CoreData (local database, I prefer Realm)&lt;/li&gt;
&lt;li&gt;UserNotifications&lt;/li&gt;
&lt;li&gt;LocalAuthentication (TouchID, FaceID)&lt;/li&gt;
&lt;li&gt;GCD (multithreading) is also good to know, at least you should be familiar with &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;asyncAfter&lt;/code&gt;, main and background queues, you’ll find more information in &lt;a href="https://www.kodeco.com/28540615-grand-central-dispatch-tutorial-for-swift-5-part-1-2"&gt;this article&lt;/a&gt;
I recommend you to use some features of each of these frameworks in your home projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3rd part libraries
&lt;/h2&gt;

&lt;p&gt;Sure, every team has its own list of libraries to use, but you should be able to discuss some most common you used in your pet projects. For your interview, I think, only Alamofire is necessary. Don’t forget to include all libraries you use into your CV, and be ready to explain your choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building your CV
&lt;/h2&gt;

&lt;p&gt;The first significant step is to pass the screening stage and get an appointment for the interview. On this stage recruiter or HR specialist evaluates your CV on being either appropriate or not for an open position they have. The well-prepared CV would help you to pass this stage successfully. The good CV contains this information in 1 (one) page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your name and your current location&lt;/li&gt;
&lt;li&gt;Desired job title (usually it would be Junior iOS developer)&lt;/li&gt;
&lt;li&gt;Your education, major fields of study and graduation status&lt;/li&gt;
&lt;li&gt;Work experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Company doesn’t need to know your home address, city or state would be well enough to understand whether or not you’re located in the same area as the company’s office. Job title is another good addition to make sure your CV would not get lost.&lt;/p&gt;

&lt;p&gt;If you’re still a student, make sure to mention that and point that you would need a flexible schedule or part-time employment. Usually, you would get much more options after graduation, because the most of employers looking for full-time developers. However, working full-time with a flexible schedule is very common.&lt;/p&gt;

&lt;p&gt;If you do have a work experience not relative to the desired job, there is no need to mention that in your CV. For example, if you developed and maintained a website for your university’s department, and you’re looking for an iOS developer job, it’s still would be a good addition to your CV. However, working as a barista at a coffee shop wouldn’t help you. What you really could do is stating that you’d been an Independent Developer and now have 2 apps published in the App Store.&lt;/p&gt;

&lt;p&gt;In the work experience section you should also mention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your completed projects (with links to App Store)&lt;/li&gt;
&lt;li&gt;Your technology stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Provided list of completed apps would help you to show the employer that you have some experience, able to write code and getting the project to release. Having developed at least 2 different apps would definitely increase the number of interview invitations and also would positively affect your initial salary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s also very important to provide a list of technologies you’re familiar with. Usually, it’s better to add only those which you had used in your apps because you would need to discuss your decisions on libraries usage. Here is an example of a technology stack description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Swift
Xcode, App Store Connect
iOS SDK: UIKit, Foundation, CoreLocation, TouchID/FaceID, GCD
Libraries: Alamofire, Realm, SwiftyJSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Don’t forget to do a research
&lt;/h2&gt;

&lt;p&gt;Before sending a CV, make your research on your future employer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the company’s website, App Store, Google Play profiles to get to know about products/projects they’re working on.&lt;/li&gt;
&lt;li&gt;Ask yourself why do you want to work for this company.&lt;/li&gt;
&lt;li&gt;Try finding other opened jobs to compare salary levels with other companies.&lt;/li&gt;
&lt;li&gt;If the company is located in a different area, check the costs of living in that place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After completing your research, you should understand your motivation of joining this company and the salary level which would be comfortable for you. Be ready to answer those questions on the interview. Many guys don’t have specific numbers on their mind when I’m asking them for it. So, they’re asking me to offer them something and then they would think about my offer. That’s not how things work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare your story
&lt;/h2&gt;

&lt;p&gt;After evaluating your knowledge and completing your CV, you should prepare for the phone interview. It usually lasts for 10–15 minutes and is intended to check your background, previous projects, motivation. The on-site interview usually starts with the same discussion. To succeed in this step you need to prepare your story. Sometimes it could be useful even to write it down. The sample structure of introduction speech could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Education, why did you choose your school, profession&lt;/li&gt;
&lt;li&gt;Why did you decide to become a developer&lt;/li&gt;
&lt;li&gt;What have you done to begin your developer’s career&lt;/li&gt;
&lt;li&gt;Completed projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s important to show your motivation to work and willing to learn. Think about how you’d choose between two companies and highlight 3–5 things that most important for you. Don’t wait to be asked for that, it’s usually better to finish your story with that.&lt;br&gt;
I’m usually looking for guys who are interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building amazing product&lt;/li&gt;
&lt;li&gt;Developing his/her skills&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Working with a great team&lt;br&gt;
And not for people who:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seeking a job closer to home&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Want to get lunch compensation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Looking only for a bigger salary&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  You’ll make it!
&lt;/h2&gt;

&lt;p&gt;It wouldn’t be easy. You may have to attend a dozen interviews before getting your first job offer. Remember to make your choice carefully. It’s usually better to choose between 2 offers instead of signing in the first one you would receive. Do not expect to win from the first shot. Continue trying. You’ll make it!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>iOS development for beginners. Part 2: courses and tutorials</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Fri, 24 Mar 2023 11:42:11 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/ios-development-for-beginners-part-2-courses-and-tutorials-1b3</link>
      <guid>https://dev.to/artem_poluektov/ios-development-for-beginners-part-2-courses-and-tutorials-1b3</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/artem_poluektov/ios-development-for-beginners-part-1-initial-requirements-and-technology-trends-3mik"&gt;first part&lt;/a&gt; of this tutorial, I’d made a brief review of the initial requirements and recent technology trends. In this part I would start with a review of two ready-made iOS Development courses and later would also provide you with list of useful resources to get in touch with popular libraries, frameworks, and some other resources widely used by developers. If you’d choose to go only for the ready-made course, be sure to check my list at the end of your study for some topics you could miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  kodeco.com
&lt;/h2&gt;

&lt;p&gt;As I stated in the first part, Ray’s website is a trendy place for the beginners. When I was an iOS developer, I used this website to learn to use new frameworks and new iOS features. The tutorials were easy enough for beginners and very straightforward. However, featured some architectural anti-patterns like writing tons of code in AppDelegate breaking Single-Responsibility principle.&lt;br&gt;
&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many tutorials covering almost every topic you would need&lt;/li&gt;
&lt;li&gt;new articles every day or two&lt;/li&gt;
&lt;li&gt;prepared a study paths&lt;/li&gt;
&lt;li&gt;very detailed tutorials&lt;/li&gt;
&lt;li&gt;for the very beginners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;most videos do not feature transcription, so you have to watch it in full&lt;/li&gt;
&lt;li&gt;some video tutorials are cropped into too small parts&lt;/li&gt;
&lt;li&gt;wide usage of architectural anti-patterns&lt;/li&gt;
&lt;li&gt;paid subscription ($19.99 monthly / $179.88 for annual plan)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, kodeco.com is a pretty good source for beginners and even for junior/middle developers exploring new technologies. However, there are so many other ways to begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  CS193P by Stanford University
&lt;/h2&gt;

&lt;p&gt;Paul Hegarty works at Stanford University. With the introduction of the first version of Swift in 2014, he started the development of this course. Stanford is one of the world’s top universities in Computer Science, so it features very high-quality mark. Latest version dated Spring 2021 features almost all you need to begin writing code.&lt;br&gt;
&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;free&lt;/li&gt;
&lt;li&gt;provided by the world-class university&lt;/li&gt;
&lt;li&gt;covers almost every important topic on iOS development including architecture&lt;/li&gt;
&lt;li&gt;very focused and straightforward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;features SwiftUI instead of UIKit&lt;/li&gt;
&lt;li&gt;suitable for people familiar with programming&lt;/li&gt;
&lt;li&gt;no text transcription&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would go for Stanford as the initial point of the study. It’s free, already approved on many students and provided by one of the world’s top universities. It was also recommended by Craig Federighi, Apple’s SVP on Software Engineering. And by the day you'll complete the course, SwiftUI would become a little [or may be much] more popular.&lt;br&gt;
You’ll find more info &lt;a href="https://cs193p.sites.stanford.edu"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful resources
&lt;/h2&gt;

&lt;p&gt;This useful resources list is not intended to replace or compete with professionally prepared courses above. Instead, I’m going to focus on real-world app development and introduce you with popular techniques, frameworks, and libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development language
&lt;/h2&gt;

&lt;p&gt;As I briefly mentioned in the first part of this tutorial, Swift is definitely #1 choice for macOS &amp;amp; iOS app development. And there a lot of official documentation about it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I would start with watching &lt;a href="https://developer.apple.com/videos/play/wwdc2016/404/"&gt;WWDC 2016 video “Getting started with Swift”&lt;/a&gt; for a brief explanation of language usage and features.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.swift.org/documentation/"&gt;The official Swift guide&lt;/a&gt; would guide you from the very beginning to in-depth and modern features. You could check it anytime you need to find new ways of solving your tasks easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Interface Builder
&lt;/h2&gt;

&lt;p&gt;The second important step is to get familiar with Interface Builder, the tool which would make your life hundred times easier :) And, I also recommend to use Auto Layout, the technology to build adaptive interfaces for all available devices.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You could find all available UI controls and best practices to build great UI in &lt;a href="https://developer.apple.com/design/human-interface-guidelines/platforms/designing-for-ios/"&gt;Apple’s Human Interface Guidelines&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Watch this &lt;a href="https://developer.apple.com/videos/wwdc2017/412/"&gt;video&lt;/a&gt; on how to use Auto Layout Techniques in Interface Builder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source control
&lt;/h2&gt;

&lt;p&gt;Every team uses a source control system to keep source code safe from losing it due to technical failures, to be able to move between versions or releases and, of course, to make working together on one project easier. Almost every mobile team today uses &lt;strong&gt;Git&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find out what is Git and how it works in &lt;a href="http://rogerdudler.github.io/git-guide/"&gt;this tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Xcode has a built-in menu to create commits, for pull/push actions. However, some developers prefer SourceTree app for a simpler, more powerful and straightforward interface. Also, sometimes Xcode is getting crazy and throwing some “unknown errors” when you’re trying to commit your code. SourceTree would save you in these situations. You could &lt;a href="https://www.sourcetreeapp.com/"&gt;download this app here&lt;/a&gt; for FREE.&lt;/p&gt;

&lt;p&gt;There are many online source code storages available free of charge. Most popular are &lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt; and &lt;a href="https://bitbucket.org/"&gt;BitBucket&lt;/a&gt;. For many years GitHub provided only public repositories for free, so that anyone could see all of your code in uncompleted projects, but a few days ago they launched private repositories, so nobody could see your code until you want it. BitBucket also provides you with free private repos at no charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency management
&lt;/h2&gt;

&lt;p&gt;Don’t try to reinvent the wheel. If you need to develop a new feature, try to use existing solutions first. There are many frameworks and open-source libraries available for free. The best way to find a library is Google [Surprise :)], which would lead you to either GitHub or StackOverflow and then to GitHub. Here are a few tips on how to choose a library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check a number of stars on the library’s GitHub page. It’s usually safe to use one with 1000+ stars.&lt;/li&gt;
&lt;li&gt;Check the latest release date. You don’t want to use an outdated framework. A new iOS releases every September, so the library should be updated for new version around this date.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Popular libraries
&lt;/h2&gt;

&lt;p&gt;There are thousands of libraries available. However, only a few are really useful and popular.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Alamofire/Alamofire"&gt;Alamofire&lt;/a&gt; is number one choice for the client-server app. It makes working with network request extremely easy. Check their GitHub page for beginners guide. Moreover, they have 30K stars :)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kishikawakatsumi/KeychainAccess"&gt;KeychainAccess&lt;/a&gt; is a beautiful and easy-to-use wrapper on Apple’s Keychain framework to provide secure storage for sensitive data such as a user’s password.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/danielgindi/Charts"&gt;iOS Charts&lt;/a&gt; is a powerful library for drawing almost every chart you’d need in UIKit.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://realm.io/"&gt;Realm&lt;/a&gt; is a very fast and easy-to-use alternative to Apple’s Core Data to create an offline database on the device. Check &lt;a href="https://medium.com/@artempoluektov/mobile-database-optimization-realm-vs-sqlite-1a203eebd2e5"&gt;my article&lt;/a&gt; on comparing both technologies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/vsouza/awesome-ios"&gt;Here&lt;/a&gt; you would find a very popular curated list of other great iOS libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you need help
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://stackoverflow.com/"&gt;StackOverflow&lt;/a&gt;. It’s the primary resource for getting help on solving developers issues. Try searching first, sure, somebody already had the same problem as you, and he/she got an answer. If no, try to ask politely, and you would get your solution very fast.&lt;br&gt;
Don’t worry, even Senior guys couldn’t know everything. They just know where to find the right solution in the shortest amount of time. So they’re using StackOverflow dozens of times every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing for your very first interview
&lt;/h2&gt;

&lt;p&gt;In the next part of this tutorial, I would guide you on preparing for your very first interview, provide you with a checklist of necessary pieces of knowledge and also supply you with some useful tips and tricks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/artem_poluektov/ios-development-for-beginners-part-3-preparing-for-the-interview-2ic2"&gt;Continue to Part 3.&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>iOS development for beginners. Part 1: initial requirements and technology trends</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Fri, 24 Mar 2023 11:24:02 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/ios-development-for-beginners-part-1-initial-requirements-and-technology-trends-3mik</link>
      <guid>https://dev.to/artem_poluektov/ios-development-for-beginners-part-1-initial-requirements-and-technology-trends-3mik</guid>
      <description>&lt;p&gt;Software Engineer job is not only exciting but also compensates well these days. You’re creating amazing products, making people’s lives better and spend your entire day in a cozy warm office with a friendly community. Few of my friends asked me how I started my career as an iOS Developer and what are the first steps they should take if they want to. First, I’d sent them to &lt;a href="https://www.kodeco.com"&gt;kodeco.com&lt;/a&gt; [ex RayWenderlich.com]. Ray had many great tutorials a few years ago which I used to get in touch with some new frameworks and technologies. As we found later, recently he started charging his users for a paid subscription for access to most of the articles. So, I want to provide you with a free, alternative guide. In this part I would start with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initial requirements,&lt;/li&gt;
&lt;li&gt;guide you through the latest technology trends&lt;/li&gt;
&lt;li&gt;give you some useful tips on how to make learning fun and productive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/artem_poluektov/ios-development-for-beginners-part-2-courses-and-tutorials-1b3"&gt;In the next part&lt;/a&gt;, I’ll provide you with a review of best available courses and give you some links to documentation/tutorials for the most popular technologies and frameworks we use in production environment. &lt;a href="https://dev.to/artem_poluektov/ios-development-for-beginners-part-3-preparing-for-the-interview-2ic2"&gt;In the final part&lt;/a&gt;, I would help you with preparing for your very first job interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical requirements
&lt;/h2&gt;

&lt;p&gt;You may have seen those monitors with black windows full of endless lines of text in movies. In real life, we don’t use the Terminal, Notepad or other text editing software for writing code. We have an Integrated Development Environment (IDE) for that. Writing an iOS code is only possible with Xcode provided by Apple.&lt;/p&gt;

&lt;p&gt;Also, Xcode runs only on Macs with installed macOS. Of course, there are some alternative ways supplemented by enthusiasts. You could try to install macOS on your PC or laptop [it called Hackintosh], try running a virtual machine with Parallels, VirtualBox and other virtualization technologies. There’s no such thing as a free lunch. In return of using these workarounds, you’ll get low frame rate, a lot of freezes and crashes. It would bring you so much pain so you would drop the study eventually. So, I recommend you to get a Mac.&lt;/p&gt;

&lt;p&gt;The new M-chips developed by Apple are extremely powerful, so even with a basic MacBook Air you would get a great performance. However, it's better to fit it with 16GB RAM, or even better with 24/32GB because Xcode would love use all the memory you have :) Here is a short list of possible options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mac mini with M2 chip and with custom configuration of 16GB memory for &lt;strong&gt;just $799&lt;/strong&gt; would be a cheapest possible option to start with&lt;/li&gt;
&lt;li&gt;MacBook Air with M2 chip and custom configuration of 16GB memory for $1,399 is light and powerful option&lt;/li&gt;
&lt;li&gt;16-inch MacBook Pro with M2 Pro chip for $2,499 would be most comfortable to work on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If money isn’t a problem for you, go for 16-inch MacBook Pro. Its high-performance, lightweight and gorgeous display makes it the best laptop to write code. It’s a de-facto industry standard for a lot of professional developers. However, if you’re tighter on a budget, you should start with Mac mini and your existing monitor. You could also try searching on eBay, but make sure to check everything is working fine before giving your money away. As an example, you could easily find 2018–2019 16-inch MacBook Pro for less than $1,000 there.&lt;/p&gt;

&lt;p&gt;Most professional developers I know also prefer using one or two 27" monitors to work with more windows and keep the eyes safe. Apple's Studio Display is gorgeous but expensive option.&lt;/p&gt;

&lt;p&gt;Also, you would need an iPhone. Although Xcode has a great built-in iOS Simulator, it still doesn’t support full list of features the real device has. iPad is suitable for development too. However, if you have neither of them, I would suggest purchasing an iPhone because it has a much bigger market share, therefore, more users and more apps. I wouldn’t recommend you going for anything older than iPhone X mostly because of possible performance issues. Sure, the latest iPhone is the best, but you still could run your apps smoothly even on SE.&lt;/p&gt;

&lt;p&gt;BTW, if you’re new the Mac world, you could download latest macOS version from the Mac App Store for FREE [open Applications folder on your Mac], and you would find an Xcode there too, also available free of charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apple Developer Program
&lt;/h2&gt;

&lt;p&gt;Signing up to ADP is another must-have action on your way of becoming an iOS developer. However, you don’t have to pay for it from the beginning. For years Apple had two different levels of their program: Standard ($99) for publishing apps to the App Store and Enterprise ($299) for corporate app development to publish to the internal app distribution system without public access. Recently they announced a new Individual program suitable for beginners. It’s completely FREE. With this program, you would get access to all Apple-provided development resources and documentation, WWDC videos, and would also become able to launch your test apps on the physical device. You wouldn’t get access to TestFlight, a beta distribution system hosted by Apple, and iTunes Connect (which was recently renamed to App Store Connect), a web portal to submit your apps to the App Store and manage them later. When you’d need a publish your first app, you could easily upgrade your program to Standard level.&lt;/p&gt;

&lt;p&gt;To sign up go to &lt;a href="//developer.apple.com"&gt;developer.apple.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology trends
&lt;/h2&gt;

&lt;p&gt;Like any other industry, we, in iOS development, have a few "holy wars" on whether or not to use specific technology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native vs. cross-platform&lt;/li&gt;
&lt;li&gt;Swift vs. Objective-C&lt;/li&gt;
&lt;li&gt;Xcode vs. other IDEs&lt;/li&gt;
&lt;li&gt;UIKit vs. SwiftUI&lt;/li&gt;
&lt;li&gt;UIKit Storyboards or writing UI in code&lt;/li&gt;
&lt;li&gt;Rx (RxSwift, ReactiveCocoa) vs. Combine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll provide some initial points and my opinion on those topics below.&lt;/p&gt;

&lt;p&gt;The idea of writing code once and launching it on both iOS and Android [and maybe even on Windows Phone] sounds pretty exciting. However, in real life, it doesn’t look so bright &amp;amp; shiny. There were four significant attempts to solve this problem: PhoneGap, Xamarin (from Microsoft), ReactNative (from Facebook) and Flutter (from Google).&lt;/p&gt;

&lt;p&gt;PhoneGap was based on WebViews contained HTML and JavaScript. The primary issue was poor performance and usability: UI controls and animations don’t feel anywhere close to native for the user. Xamarin has chosen a different way. They tried to translate C# code you’re writing to native code. Again, they’d got poor performance, lower usability and enormously long delays supporting new features. Being a popular framework for Web development, React tried to conquer mobile apps with ReactNative extension. You need to write JavaScript code, which later compiles to two native apps: one for iOS, one for Android. JS is a very popular technology these days, and there are a lot of JS developers on the market. So, we, the native developers, were a little bit scared of losing our jobs. Hopefully, even with Facebook support, ReactNative became another great failure. It features the same problems as its predecessors: poor performance, a lot of bugs/crashes, usability issues and, as always, you still need to write some native code to get access to system features or lower level hardware. AirBnb had recently published a fantastic article of their experience with ReactNative [spoiler: they’re sunsetting it]. You’ll find more details &lt;a href="https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c"&gt;here&lt;/a&gt;. Both these technologies are almost dead right now. Flutter is the same idea from Google. Once again, it's good if you need to build quick prototype, but not suitable for bigger projects focused on great user experience and stability. The main reason for cross-platform technologies failures, in my opinion, is the need to support platform-specific changes and features. Every year Apple and Google release new versions of their Operating Systems full of new functions and SDK changes. With native development, you have access to beta versions and could prepare your app for upcoming changes. With any cross-platform technology, you have to wait until the developer this technology would release an update. Usually, it takes months. As for market-specific reasons, I would estimate that less than 1 of 20 companies chose to go cross-platform. So, the market for native developers is much bigger, and that’s great for you!&lt;/p&gt;

&lt;p&gt;There are 2 main programming languages you could use to develop iOS apps: Objective-C and Swift. When I started my career [iOS 3.x times], Obj-C was the only option. Not going more in-depth into details, would point that Obj-C is based on C language which is pretty old these days. In 2014 Apple released a new modern language — Swift and they want you to use it. My team started the transition to Swift right after its first release, and we’d got many benefits: it’s simpler, safer, faster and more expressive language. It’s also much easier to learn and understand. From my point of expertise, I would estimate that Swift adoption was very successful: every new project today starts with pure Swift and the most of existing projects either already completed transition or at 80-95% of doing so. I don’t know any single developer who would be willing to write Objective-C code, so it’s incredibly challenging to find new developers for these companies which stuck with Obj-C. However, language is just a tool, and if you’re familiar with Swift and iOS SDK, you should be able to support and bug fix old Obj-C code. SDK knowledge is much more critical in this case. So, if you’re starting your way of becoming an iOS developer, go for Swift.&lt;/p&gt;

&lt;p&gt;There are not so many alternative IDEs to Xcode. Xcode is doing a pretty great job these days. My teammates from Android community, actually, found that it’s doing better than Android studio :) The only alternative IDE that worth looking at is App Code, but you would have to pay for it, and you’d still have to use Interface Builder built-in in the Xcode. I use Xcode. It’s Apple-provided so you would get all the latest features and updates right after release.&lt;/p&gt;

&lt;p&gt;SwiftUI is a new modern UI framework from Apple. During first releases it missed a lot of required functions easily available in UIKit, so not many teams decided to use it in their production environment. And like any other Apple's frameworks it misses backward compatibility. Starting from iOS 15 SwiftUI had finally got most of required features and components. So it could be used in new projects. But it's still too early to use it in existing apps if you have to support older versions of iOS. That's why I would start learning UIKit first.&lt;/p&gt;

&lt;p&gt;Another hot discussion we had in the iOS community is whether or not use Storyboards. It’s UIKit's tool to create app’s user interface without in graphical editor without [almost] any line of code. With iOS 2.0 Apple introduced an iOS Interface Builder which allowed to create a single screen of the app. You could find the names NIB or XIB. It’s the name for a file containing user interface. XIB is the latest version. A few years later they introduced Storyboards, the way not only to build a single screen, but also to create transitions between screens, or making navigation. The main issues with Storyboards are lack of ability to reuse components [you still have to write code for that] and merge conflicts happening when more than one developer is trying to make changes in a single file at the same time. Also, you could get some performance issues while trying to open Storyboards containing many screens. Apple’s solution for that is Storyboard References, the way to separate one huge file into few smaller. So, it's ok to use Storyboards while learning UIKit but it's better to write UI in code in bigger projects.&lt;/p&gt;

&lt;p&gt;Rx [Reactive eXtensions] is the technology which implements reactive programming paradigm. In a few words, it’s an alternative way to organize data flow in a multi-threading environment. While RxJava quickly became extremely popular in the Android community, a very low number of iOS teams widely adopted RxSwift/ReactiveCocoa. I see two main reasons for this. The first is that Apple’s GCD is helping a lot working with multiple threads, while Android’s AsyncTasks were not so useful. The second is that it affects your entire project so deep, so you need to rethink core architecture and code style completely. It would also raise the bar for newcomers: due to the low adoption of this technology in the iOS community there are not so many experts in it, so you’ll have to hire and teach new guys. It’s also important to notice that with the release of Kotlin’s coroutines RxJava started losing its positions within the Android community. I think that you shouldn’t worry about Rx and could skip this technology. Lately Apple introduced own reactive framework - Combine. However it doesn't seem to be widely popular due to completely different approach comparing to normal development practice.&lt;/p&gt;

&lt;p&gt;Let’s summarize all the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go for Native. It’s in very high demand these days&lt;/li&gt;
&lt;li&gt;Start with Swift, it’s the future&lt;/li&gt;
&lt;li&gt;Use Xcode for your first steps&lt;/li&gt;
&lt;li&gt;Learn UIKit first&lt;/li&gt;
&lt;li&gt;Storyboards would save you much time&lt;/li&gt;
&lt;li&gt;You don’t need to think about Rx at the beginning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build your portfolio from the very beginning
&lt;/h2&gt;

&lt;p&gt;Most of the tutorials would teach you how to use a new technology or framework. Unfortunately, that’s not enough to succeed in the interview. People want to see not just your knowledge, but also the results you’d got. The best way to do so is to write a real iOS app or two. That would also help you to get more practice making your knowledge deeper and yourself — more valuable candidate. Don’t try to make another Facebook or Instagram, they have thousands of developers working on their site. What you really could do is create a simple and beautiful app which would make people’s lives better. Here are some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weather app&lt;/li&gt;
&lt;li&gt;Shopping list&lt;/li&gt;
&lt;li&gt;Expenses tracker&lt;/li&gt;
&lt;li&gt;Time tracker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sure, you may continue this list. So, the key is taking one idea and making it real. First of all, you’d need a UI design. Don’t try to make it complete and final from the beginning. Try drawing some screens on paper, browse the App Store and iOS built-in apps to study your competitors. Pick up the most exciting ideas and focus on what’s really important. You could learn more about how I’ve done that in this article. Then find out what technologies you need to use to complete your app. For example, for Weather app you would need to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swift as a programming language,&lt;/li&gt;
&lt;li&gt;Apple’s UIKit to create a user interface for your app,&lt;/li&gt;
&lt;li&gt;Alamofire to get a forecast from the remote server,&lt;/li&gt;
&lt;li&gt;How to work with JSON and parse data into objects,&lt;/li&gt;
&lt;li&gt;CoreLocation to get user’s current coordinates and show appropriate forecast inside your app,&lt;/li&gt;
&lt;li&gt;CoreData or Realm to make your forecasts available offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some part of this list would be the same for any other app of your choice. Sure, some technologies would be an app-specific. If you have enough time and want to learn more in-depth, pick up the second idea and create another app. It would help you with your interviews, not only to pass it but also to get yourself a higher compensation level.&lt;/p&gt;

&lt;p&gt;Remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick up an idea&lt;/li&gt;
&lt;li&gt;Create a UI prototype (on a paper, or take screenshots of apps you like)&lt;/li&gt;
&lt;li&gt;Focus only on important features&lt;/li&gt;
&lt;li&gt;Move with smaller steps to your own very first app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tip: if you’d choose to create a weather app, check the AccuWeather APIs, they provide some weather data for free :)&lt;/p&gt;

&lt;h2&gt;
  
  
  What we’d got here
&lt;/h2&gt;

&lt;p&gt;After reading this first part of my tutorial, you should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mac with the latest version of macOS installed&lt;/li&gt;
&lt;li&gt;Xcode&lt;/li&gt;
&lt;li&gt;iPhone&lt;/li&gt;
&lt;li&gt;Apple Developer Account&lt;/li&gt;
&lt;li&gt;Higher-level understanding of recent technology trends&lt;/li&gt;
&lt;li&gt;A bright idea of your very first iOS App and some UI prototypes for that&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/artem_poluektov/ios-development-for-beginners-part-2-courses-and-tutorials-1b3"&gt;In the second part&lt;/a&gt; of this tutorial I’m going to review ready-made courses and provide you with useful frameworks and libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/artem_poluektov/ios-development-for-beginners-part-2-courses-and-tutorials-1b3"&gt;Continue to part 2&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Making interviews great again</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Fri, 24 Mar 2023 09:47:04 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/making-interviews-great-again-4k3n</link>
      <guid>https://dev.to/artem_poluektov/making-interviews-great-again-4k3n</guid>
      <description>&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--V2wQ9V08--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1163961107356233729/wRdxfoXi_normal.jpg" alt="Bob Vulfov profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Bob Vulfov
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @bobvulfov
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      [job interview]&lt;br&gt;INTERVIEWER: before we start can i get u anything? water or—&lt;br&gt;ME: id like a job offer&lt;br&gt;I: um&lt;br&gt;M: u said "anything"&lt;br&gt;I: well darn
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      15:30 PM - 22 Apr 2015
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=590900403157479425" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=590900403157479425" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=590900403157479425" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;So, you need to hire some more developers? Moreover, you want to make it quick, productive and fun? Don’t worry, it’s easy, and I’ll show you the right way.&lt;/p&gt;

&lt;p&gt;During my professional career, I attended more than 100 interviews, interviewed hundreds of iOS &amp;amp; Android developers and hired about 50 of them. In this article, I’ll tell you how we changed our hiring process to make it comfortable and productive for both sides. I’m going to describe how I interviewed mobile developers but the same ideas could and should be applied to different roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  CV screening
&lt;/h2&gt;

&lt;p&gt;Screening is the very first step in the hiring process usually done by HR or recruitment specialist. The main tricky thing for HR is to find the right skills and measure the candidate’s level based on CV. So, we [the developers] made a small checklist of what we’re looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Years of work experience&lt;/li&gt;
&lt;li&gt;Number of apps released&lt;/li&gt;
&lt;li&gt;Technology Stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we’re not hiring junior developers, we expect the candidate to have at least 1–2 years of experience in mobile development. The only exception of this rule would be a guy switching from other technologies, in this case, we would expect at least 1 year of experience. For the number of apps, it could be either working on one big popular app or a few smaller apps. We also supplied our HRs with our technology stack, for example, we’re looking Android developers with Java or Kotlin knowledge, not Xamarin or ReactNative. Being familiar with RxJava or tests would add you some points. The idea of this activity is to help our HRs to filter hundreds of CVs they’re reading.&lt;/p&gt;

&lt;p&gt;If HR finds CV to be fitting our position, he/she sends it to appropriate Telegram channel waiting for our developers to approve the CV. We don’t do polls or votes. Somebody sends “+” to the chat, and that’s enough. Trust me, sometimes we’re having hot discussions. Timing is the most important thing here. We’re trying to make a decision as fast as possible and either schedule a technical interview or say no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phone interview
&lt;/h2&gt;

&lt;p&gt;We actually do not make any kind of interview over the phone. The main goal of the call is to make the candidate interested in attending the real interview and, of course, arrange a meeting. We’re working very closely with our HRs providing them with a lot of information about our technology stack and current projects. It happened to me many times over the first contact with a new company when I asked HR about team size, future plans, upcoming projects or the number of opened developers’ positions and they were unable to answer me sending me to interview with their colleagues. That’s not what I expect as a candidate. Let me know your company better, help me with initial research. I had an amazing experience talking to London-based recruiter earlier this month: he had so deep understanding of company’s current situation, products, issues and roadmap. And he was not even in-house recruiter, he’d worked for outsource agency. He also provided me with a lot of useful information about the areas I should focus on the interview. And that was really helpful. We want our HRs to do so, that’s why they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aware of our technology stack and have some high-level insights from the technical roadmap, for example, Java to Kotlin transition&lt;/li&gt;
&lt;li&gt;briefly describe future plans for our project to the candidate&lt;/li&gt;
&lt;li&gt;kindly ask the candidate to attend a technical interview&lt;/li&gt;
&lt;li&gt;describe our interview process to candidate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last thing is crucial because we want the candidate to feel comfortable during the interview, so, he or she should have some expectations before the meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical interview
&lt;/h2&gt;

&lt;p&gt;The primary purpose of this stage is to measure the candidate’s level. There are so many different ways to do so: some companies send homework to check the code, some ask you about almost every algorithm from university class during 5 hours long session, some attack you with hundreds of low-level technical questions, while others try to set up a series of endless meetings with different developers they have. In my opinion, this is an unnecessary waste of time. You can’t be sure that candidate is making homework him- or herself without any help from the side. Usually, developers don’t use all these sorting algorithms in real life. Nobody can keep in their head all the functions from iOS SDK. What is truly important for me is that candidate is smart and gets things done. I found this great idea in &lt;a href="https://www.amazon.com/Smart-Gets-Things-Done-Technical/dp/1590598385"&gt;Joel Spolsky’s book&lt;/a&gt;. How to find out that candidate is smart and would help you bringing your project to launch? First is education, of course. It just has to be. More important is to discuss a candidate’s career and achievements. What he or she did, maybe he/she found an easy way to solve a complex issue in a short amount of time or proposed an entirely new approach to improve product quality. That’s how we’re finding out that candidate is more focused on getting results instead of just writing great code.&lt;/p&gt;

&lt;p&gt;It’s great when the candidate drives the interview, when he/she has a prepared route through his/her career pointing most exciting moments. Sure, sometimes interviewers need to turn the discussion a little bit into a right direction by asking suggestive questions. That’s why we usually send one experienced guy with strong soft-skills and one junior or middle guy who’s still learning that.&lt;/p&gt;

&lt;p&gt;We usually start the interview with a friendly conversation about the candidate’s previous experience. Sometimes it may take up to an hour. Instead of asking prepared questions we discuss large topics like technology stack, why do they choose the specific library, what architecture they used and why, what are the pros and cons of some framework. Finding out how the candidate is thinking is another primary goal of this meeting. Usually, we’re fortunate enough to turn our conversation into talking about data structures, maybe some performance optimizations made in candidate’s past projects. In this topic, we usually ask about array/hashmap insertion/searching complexity, hash functions and collisions in hashmaps. Very few guys were familiar with cycle buffer, the way to maintain constant insertion time into the first or last indexes of the array.&lt;/p&gt;

&lt;p&gt;From such talks, we not only understand the candidate’s level but also his/her ability making the right decisions which is much more important than specific SDK knowledge. You could quickly learn how to use new technology but learning to learn or to make technical decisions definitely would take much time. Giving a chance to a smart guy with a proven track record but the slightly different technical stack would benefit your product much more in long-term than hiring the senior developer who is just writing good code. Leaving candidate’s CV behind the meeting is essential for a great conversation: we’re reading it before and not during the interview. You have a live person in front of you able to tell much more than some papers.&lt;/p&gt;

&lt;p&gt;During the interview, we also discuss the technologies we use, the solutions we made, our development process. All that very important to let candidate feel the spirit of our team. Eventually, our goal is not only to measure candidate’s level but also “sell” him/her our company. So, we’re doing our best to create a comfortable feeling. You know, competition is extremely hard for good guys. We usually send 2 our developers to technical interview session to reduce subjective judgments. At the end of the meeting, they should fill the “knowledge map”. That’s our another invention. There are 2 parts in this table: technical and soft (or personal) skills. I’ll give you a sneak peek for that table for Android interview. Also, we have almost the same one for iOS just with different tech skills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical skills&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Android SDK, popular frameworks&lt;/li&gt;
&lt;li&gt;Architecture, patterns&lt;/li&gt;
&lt;li&gt;CI/CD, tests&lt;/li&gt;
&lt;li&gt;Task or algorithms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Soft skills&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Communication&lt;/li&gt;
&lt;li&gt;Leadership&lt;/li&gt;
&lt;li&gt;Problem-solving&lt;/li&gt;
&lt;li&gt;Learning&lt;/li&gt;
&lt;li&gt;Fitting our team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, we have a grading scale for these skills:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical skills&lt;/strong&gt;&lt;br&gt;
0 - doesn't have experience&lt;br&gt;
1 - fragmented&lt;br&gt;
2 - strong base, enough to work on our project&lt;br&gt;
3 - above average&lt;br&gt;
4 - senior&lt;br&gt;
5 - above interviewer&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soft skills&lt;/strong&gt;&lt;br&gt;
1 - weak&lt;br&gt;
2 - medium&lt;br&gt;
3 - strong&lt;/p&gt;

&lt;p&gt;At the end of the meeting, interviewers are filling up this table independently and getting average for each skill. While they’re doing this, we have our HR ready to take the candidate to small office tour and give him/her a souvenir with company’s brand. Even if we wouldn’t hire this guy right now, we want to leave a positive image of our company. However, if he/she is meeting out requirements, after the tour I’m coming for the final part. We’re doing this to reduce the number of meetings and cut waiting time. Making decision faster really helps us to compete with other companies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal interview
&lt;/h2&gt;

&lt;p&gt;The primary goal of this part is to get to know each other better, find out what kind of person the candidate is and understand what his/her motivation is. Last is extremely important for us. I’m looking for guys who would like to build a great product improving people’s lives, who enjoys experimenting with new technologies and who has a passion for problem-solving. Will this person be a great addition to our team, our culture or this is another toxic guy interested only in salary increase trying to sneak into our company. I usually start introducing my self, briefly describing my achievements and then expect the candidate to do the same. I don’t need to ask any deep technical questions. My colleagues already did that. So, we’re having a nice friendly conversation about our lives. There are many areas to discuss, but I usually have a few general topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How will you choose your next place to work? What is important for you?&lt;/li&gt;
&lt;li&gt;How will you spend your typical day?&lt;/li&gt;
&lt;li&gt;What are you going to do this evening/weekend?&lt;/li&gt;
&lt;li&gt;What are your favorite hobbies?&lt;/li&gt;
&lt;li&gt;Do you have any vacation plans?&lt;/li&gt;
&lt;li&gt;[And sometimes even] what’s your favorite drink?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From all these answers I’m able to compose an image of this guy. I’m not looking for specific answers. Here diversity is the key to success. Everyone in my team has different experience, character, and hobby but together we’re making a solid high-performance team capable of solving any task or issue. So, I’m mostly looking for a great addition to our team than a specific combination of different qualities.&lt;/p&gt;

&lt;p&gt;Another useful thing is how we’re telling our decision. In other companies, candidate leaves office, interviewers are taking a break for a few days to make their decision and after a week or so would send an email with either job offer or standard polite NO. However, we thought, is it so necessary to take a break? We can and, for sure, would lose a great guy due to high competition with other employers. That’s why I always have pre-printed job offer ready for the final stage. And if things are going the right way, I only need to fill in an appropriate salary and sign it. I already know the candidate’s level, I just had a nice talk to this guy, why should I wait? That’s what bribes the candidate. Being honest and transparent, like they expect you to do in the everyday working process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saying NO the right way
&lt;/h2&gt;

&lt;p&gt;If the things aren’t going the right direction, either candidate is too junior or his/her soft skills is not enough to join our team, providing feedback is very important. This guy could not be a good fit for this position now, but later he/she would improve the level, would learn to use new technologies and may just become a better person. That’s why we’re trying to be honest and transparent maintaining positive employer image. If I know the exact reasons why we can’t hire right now, I’ll highlight them right after the interview, I’ll try to give useful recommendations on how to improve some things, to show areas of possible improvement and good candidate would come back later and tell his/her friends a lot of good things about our company. The market is too small these days. Sometimes I can’t say anything and need to discuss this case with my colleagues. In this situation, it’s important to mark the date when you’re going to provide feedback in written form. Again, we’re not using standard replies. We’re providing the exact reasons with recommendations on how to improve it and even some useful links to books or articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doing all this?
&lt;/h2&gt;

&lt;p&gt;We’d made a dramatic improvement to our interview process, and we’d got great results. We were able to hire the best guys from the open market and even some guys on recommendations. Maintaining a positive employer image would increase incoming candidates flow a lot. People would know that you have a friendly and helpful culture in your company. Moreover, they would value it, for sure.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Mobile database optimization: Realm vs. SQLite</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Sun, 19 Mar 2023 14:13:38 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/mobile-database-optimization-realm-vs-sqlite-3157</link>
      <guid>https://dev.to/artem_poluektov/mobile-database-optimization-realm-vs-sqlite-3157</guid>
      <description>&lt;p&gt;In this story, I’m going to tell you how we developed a structured approach to solving complex technical issue and successfully improved our app’s performance up to 30 times.&lt;/p&gt;

&lt;p&gt;We started the development of a new enterprise app with gathering technical requirements. Initially, we found out that our app should include those primary features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reports containing charts&lt;/li&gt;
&lt;li&gt;A simple task tracking system&lt;/li&gt;
&lt;li&gt;List of all company’s employees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these data should also be available for offline use. Sounds not so hard, right? We thought so. We picked up our initial technology stack basing on JSON for client-server interactions and SQLite for offline data storage on a mobile device. On iOS, we used MagicalRecord, a CoreData wrapper, very popular few years ago. Today it would definitely be a wrong choice because it’s written in Objective-C and hadn’t got any updates over the last 3 years. However, in 2014 it was free, open-source and had 10K+ stars on GitHub, but the main reason was lack of any alternatives. On Android, we decided to use ORMLite for the very same reasons.&lt;/p&gt;

&lt;p&gt;Everything was going fine in the early stages of the development process. We had some demo reports and a few dozens of employees on our development server. However, we’d got serious performance issues right after selling our product to the first customer. Our colleagues from the backend team imported a part of the client’s production database to our test environment. And we found out that our app is not ready to operate with a list of 200K employees. It took between 5 to 10 minutes to download the full list, scrolling performance was poor and search just didn’t work at all. That’s not what our end-users expected. Another issue was reports. Some of them contained charts with millions of points, so we got the same issues: slow downloading and poor performance on both platforms.&lt;/p&gt;

&lt;p&gt;We tried to make some hotfixes, introducing pagination to client-server requests and batch writing to the local database, but we were unable to increase performance high enough. Lucky for us, our sales department signed 6-months long contract with the client, and the first part was updating some internal systems, so we’d got a month or two to fix our issues. We understood that we needed to perform full research of what’s going on wrong instead of writing chaotic bug fixes. Below I’m going to tell you about our approach in details.&lt;/p&gt;




&lt;h2&gt;
  
  
  The research
&lt;/h2&gt;

&lt;p&gt;We started with optimization of employees list performance. After few failed attempts of fast bug fixing, we decided to divide the process of downloading employees list and showing them in UI into smaller steps and measure a duration of each step to find out what needs to be fixed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimt05xzvbmfjm65bwskz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimt05xzvbmfjm65bwskz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we found that waiting for a server response (30s) and writing to the local database (5–10m) is two longest parts of the entire process, while other parts took just a few hundreds of milliseconds, therefore didn’t require our attention.&lt;/p&gt;




&lt;h2&gt;
  
  
  Server response
&lt;/h2&gt;

&lt;p&gt;Before any optimizations, we had a single request to download the full list of employees. This approach was chosen intentionally because any single part of this list doesn’t carry any value for end-user. Nobody wants to see just employees with last names starting with letter A. Our users need a full list to access all contacts and perform an offline search.&lt;/p&gt;

&lt;p&gt;We also had a favorite contacts feature with sync between user’s devices through our server. First of all, we moved favorite data to separate request and separate table in our local database. That didn’t give us huge performance increase, but let us enable in-memory storage of full employees list on the server side.&lt;/p&gt;

&lt;p&gt;The final and most important part of response time optimization was introduction of delta-updates. As I stated above, we stored a full list of employees in-memory on server side. It reduced response time from ~30s to just 1–2 seconds, we were unable to reduce it anymore due to network delay. After discussing our solution with backend and sales teams, we also found out that employees list usually could be updated just once a day, there is no need for more frequent updates. So, to reduce a load on server side and speed up database writing on a mobile client we introduced delta-updates. Right after the first install our app downloads full list, but the next day it asks the server for just a small delta-update providing a version (timestamp) of the latest data app has.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz7tc32f148jmnh4pzcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz7tc32f148jmnh4pzcf.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Local database optimization
&lt;/h2&gt;

&lt;p&gt;Delta-updates dramatically reduced employees list update time. However, we still had two issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initial load was too slow due to SQLite writing speed&lt;/li&gt;
&lt;li&gt;Apply few delta-updates (for example, after 2 weeks inactivity) also took much time due to the exact same reason&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After batch writing didn’t increase performance, we decided to try different approach. We had a hypothesis that either SQLite or MagicalRecord was a bottleneck in our case. So, we proposed 2 possible solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switching the ORM&lt;/li&gt;
&lt;li&gt;Switching SQLite with other DB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We didn’t find any replacement to MagicalRecord, so decided to use CoreData. As SQLite replacement we used Realm. &lt;strong&gt;Then we developed a synthetic test to measure writing performance of 200K entities.&lt;/strong&gt; Results were almost the same. That’s why we also decided to include MagicalRecord to our test. We were surprised finding that all 3 tests were running for almost the same amount of time. Here we finally found our issue: creating relations between entities. In our synthetic test, we implemented writing to a single table, while in the production app we had 2 tables with relations. See image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchwmjy7xhgumhknf1cnh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchwmjy7xhgumhknf1cnh.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each employee had approximately 3 contacts: email, mobile phone, and work phone. So, we needed to write 200K employees to one table, 600K contacts to another and also create 600K relations between these two tables.&lt;/p&gt;

&lt;p&gt;We updated our synthetic test and found that &lt;strong&gt;CoreData (or SQLite itself) is working with relations extremely slow&lt;/strong&gt;. We were able to achieve up to 30x performance increase switching from CoreData to Realm. The primary reason for that is Realm’s no-SQL nature. You could find more technical details and explanations &lt;a href="https://academy.realm.io/posts/threading-deep-dive/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. See the chart below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8i4d8kwhznorzja87wq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8i4d8kwhznorzja87wq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Search performance
&lt;/h2&gt;

&lt;p&gt;So, we were able to achieve great downloading performance decreasing initial load time from a few minutes to just 15 seconds. Scrolling was now smooth too. However, we still were unhappy with search performance. In some cases, it took minutes to reveal all search results. Below I’m going to explain to you why it was so complicated.&lt;/p&gt;

&lt;p&gt;Here is the list of all search fields in 2 different tables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp73683ws98de4ypeelfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp73683ws98de4ypeelfk.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
It was required to develop full-text search over first name, middle name, last name, any combination of those three, position, company, division, email and phone. Remember, we had 200K employees and almost 600K contacts (both emails and phones) in our database. We had to run 8 search queries for each user’s input. It took too much time to process a final response from the database. Another complication was wide usage of Cyrillic characters. All names, positions, companies, and divisions were Cyrillic-only. While emails were a mix of Latin characters and digits, and phones were digits-only. I would skip deeper details on phone extensions.&lt;/p&gt;

&lt;p&gt;First, we decided to limit a minimal search query to 3 symbols. Then, we performed some basic analysis of those symbols. If there were Latin characters, it definitely was an email address. So, instead of running 8 queries we were able to run just one. We did the same with phone numbers. However, some emails contained digits, that’s why we run 2 queries over emails and phones. With this optimization, we cut the number of search queries twice.&lt;/p&gt;

&lt;p&gt;We still needed to increase name, position, division and company title search performance. After another research over the latest publications over different websites, we found that our main issue is case insensitive search. It worked very fast for Latin-only characters and 8–12 times slowly for all others due to checks for similar characters like &lt;code&gt;a, à, á, â, ä, ã, æ, å, ā&lt;/code&gt;. There are 9 different A letters! And the same for many others. So, we had to turn off case insensitive search and create lowercase fields in our tables. That gave us 8 to 12 times performance increase depending on a specific word.&lt;/p&gt;

&lt;p&gt;We also removed some cases of first, middle and last name search combinations using our knowledge of Russian writing specifics. Users usually use 1 of 7 combinations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First&lt;/li&gt;
&lt;li&gt;Last&lt;/li&gt;
&lt;li&gt;First Last&lt;/li&gt;
&lt;li&gt;Last First&lt;/li&gt;
&lt;li&gt;First Middle&lt;/li&gt;
&lt;li&gt;First Middle Last&lt;/li&gt;
&lt;li&gt;Last First Middle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody in Russia would search for &lt;code&gt;Middle First&lt;/code&gt; or &lt;code&gt;Middle Last&lt;/code&gt; combination. That knowledge let us remove, for example, search of single word entry over Middle name field. With this, final update, we achieved another 1.5–2 times performance increase.&lt;/p&gt;

&lt;p&gt;We created a synthetic test to measure search performance. It contained ~ 100 different search queries, mostly focused on name and position search, like it is in real-life usage. And here are our results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk86rx6qh7gma0v24bwa1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk86rx6qh7gma0v24bwa1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After applying all new search optimization algorithms we increased search performance &lt;strong&gt;23 times&lt;/strong&gt;!&lt;/p&gt;




&lt;h2&gt;
  
  
  Reports workaround
&lt;/h2&gt;

&lt;p&gt;After succeeding with employees list performance improvements, we started a full transition to Realm. Eventually, we found that some reports are still working very slow. We implemented the same storage model as on our server side: storing each report in 4-5 different tables. We had 5 different report types, and each had different data structure. For example, one of the reports consisted of chapters, chapters contained charts, charts contained series, and series contained points. We created 5 tables to store all these data. Some charts contained millions of points, it was really huge amount of data.&lt;/p&gt;

&lt;p&gt;After making a time measurement research, we found that our bottleneck in this case was the database, again. However, we had already switched to Realm, which gave us some performance increase, and we, actually, didn’t have other databases to switch to. Simplest solution we found was sending mapped data from server directly to UI and writing to DB in background thread for future usages. It should work very fast, but would create architectural hell. Think about it, you would need to map DB’s managed objects to some plain objects every time you try to pull data from database. Creating some protocols (interfaces) wasn’t considerable option due to managed objects behavior: it could change or even be deleted in every moment, and we need to observe that. However, plain objects would always stay the same.&lt;/p&gt;

&lt;p&gt;Finally, we found that if we could use plain objects in our UI, we don’t need managed objects at all. &lt;strong&gt;We could store raw JSON data directly in database and map it on demand.&lt;/strong&gt; We still had some fields on reports table, like title, update date, author, etc., but report’s body didn’t contain any relations to other parts of database, that’s why could be stored as raw JSON. This tweak gave us up to 300 (yes, three hundred) times performance increase and amazing user experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;We spent almost 3 weeks trying to optimize CoreData queries, and we failed that. However, we were able to stop these attempts and found a better solution. Switching database was extremely hard process. It took about 2 months to complete this task, and I’m still not sure if it’s possible to complete this faster. In our iOS app we used VIPER architecture and implemented Service-Oriented Approach, but due to CoreData’s nature (we widely used &lt;code&gt;NSFetchedResultsController&lt;/code&gt; to improve performance) we still had parts of our service (database) layer in Interactors. However, in terms of VIPER, Interactor shouldn’t know anything about specific database adaptor we use, which required us to drop &lt;code&gt;NSFetchedResultsController&lt;/code&gt; usage. What we cannot afford due to performance. Yes, it’s a cycle reference :) So, we decided to leave part of our service layer in Interactors replacing &lt;code&gt;NSFetchedResultsController&lt;/code&gt; with Realm notifications mechanics. It was the hardest part, we hadn’t change database entities at all, just replaced NSManagedObject inheritance with RealmObject.&lt;/p&gt;

&lt;p&gt;Both my iOS and Android teams really enjoyed working with Realm. It’s working fast, easy-to-learn and easy-to use. It requires much less code, especially on Android comparing to old ORMLite and even new Room.&lt;/p&gt;

&lt;p&gt;What we learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before making any optimization, be sure that you know the specific place needs to be improved&lt;/li&gt;
&lt;li&gt;Don’t try to squeeze every last bit of performance if you’re not sure what to do&lt;/li&gt;
&lt;li&gt;Make experiments and try new technologies&lt;/li&gt;
&lt;li&gt;Always look for suitable solution for your specific task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maybe Realm wouldn’t work for your specific task such well as it worked for us, but before making your choice, try to run some performance tests on your tasks. Costs of switching local database would always be bigger than the costs of running a few tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Realm would be usually faster, much faster than CoreData due to its no-SQL nature. The main advantage is extremely fast management of relations between tables. Also, it’s much easier to use comparing to existing SQL ORMs on both iOS and Android platforms. However, you could experience some bugs and lack of documentation in deeper areas. Personally, I would go for Realm on my next app.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>realm</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>iOS PDFKit: creating PDF document in Swift, inserting/deleting pages</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Sun, 19 Mar 2023 13:44:24 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/ios-pdfkit-creating-pdf-document-in-swift-insertingdeleting-pages-4cdj</link>
      <guid>https://dev.to/artem_poluektov/ios-pdfkit-creating-pdf-document-in-swift-insertingdeleting-pages-4cdj</guid>
      <description>&lt;p&gt;This is the third article about Apple’s PDFkit featuring in-code document creation and pages operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/artem_poluektov/ios-pdfkit-ink-annotations-tutorial-4kgh"&gt;First&lt;/a&gt; article is about PDFKit basics &amp;amp; Ink annotations&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/artem_poluektov/ios-pdfkit-tutorial-text-annotations-more-bc7"&gt;Second&lt;/a&gt; is about PencilKit, Text annotations &amp;amp; auto-saving&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create PDF Document using Swift
&lt;/h2&gt;

&lt;p&gt;In case you need to create a new PDF file on iOS device you won’t actually need to use PDFKit at all. There are few simple UIKit methods for that.&lt;/p&gt;

&lt;p&gt;First, you need to create &lt;code&gt;UIGraphicsPDFRendererFormat&lt;/code&gt; object to provide PDF document metadata such as author, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let format = UIGraphicsPDFRendererFormat()
let metaData = [
    kCGPDFContextTitle: "Hello, World!",
    kCGPDFContextAuthor: "John Doe"
  ]
format.documentInfo = metaData as [String: Any]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll find a full list of available metadata parameters in CoreGraphics framework reference documentation, just start typing &lt;code&gt;kCGPDF&lt;/code&gt; in your code in Xcode.&lt;/p&gt;

&lt;p&gt;Second, we need to calculate PDF page dimensions. PDF documents use a default resolution of 72 DPI. Page dimensions would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// US Letter
Width: 8.5 inches * 72 DPI = 612 points
Height: 11 inches * 72 DPI = 792 points
// A4 would be [W x H] 595 x 842 points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After doing this math, we need to instantiate &lt;code&gt;UIGraphicsPDFRenderer&lt;/code&gt; object, which is responsible of document rendering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let pageRect = CGRect(x: 0, y: 0, width: 595, height: 842)
let renderer = UIGraphicsPDFRenderer(bounds: pageRect,
                                     format: format)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third, to start drawing just call .pdfData method of your renderer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let data = renderer.pdfData { (context) in
// Perform your drawing here
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could call every drawing method of &lt;code&gt;CoreGraphics&lt;/code&gt; framework here. But, first you need to create new PDF page by calling &lt;code&gt;context.beginPage()&lt;/code&gt;. Call this code every time you need to start new page.&lt;br&gt;
To draw a simple “Hello, world!” text use this code example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let data = renderer.pdfData { (context) in
  context.beginPage()

  let paragraphStyle = NSMutableParagraphStyle()
  paragraphStyle.alignment = .center
  let attributes = [
    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14),
    NSAttributedString.Key.paragraphStyle: paragraphStyle
  ]
  let text = "Hello, World!"
  let textRect = CGRect(x: 100, // left margin
                        y: 100, // top margin
                    width: 200,
                   height: 20)

  text.draw(in: textRect, withAttributes: attributes)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s just happened?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We started new PDF page.&lt;/li&gt;
&lt;li&gt;Created new &lt;code&gt;NSParagraphStyle&lt;/code&gt; object to add &lt;code&gt;.center&lt;/code&gt; alignment to our text.&lt;/li&gt;
&lt;li&gt;Created &lt;code&gt;attributes&lt;/code&gt; dictionary containing font and paragraph style.&lt;/li&gt;
&lt;li&gt;Created text String.&lt;/li&gt;
&lt;li&gt;Created &lt;code&gt;textRect&lt;/code&gt; struct describing specific rect where our text would be drawn.&lt;/li&gt;
&lt;li&gt;Called &lt;code&gt;.draw&lt;/code&gt; method of string object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. Easy, right? Just provide your own attributes like color, font, etc.&lt;/p&gt;

&lt;p&gt;Finally, we must save out PDF context to file on device. As rendered would return &lt;code&gt;Data&lt;/code&gt; object, we have 2 ways of saving our document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initializing &lt;code&gt;PDFKit&lt;/code&gt;'s &lt;code&gt;PDFDocument(data:)&lt;/code&gt; object and writing it to file
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let pdfDocument = PDFDocument(data: data)
let path = PATH_TO_DESIRED_FILE
pdfDocument.write(to: path)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;UIGraphicsPDFRendered&lt;/code&gt;'s method &lt;code&gt;writePDF&lt;/code&gt; instead of &lt;code&gt;pdfData&lt;/code&gt; in third step. Let's replace
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let data = renderer.pdfData { (context) in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let url = URL_TO_DESIRED_FILE
try? rendered.writePDF(to: url) { (context) in
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You must add &lt;code&gt;try?&lt;/code&gt; call because writing operation is throwable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Insert page to PDF document
&lt;/h2&gt;

&lt;p&gt;To add or remove pages to/from PDF document we need to use Apple’s &lt;code&gt;PDFKit&lt;/code&gt; methods.&lt;br&gt;
First, you need to initialize 2 PDF documents. One where the page would be inserted, and another from which we’ll take the page to insert.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let firstPDFDocument = PDFDocument(url: URL_TO_YOUR_FIRST_DOC)
let secondPDFDocument = PDFDocument(url: URL_TO_YOUR_SECOND_DOC)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, locate the page you want to insert and insert it at desired location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// let's assume we want to insert 5th page of 2nd doc to 1st doc
let page = secondDocument.page(at: 5)
// And insert this page as first
firstDocument.insert(page, at: 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to save your document after insertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firstDocument.write(to: URL_TO_YOUR_FIRST_DOC)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Remove page from PDF Document
&lt;/h2&gt;

&lt;p&gt;Removing pages is easy as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let pdfDocument = PDFDocument(url: URL_TO_YOUR_DOC)
pdfDocument.removePage(at: 0) // removing the first page
firstDocument.write(to: URL_TO_YOUR_DOC)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>pdfkit</category>
    </item>
    <item>
      <title>iOS PDFKit tutorial: Text Annotations &amp; more</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Sun, 19 Mar 2023 13:35:59 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/ios-pdfkit-tutorial-text-annotations-more-bc7</link>
      <guid>https://dev.to/artem_poluektov/ios-pdfkit-tutorial-text-annotations-more-bc7</guid>
      <description>&lt;p&gt;This is the second article about Apple’s PDFkit featuring working with Text Annotations, document auto-saving and PencilKit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/artem_poluektov/ios-pdfkit-ink-annotations-tutorial-4kgh"&gt;First&lt;/a&gt; article is about PDFKit basics &amp;amp; Ink annotations&lt;/li&gt;
&lt;li&gt;Third article is about creating PDF document on device and inserting/removing pages&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PencilKit
&lt;/h2&gt;

&lt;p&gt;We tried to implement &lt;code&gt;PencilKit&lt;/code&gt; support in our app right after iOS 13 release. We even created a ticket to Apple Technical Support asking is there any easy way to do so. Short answer is &lt;strong&gt;NO&lt;/strong&gt;.&lt;br&gt;
You just can't use &lt;code&gt;PencilKit&lt;/code&gt; together with &lt;code&gt;PDFKit&lt;/code&gt; like how it works in native iOS markup screens.&lt;/p&gt;

&lt;p&gt;Apple Technical Support answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The PencilKit team recommends developers put a PKCanvasView over each PDFPage. Signatures are a little different - you'd probably need to grab the PKDrawing from the PKCanvasView and then render the PKDrawing as an image inside of a PDFPage as you would a watermark.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Proposed solution seems to be impossible to implement (adding &lt;code&gt;PKCanvasView&lt;/code&gt; to &lt;code&gt;PDFPage&lt;/code&gt;). Second part (about grabbing &lt;code&gt;PKDrawing&lt;/code&gt;) seems to be not the right way for our task too because of requirement to zoom page and erase previously added annotations. So, I feel that our solution from the first part of this tutorial is still the right one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PKToolPicker&lt;/code&gt; seems not to be the right fit as well. It looks much better than out instruments view, but is completely not customizable. For example, in iOS 13 it features ruler, and if we don't need it in our app, we're still unable to remove it from ToolPicker. Hope, Apple would add some ways for customization in future releases.&lt;/p&gt;


&lt;h2&gt;
  
  
  Text annotations
&lt;/h2&gt;

&lt;p&gt;Text annotations seems to be easier than drawing (Ink). However due to lack of documentation and sample code (again!) it wasn't an easy task.&lt;/p&gt;

&lt;p&gt;So, let's assume we need to add some "Hello, world!" text to some place on a document's last page. To do so, write the code below (in your Drawing View Controller, for this example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func addDateAnnotation() {
  guard let document = pdfView.document else { return }
  let lastPage = document.page(at: document.pageCount - 1)
  let annotation = PDFAnnotation(bounds: CGRect(x: 100, y: 100, width: 100, height: 20), forType: .freeText, withProperties: nil)
  annotation.contents = "Hello, world!"
  annotation.font = UIFont.systemFont(ofSize: 15.0)
  annotation.fontColor = .blue
  annotation.color = .clear
  lastPage?.addAnnotation(annotation)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, you need a page where you want to add annotation. Then you need to set following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;contents&lt;/code&gt;: text to display,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;font&lt;/code&gt;: font,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fontColor&lt;/code&gt;: foreground color,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;color&lt;/code&gt;: background color.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Just don't forget to save your document.&lt;/p&gt;




&lt;h2&gt;
  
  
  Annotation types
&lt;/h2&gt;

&lt;p&gt;In one of our tasks we needed to iterate through all annotations array to remove annotation of the specific type. We found that calling &lt;code&gt;PDFAnnotationSubtype.freeText&lt;/code&gt; and &lt;code&gt;annotation.type&lt;/code&gt; would actually return different results!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDFAnnotationSubtype.freeText // "/freeText"
annotation.type // "FreeText"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, to filter annotations of the specific type we had to call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allPageAnnotations.filter { $0.type == "FreeText" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allPageAnnotations.filter { $0.type == PDFAnnotationSubtype.freeText }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Next &amp;amp; Previous buttons
&lt;/h2&gt;

&lt;p&gt;Implementing next &amp;amp; previous buttons [which are very useful for your users] is very easy. Just add those buttons in your Storyboard and call one of those methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pdfView.goToPreviousPage(nil)
// or
pdfView.goToNextPage(nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Auto-saving the document
&lt;/h2&gt;

&lt;p&gt;Due to our long history with crashes we decided to implement auto-saving feature. We wanted to save PDFDocument after each successfully drawn annotation and used this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pdfDocument.write(to: url)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, with larger documents this code caused UI freezes, so we tried to do it in background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DispatchQueue.global(qos: .background).async {
  pdfDocument.write(to: url)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which caused crashes when document's content changed during saving.&lt;br&gt;
Our final solution was to use &lt;code&gt;PDFDocument&lt;/code&gt; method &lt;code&gt;dataRepresentation&lt;/code&gt; combined with simple &lt;code&gt;Timer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if let data = pdfDocument.dataRepresentation() {
  try? data.write(to: url)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution didn't cause crashes, but made &lt;code&gt;PDFView&lt;/code&gt; blink on this call. The only working solution we found was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a copy of &lt;code&gt;PDFDocument&lt;/code&gt; ,&lt;/li&gt;
&lt;li&gt;apply all changes (adding/removing annotations) to visible document in &lt;code&gt;PDFView&lt;/code&gt; and to copy, only add completed annotations due to performance,&lt;/li&gt;
&lt;li&gt;save copy each 30 seconds (for example),&lt;/li&gt;
&lt;li&gt;track all changes during saving: you cannot apply changes during save, so need to store changes history in memory during saving, which may take some time with a bigger files.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Useful links&lt;br&gt;
You would find some initial information about PDFKit in this WWDC video:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2017/241/"&gt;https://developer.apple.com/videos/play/wwdc2017/241/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here you'll find Apple's sample code of advanced drawing with Apple Pencil. Not sure it would work really great with PDFs due to performance issues.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/leveraging_touch_input_for_drawing_apps"&gt;https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/leveraging_touch_input_for_drawing_apps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're looking for any alternative solution, check one of these frameworks. I haven't found any free or open-source solutions, and licenses for those in the list are pretty expensive ($500-$1K+).&lt;br&gt;
PSPDFKit. Features drop-in replacement APIs for Apple's &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDFKit,&lt;/li&gt;
&lt;li&gt;Foxit,&lt;/li&gt;
&lt;li&gt;PDFTron.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>pdfkit</category>
    </item>
    <item>
      <title>iOS PDFKit Ink Annotations Tutorial</title>
      <dc:creator>Artem Poluektov</dc:creator>
      <pubDate>Sun, 19 Mar 2023 13:32:49 +0000</pubDate>
      <link>https://dev.to/artem_poluektov/ios-pdfkit-ink-annotations-tutorial-4kgh</link>
      <guid>https://dev.to/artem_poluektov/ios-pdfkit-ink-annotations-tutorial-4kgh</guid>
      <description>&lt;p&gt;This is my first article about Apple’s PDFKit. We’ll start with PDFKit basics and will create first Ink annotations in the end of this tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/artem_poluektov/ios-pdfkit-tutorial-text-annotations-more-bc7"&gt;The second&lt;/a&gt; is about PencilKit, Text annotations and auto-saving.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/artem_poluektov/ios-pdfkit-creating-pdf-document-in-swift-insertingdeleting-pages-4cdj"&gt;The third&lt;/a&gt; article is about creating PDF document on devices, and inserting and removing pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My team recently started a new project: to develop a new iOS app with a built-in PDF Viewer.&lt;/p&gt;

&lt;p&gt;The key feature of this viewer was the ability to add annotations to a PDF file with a finger or Apple Pencil. Of course, we understood it wouldn’t be an easy task, but we never imagined quite how challenging it would be.&lt;/p&gt;

&lt;p&gt;At first sight, &lt;code&gt;PDFKit&lt;/code&gt; looks like any other Apple’s framework included in iOS SDK. It includes views for PDF documents and thumbnails with built-in gestures support and lot of animations. It actually seems to be "magical" framework to solve our task in just a few lines of code.&lt;/p&gt;

&lt;p&gt;However, I’d spent a lot of time making drawings, touches, and annotations work as expected. We also found a few bugs, which lead to crashes (inside the &lt;code&gt;Framework&lt;/code&gt;), and lack of documentation and tutorials or examples.&lt;br&gt;
That’s why I made this tutorial for you!&lt;/p&gt;




&lt;h2&gt;
  
  
  Updated for iOS 15
&lt;/h2&gt;

&lt;p&gt;This tutorial was updated to iOS 15, you’ll also find some useful information about &lt;code&gt;PencilKit&lt;/code&gt;, &lt;code&gt;PKCanvasView&lt;/code&gt; and &lt;code&gt;PKToolPicker&lt;/code&gt; introduced with iOS 13 in the second part, as well as how to add text annotations.&lt;br&gt;
However, there were no major changes in PDFKit framework since iOS 13. &lt;/p&gt;




&lt;h2&gt;
  
  
  The task
&lt;/h2&gt;

&lt;p&gt;The task was pretty simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download a PDF file to the device.&lt;/li&gt;
&lt;li&gt;View a PDF document and allow the user to navigate between pages.&lt;/li&gt;
&lt;li&gt;Add ink annotations to a PDF with the user’s finger or an Apple pencil&lt;/li&gt;
&lt;li&gt;Save a PDF document and upload it to the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looks easy, doesn’t it? We don’t need to teach you how to use Alamofire for downloading and uploading files — we’ll go straight to PDFKit.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: In this tutorial, I’m going to build an iPad app, but PDFKit also works for iPhone.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  PDFView
&lt;/h2&gt;

&lt;p&gt;PDFView is the key object in PDFKit. It’s a subclass of UIView, and its main purpose is to display a PDF Document. To add one to your &lt;code&gt;ViewController&lt;/code&gt;’s view, you could use either &lt;code&gt;Storyboard&lt;/code&gt; or in-code initialization. Personally, I prefer &lt;code&gt;Storyboard&lt;/code&gt; to quickly build an example project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To add a PDFView to your ViewController’s view&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simply add new UIView&lt;/li&gt;
&lt;li&gt;Setup constraints (I’m going to leave some space on the left for thumbnails view)&lt;/li&gt;
&lt;li&gt;Go to the Inspector panel (on the right side), select Identity tab and enter the class name &lt;code&gt;PDFView&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhq58mpsn7g5zxd0hwzq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhq58mpsn7g5zxd0hwzq.png" alt="Adding PDFView to your ViewController’s view"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can set up &lt;code&gt;PDFView&lt;/code&gt;:&lt;br&gt;
Create &lt;code&gt;@IBOutlet&lt;/code&gt; to your &lt;code&gt;ViewController&lt;/code&gt;’s class using Interface Builder&lt;br&gt;
Don’t forget to &lt;code&gt;import PDFKit&lt;/code&gt;&lt;br&gt;
Here’s my PDFView setup code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pdfView.displayDirection = .vertical
pdfView.usePageViewController(true)
pdfView.pageBreakMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
pdfView.autoScales = true
pdfView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The first line is pretty simple — your &lt;code&gt;PDFView&lt;/code&gt; can have either a vertical or horizontal scrolling direction.&lt;/p&gt;

&lt;p&gt;The second line is very useful. By default, &lt;code&gt;PDFView&lt;/code&gt; uses the built-in &lt;code&gt;UIScrollView&lt;/code&gt; to enable a continuous scroll across the whole document. However, you may activate &lt;code&gt;PageViewController&lt;/code&gt; mode, so that the &lt;code&gt;PDFView&lt;/code&gt; shows only one page at the screen. Of course, zooming is supported and enabled by default.&lt;/p&gt;

&lt;p&gt;I found that the &lt;code&gt;.autoScales&lt;/code&gt; setting contains a bug. It doesn’t work on iPad when the screen rotates. To solve this issue, you have to add this call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  pdfView.autoScales = true
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Another method with unexpected behavior is &lt;code&gt;.backgroundColor&lt;/code&gt;. It works in code only and doesn’t work when you’re trying to set it in Interface Builder. I spent a long time trying to understand what I had done wrong!&lt;/p&gt;

&lt;p&gt;Finally, I was able to find the reason: Calling &lt;code&gt;pdfView.document = ...&lt;/code&gt; resets &lt;code&gt;PDFView&lt;/code&gt;’s background color to default. So, call &lt;code&gt;pdfView.backgroundColor = ...&lt;/code&gt; after &lt;code&gt;pdfView.document = ...&lt;/code&gt;.&lt;br&gt;
After completing &lt;code&gt;PDFView&lt;/code&gt; setup, let’s add an example PDF document to your project (drag and drop it to the left panel):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5nf72p9b2k6g8jd7i33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5nf72p9b2k6g8jd7i33.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;PDFDocument&lt;/code&gt; and add it to your &lt;code&gt;PDFView&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

guard let path = Bundle.main.url(forResource: "YOUR_FILE_NAME", withExtension: "pdf") else { return }
pdfView.document = PDFDocument(url: path)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s easy!&lt;/p&gt;

&lt;p&gt;Now we have our app up and running with these out-of-the-box features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zooming with the two-finger gesture.&lt;/li&gt;
&lt;li&gt;Scrolling.&lt;/li&gt;
&lt;li&gt;Long-pressure gesture to call the copy menu.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ThumbnailView&lt;/code&gt; support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what my &lt;code&gt;viewDidLoad&lt;/code&gt; method looks like after this step:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

override func viewDidLoad() {
super.viewDidLoad()
// Setup PDF View
pdfView.displayDirection = .vertical
pdfView.usePageViewController(true)
pdfView.pageBreakMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
pdfView.autoScales = true
// Load PDF File
guard let path = Bundle.main.url(forResource: "241_introducing_pdfkit_on_ios", withExtension: "pdf") else {
print("file not found")
return
}
pdfView.document = PDFDocument(url: path)
// Set background color after setting new document
pdfView.backgroundColor = .systemBackground
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: It’s not the best idea to put so many lines of code in your &lt;code&gt;viewDidLoad&lt;/code&gt;, but it’s a tutorial, so let’s just leave it!&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  PDFThumbnailView
&lt;/h2&gt;

&lt;p&gt;Creating a thumbnail view is easy. Like we just did in the previous step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new &lt;code&gt;UIView&lt;/code&gt; to your &lt;code&gt;ViewController&lt;/code&gt;’s view (not &lt;code&gt;PDFView&lt;/code&gt;’s subview).&lt;/li&gt;
&lt;li&gt;Setup constraints.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;PDFThumbnailView&lt;/code&gt; as a class name in the Identity Inspector tab in the right panel.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;@IBOutlet&lt;/code&gt; to your &lt;code&gt;ViewController&lt;/code&gt;’s code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxsg3mlx1oycmewyahbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxsg3mlx1oycmewyahbw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use this code in your &lt;code&gt;viewDidLoad&lt;/code&gt; method to set up your new &lt;code&gt;Thumbnail View&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 100, height: 100)
thumbnailView.layoutMode = .vertical
thumbnailView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This code is clear. I have to mention that &lt;code&gt;PDFThumbnailView&lt;/code&gt;, like &lt;code&gt;PDFView&lt;/code&gt;, doesn’t respect the &lt;code&gt;.backgroundColor&lt;/code&gt; Interface Building setting. However, it stores color you set, so you don’t have to reset it every time when calling &lt;code&gt;pdfView.document = ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, we had crashes with &lt;code&gt;PDFThumbnailView&lt;/code&gt; on iOS 12 when the user tried to add annotations. In one case user was able to draw for an hour with no crashes but in other cases, the app crashed after the first annotation. It seems to have been fixed in iOS 13.&lt;/p&gt;




&lt;h2&gt;
  
  
  Receiving Touch Events
&lt;/h2&gt;

&lt;p&gt;Initially, to implement user input, I tried to add transparent &lt;code&gt;UIView&lt;/code&gt; as &lt;code&gt;pdfView&lt;/code&gt;'s subview. I found that transparent views do not receive touch events.&lt;/p&gt;

&lt;p&gt;Then I set alpha to 0.01, the minimal alpha for &lt;code&gt;view&lt;/code&gt; to receive touches. However, I still had an issue with zooming and scrolling gestures. I wanted my transparent view to receive one-finger gestures and forward other (two-finger) gestures to its parent view ( &lt;code&gt;pdfView&lt;/code&gt; ). I finally found that this approach just wouldn’t work.&lt;/p&gt;

&lt;p&gt;So, finally, I implemented my own custom gesture recognizer. It only works with one-finger gestures and fails if the gesture has multiple fingers. Here is the code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class DrawingGestureRecognizer: UIGestureRecognizer {
  weak var drawingDelegate: DrawingGestureRecognizerDelegate?
  override func touchesBegan(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
    if let touch = touches.first,
    touch.type == .pencil,
    let numberOfTouches = event?.allTouches?.count,
    numberOfTouches == 1 {
      state = .began
      let location = touch.location(in: self.view)
      drawingDelegate?.gestureRecognizerBegan(location)
    } else {
      state = .failed
    }
  }
  override func touchesMoved(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
    state = .changed
    guard let location = touches.first?.location(in: self.view) else { return }
    drawingDelegate?.gestureRecognizerMoved(location)
  }
  override func touchesEnded(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
    guard let location = touches.first?.location(in: self.view) else  {
      state = .ended
      return
    }
    drawingDelegate?.gestureRecognizerEnded(location)
    state = .ended
  }
  override func touchesCancelled(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent) {
    state = .failed
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First, I implemented the &lt;code&gt;touchesBegan&lt;/code&gt; method, where it analyzes the touch it received.&lt;/p&gt;

&lt;p&gt;If it’s Apple Pencil and a one-finger gesture, I set the gesture recognizer state to &lt;code&gt;.began&lt;/code&gt; and continue receiving touch events. You could comment the &lt;code&gt;Pencil&lt;/code&gt; line to make this code work on iOS Simulator. You cannot simulate &lt;code&gt;Pencil&lt;/code&gt; on your Mac, unfortunately. If it’s multi-touch gesture, I set gesture recognizer state to &lt;code&gt;.failed&lt;/code&gt;, so that other &lt;code&gt;pdfView&lt;/code&gt;'s gesture recognizers work.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;touchesBegan&lt;/code&gt; I’m also notifying my delegate that we received a new touch event so it could create the new &lt;code&gt;PDFAnnotation&lt;/code&gt;. In the &lt;code&gt;touchesMoved&lt;/code&gt; method I’m only notifying the delegate about new touches locations.&lt;/p&gt;

&lt;p&gt;Finally, in the &lt;code&gt;touchesEnded&lt;/code&gt; method, I’m sending the final touch location and notifying my delegate that touch has ended.&lt;br&gt;
Here’s the protocol reference — it’s quite simple:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

protocol DrawingGestureRecognizerDelegate: class {
func gestureRecognizerBegan(_ location: CGPoint)
func gestureRecognizerMoved(_ location: CGPoint)
func gestureRecognizerEnded(_ location: CGPoint)
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add this gesture recognizer to your &lt;code&gt;PDFView&lt;/code&gt; by adding this code to your &lt;code&gt;ViewController&lt;/code&gt;’s &lt;code&gt;viewDidLoad&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

let pdfDrawingGestureRecognizer = DrawingGestureRecognizer()
pdfView.addGestureRecognizer(pdfDrawingGestureRecognizer)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Coalesced and Predicted Touches
&lt;/h2&gt;

&lt;p&gt;Each touch event (&lt;code&gt;UIEvent&lt;/code&gt;) from our gesture recognizer also has coalesced and predicted touch arrays. Due to different device hardware and to improve performance, only some touch events are being received by the gesture recognizer in real-time.&lt;/p&gt;

&lt;p&gt;If your app needs better precision, you could access all touches by checking &lt;code&gt;UIEvent&lt;/code&gt;'s &lt;code&gt;coalescedTouches&lt;/code&gt; property. iOS would also try to predict the user’s finger or pencil movement and create a &lt;code&gt;predictedTouches&lt;/code&gt; array for each &lt;code&gt;UIEvent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;According to Apple’s documentation, you could store &lt;code&gt;coalescedTouches&lt;/code&gt; and use them for drawing. However, you must remove any drawing used data from &lt;code&gt;predictedTouches&lt;/code&gt; when you receive the next touch event.&lt;/p&gt;

&lt;p&gt;We tried to forward the gesture recognizer’s delegate not only the location of current touch, but also the arrays of coalesced and predicted touches. However, it caused performance issues, so in the final implementation we decided to stuck with just regular touch events and skipped coalesced and predicted touches. In our case, it didn’t affect user experience in the end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Drawing Implementation
&lt;/h2&gt;

&lt;p&gt;After we created the gesture recognizer, we need to implement drawing. I’ll try to follow the single-responsibility principle and create another class to handle drawing. Let’s call it &lt;code&gt;PDFDrawer&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class PDFDrawer {
  weak var pdfView: PDFView!
  private var path: UIBezierPath?
  private var currentAnnotation : PDFAnnotation?
  private var currentPage: PDFPage?
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our &lt;code&gt;PDFDrawer&lt;/code&gt; should hold a reference to &lt;code&gt;PDFView&lt;/code&gt;, to which we add our annotations. &lt;code&gt;Path&lt;/code&gt; is used to store all touch events in a single gesture. &lt;code&gt;CurrentAnnotation&lt;/code&gt; reference is also required because we add new points to our path whenever the user moves their Apple Pencil, so we need to update it. &lt;code&gt;CurrentPage&lt;/code&gt; is also required for cases where a user starts a gesture on one page and then accidentally moves to the next page.&lt;/p&gt;

&lt;p&gt;Here I should explain how &lt;code&gt;PDFDocument&lt;/code&gt; works. The document contains pages which are represented by the &lt;code&gt;PDFPage&lt;/code&gt; class. Each page contains its own annotations of class &lt;code&gt;PDFAnnotation&lt;/code&gt;. So, in the case where a user starts a gesture on one page and moves touches to another page, we have to keep drawing on the initial page.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PDFDrawer&lt;/code&gt; is gesture recogniser’s delegate, so we need to implement three methods of &lt;code&gt;DrawingGestureRecognizerDelegate&lt;/code&gt; protocol. First, one is called on the user’s first touch:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

func gestureRecognizerBegan(_ location: CGPoint) {
  guard let page = pdfView.page(for: location, nearest: true) else { return }
  currentPage = page
  let convertedPoint = pdfView.convert(location, to: currentPage!)
  path = UIBezierPath()
  path?.move(to: convertedPoint)
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here we storing current page reference and creating new &lt;code&gt;UIBezierPath&lt;/code&gt; for the new drawing.&lt;/p&gt;

&lt;p&gt;When the user moves their finger or Pencil the following method is called:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

func gestureRecognizerMoved(_ location: CGPoint) {
  guard let page = currentPage else { return }
  let convertedPoint = pdfView.convert(location, to: page)
  path?.addLine(to: convertedPoint)
  path?.move(to: convertedPoint)
  drawAnnotation(onPage: page)
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This checks if the current page reference exists, adds a new point to our path, and adds drawing annotation. I will provide the &lt;code&gt;drawAnnotation&lt;/code&gt; method implementation later.&lt;/p&gt;

&lt;p&gt;When the user finishes their touch, exactly the same code is called. Or at least, it’s the same at this stage of the tutorial, but we will add some specific code later to implement an eraser.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

func gestureRecognizerEnded(_ location: CGPoint) {
  guard let page = currentPage else { return }
  let convertedPoint = pdfView.convert(location, to: page)
  path?.addLine(to: convertedPoint)
  path?.move(to: convertedPoint)
  drawAnnotation(onPage: page)
  currentAnnotation = nil
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Easy, right? Initially, I tried to reuse one annotation across began, moved, and ended events. I added a new &lt;code&gt;UIBezierPath&lt;/code&gt; to the current annotation every time by calling &lt;code&gt;.add(UIBezierPath)&lt;/code&gt; method of &lt;code&gt;PDFAnnotation&lt;/code&gt;. However, we found that the app crashes somewhere inside the &lt;code&gt;PDFKit&lt;/code&gt; framework.&lt;/p&gt;

&lt;p&gt;So instead of calling &lt;code&gt;.add&lt;/code&gt; on one annotation, I create a new annotation every time touch moves and remove the old one. This (adding and removing) is visible on iOS Simulator but works smoothly on a real iPad. This is the only solution I’ve found that does not causes crashes inside the PDFKit Framework.&lt;/p&gt;

&lt;p&gt;Here’s my code to create the annotation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

private func createAnnotation(path: UIBezierPath, page: PDFPage) -&amp;gt; PDFAnnotation {
  let border = PDFBorder()
  border.lineWidth = 5.0 // Set your line width here
  let annotation = PDFAnnotation(bounds: page.bounds(for: pdfView.displayBox), forType: .ink, withProperties: nil)
  annotation.color = .red
  annotation.border = border
  annotation.add(path)
  return annotation
}
private func drawAnnotation(onPage: PDFPage) {
  guard let path = path else { return }
  let annotation = createAnnotation(path: path, page: onPage)
  if let _ = currentAnnotation {
    currentAnnotation!.page?.removeAnnotation(currentAnnotation!)
  }
  onPage.addAnnotation(annotation)
  currentAnnotation = annotation
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Another issue that forced us to use this solution is the feature request from our client. They wanted not only drawing with full color but also to be able to draw with transparent color (with alpha component).&lt;/p&gt;

&lt;p&gt;The default &lt;code&gt;PDFAnnotation&lt;/code&gt; with few paths looked really bad. Because it consisted of few transparent paths, the points where one path ends and the other begins were brighter than lines because of intersections. Because all the drawing is performed inside &lt;code&gt;PDFKit&lt;/code&gt;, we’re unable to change that. That’s why we used our solution instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finujq1pfp3cjihg5979u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finujq1pfp3cjihg5979u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, to make things work, we need to create a &lt;code&gt;PDFDrawer&lt;/code&gt; property in our &lt;code&gt;ViewController&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

private let pdfDrawer = PDFDrawer()



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And add this setup calls, for example, in &lt;code&gt;viewDidLoad&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pdfDrawingGestureRecognizer.drawingDelegate = pdfDrawer
pdfDrawer.pdfView = pdfView


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Long Pressure Gesture Recognizer
&lt;/h2&gt;

&lt;p&gt;As I mentioned above, &lt;code&gt;PDFView&lt;/code&gt; contains a lot of built-in gesture recognizers. In some rare cases, our custom gesture recognizer conflicted with built-in long-pressure gesture recognizer used to copy action. We haven’t found any setting of &lt;code&gt;PDFView&lt;/code&gt; to disable it, so we had to implement this subclass of &lt;code&gt;PDFView&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import UIKit
import PDFKit
class NonSelectablePDFView: PDFView {
// Disable selection
  override func canPerformAction(_ action: Selector, withSender sender: Any?) -&amp;gt; Bool {
    return false
  }
  override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
    if gestureRecognizer is UILongPressGestureRecognizer {
      gestureRecognizer.isEnabled = false
    }
    super.addGestureRecognizer(gestureRecognizer)
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Custom PDFAnnotations
&lt;/h2&gt;

&lt;p&gt;We also experimented with custom PDFAnnotations. As I mentioned above, we needed to remove and add new PDFAnnotation after receiving new touch events.&lt;/p&gt;

&lt;p&gt;We tried to use a single annotation from the beginning of the gesture to its end, to improve drawing performance. So, we created a custom annotation class with path property and overridden &lt;code&gt;draw()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;However, we were unable to force PDFKit to redraw the annotation every time we modified the path because &lt;code&gt;PDFAnnotation&lt;/code&gt; is not &lt;code&gt;UIView&lt;/code&gt; and does not have any methods for that.&lt;/p&gt;

&lt;p&gt;Finally, we decided to add and remove the same annotation from the page every time we received a new touch event (without creating a new one). With custom &lt;code&gt;draw&lt;/code&gt; method we achieved visual drawing performance increase noticeably improving user experience. Here’s the &lt;code&gt;draw()&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class DrawingAnnotation: PDFAnnotation {
  public var path = UIBezierPath()
  override func draw(with box: PDFDisplayBox, in context: CGContext) {
    UIGraphicsPushContext(context)
    context.saveGState()
    color.set()
    path.lineWidth = border?.lineWidth
    path.stroke() // this is actual drawing call
    context.restoreGState()
    UIGraphicsPopContext()
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To eliminate drawing artefacts we add some extra settings to the &lt;code&gt;draw()&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

path.lineJoinStyle = .round
path.lineCapStyle = .round


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To make this method thread-safe we replaced path the copy inside the &lt;code&gt;draw()&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

override func draw(with box: PDFDisplayBox, in context: CGContext) {
  let pathCopy = path.copy() as! UIBezierPath
  UIGraphicsPushContext(context)
  context.saveGState()
  context.setShouldAntialias(true)
  color.set()
  pathCopy.lineJoinStyle = .round
  pathCopy.lineCapStyle = .round
  pathCopy.lineWidth = border?.lineWidth ?? 1.0
  pathCopy.stroke()
  context.restoreGState()
  UIGraphicsPopContext()
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Custom Annotations and Performance
&lt;/h2&gt;

&lt;p&gt;After the first release, we found another performance issue. Users experienced freezes on scrolling and zooming PDF documents with our custom annotations. Some even crashed due to memory warnings — even in-system and third-party apps.&lt;/p&gt;

&lt;p&gt;To fix this issue and maintain a great drawing experience we decided to use custom annotation while the user is drawing and replace it with the system &lt;code&gt;PDFAnnotation&lt;/code&gt; right after the user finishes touch. We also added new bounds calculation mechanics to minimize final annotation’s bounds size, reducing memory usage and improving drawing performance. I’ll remind you that we used whole page size as the bounds for our custom annotation during the drawing process — the user can draw over the whole page, quickly and smoothly.&lt;/p&gt;

&lt;p&gt;Updated &lt;code&gt;PDFDrawer&lt;/code&gt; &lt;code&gt;gestureRecognizerEnded&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

func gestureRecognizerEnded(_ location: CGPoint) {
  guard let page = currentPage else { return }
  let convertedPoint = pdfView.convert(location, to: page)
  // Erasing
  if drawingTool == .eraser {
    removeAnnotationAtPoint(point: convertedPoint, page: page)
    return
  }
  guard let _ = currentAnnotation else { return }
  path?.addLine(to: convertedPoint)
  path?.move(to: convertedPoint)
  // Final annotation
  page.removeAnnotation(currentAnnotation!)
  currentAnnotation = nil
  createFinalAnnotation(path: path!, page: page)
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this method we’re still updating path, then removing the old (custom) annotation and creating final (system) annotation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

private func createFinalAnnotation(path: UIBezierPath, page: PDFPage) -&amp;gt; PDFAnnotation {
  let border = PDFBorder()
  border.lineWidth = drawingTool.width
  let bounds = CGRect(x: path.bounds.origin.x - 5,
                      y: path.bounds.origin.y - 5,
                  width: path.bounds.size.width + 10,
                 height: path.bounds.size.height + 10)
  var signingPathCentered = UIBezierPath()
  signingPathCentered.cgPath = path.cgPath
  signingPathCentered.moveCenter(to: bounds.center)
  let annotation = PDFAnnotation(bounds: bounds, forType: .ink, withProperties: nil)
  annotation.color = color.withAlphaComponent(drawingTool.alpha)
  annotation.border = border
  annotation.add(signingPathCentered)
  page.addAnnotation(annotation)
  return annotation
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s it. Great performance during drawing and previewing. Just don’t forget to add the &lt;code&gt;UIBezierPath+.swift&lt;/code&gt; file to your project if you’re going to use my code. You may find it in the sample code at the bottom of this tutorial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Eraser
&lt;/h2&gt;

&lt;p&gt;We also needed to implement an eraser tool. However, we were unable to use the &lt;code&gt;annotation(at:CGPoint)&lt;/code&gt; method provided by &lt;code&gt;PDFPage&lt;/code&gt; because it’s based on annotation’s bounds and returned annotation even when the user tapped on white space inside bounds &lt;code&gt;rect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In our case (where the user may write text with an Apple Pencil) it didn’t work as the user would expect. We needed to find out whether the path was tapped, not the path’s bounds. First, we implemented &lt;code&gt;PDFAnnotation&lt;/code&gt; subclass with computable &lt;code&gt;hitPath&lt;/code&gt; property (more details on that later) which is the final path of annotation. For performance reasons, it’s only calculated when the path is completed.&lt;/p&gt;

&lt;p&gt;We found that this approach doesn’t work after closing the &lt;code&gt;PDFDocument&lt;/code&gt; and opening it again, because all the previously created annotations of class &lt;code&gt;PDFAnnotationWithPath&lt;/code&gt; became regular &lt;code&gt;PDFAnnotation&lt;/code&gt;. So, we had to sacrifice a bit of performance and create an extension instead of subclass:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

extension PDFAnnotation {
  func contains(point: CGPoint) -&amp;gt; Bool {
    var hitPath: CGPath?
    if let path = paths?.first {
    hitPath = path.cgPath.copy(strokingWithWidth: 10.0, lineCap: .round, lineJoin: .round, miterLimit: 0)
    }
    return hitPath?.contains(point) ?? false
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It’s important to note that we’re using &lt;code&gt;paths&lt;/code&gt; property of &lt;code&gt;PDFAnnotation&lt;/code&gt; here — make sure to put your final path there. We’re also using ten points as the default stroking width for all lines to make it easier for the user to hit the path with the eraser.&lt;br&gt;
We also implemented &lt;code&gt;PDFPage&lt;/code&gt; extension to call the &lt;code&gt;contains&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


extension PDFPage {
  func annotationWithHitTest(at: CGPoint) -&amp;gt; PDFAnnotation? {
    for annotation in annotations {
      if annotation.contains(point: at) {
        return annotation
      }
    }
    return nil
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, we need to update &lt;code&gt;PDFDrawer&lt;/code&gt; methods to support the eraser tool:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

func gestureRecognizerMoved(_ location: CGPoint) {
  guard let page = currentPage else { return }
  let convertedPoint = pdfView.convert(location, to: page)

  // Erasing
  if drawingTool == .eraser {
    removeAnnotationAtPoint(point: convertedPoint, page: page)
    return
  }


  // Drawing
  ...
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;DrawingTool&lt;/code&gt; is an enum, containing all the drawing tools we have. Users may select different tools with different line width and color in the UI. We’re storing it as a property of &lt;code&gt;PDFDrawer&lt;/code&gt;.&lt;br&gt;
Apply the same changes to &lt;code&gt;gestureRecognizerEnded&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

func gestureRecognizerEnded(_ location: CGPoint) {
  guard let page = currentPage else { return }
  let convertedPoint = pdfView.convert(location, to: page)
  // Erasing
  if drawingTool == .eraser {
    removeAnnotationAtPoint(point: convertedPoint, page: page)
    return
  }
  // Drawing
  ...
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here’s &lt;code&gt;removeAnnotationAtPoint&lt;/code&gt; implementation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

 private func removeAnnotationAtPoint(point: CGPoint, page: PDFPage) {
  if let selectedAnnotation = page.annotationWithHitTest(at: point) {
    selectedAnnotation.page?.removeAnnotation(selectedAnnotation)
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Saving the Document
&lt;/h2&gt;

&lt;p&gt;Saving your &lt;code&gt;PDFDocument&lt;/code&gt; after adding all the annotations you need is also very easy:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

let path = Bundle.main.url(forResource: "YOUR_DOCUMENT_NAME", withExtension: "pdf")
pdfView.document?.write(to: path!)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here’s the final view of our app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feudr56hp48fey6qrxszt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feudr56hp48fey6qrxszt.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sample Code
&lt;/h2&gt;

&lt;p&gt;For a demo project, please check the &lt;a href="https://github.com/poluektov/pdfkit-ink-annotations/" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I didn’t create a library or framework intentionally. Mostly because all the apps/projects to work with PDF are so different and require a lot of customizations. So, guys, feel free to use my sample code as a base for your own project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Continue Reading
&lt;/h2&gt;

&lt;p&gt;To learn more about &lt;code&gt;PDFKit&lt;/code&gt;, including working with &lt;code&gt;PencilKit&lt;/code&gt;, adding text annotations, and alternative solutions, continue to the &lt;a href="https://dev.to/artem_poluektov/ios-pdfkit-tutorial-text-annotations-more-bc7"&gt;second part&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>pdfkit</category>
    </item>
  </channel>
</rss>
