<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ray Kim on Medium]]></title>
        <description><![CDATA[Stories by Ray Kim on Medium]]></description>
        <link>https://medium.com/@rckim77?source=rss-71c5568dce47------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*4AVH4oodG9tiMQiOSv2m8Q.jpeg</url>
            <title>Stories by Ray Kim on Medium</title>
            <link>https://medium.com/@rckim77?source=rss-71c5568dce47------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 09 May 2026 12:42:51 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@rckim77/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Introducing New Search Modalities on Mobile]]></title>
            <link>https://medium.com/classpass-engineering/introducing-new-search-modalities-on-mobile-9150bcdf672b?source=rss-71c5568dce47------2</link>
            <guid isPermaLink="false">https://medium.com/p/9150bcdf672b</guid>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[search]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Ray Kim]]></dc:creator>
            <pubDate>Wed, 13 Dec 2023 15:29:31 GMT</pubDate>
            <atom:updated>2023-12-13T18:16:41.769Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mwZh28jdbgCZjwn730Cs7Q.png" /></figure><p>At ClassPass, we’re always looking for ways to make search feel more intuitive to our users. For the last several months, the Search &amp; Discovery team worked on introducing search modalities in our mobile apps based on user research. We introduced phrase-based search (i.e., search by any arbitrary text), multi-location search, and many other improvements through a series of experiments and feature flags via Optimizely to ensure the new features didn’t harm core metrics. We chose to develop these features using milestones to save upfront engineering investment in case the results from the first milestone were subpar.</p><h3>Milestone One: Implicit Full Text Search</h3><p>The first milestone was to broaden the results we get back for existing queries. We started at the Opensearch level by adding text fields containing venue (e.g., fitness studios) and class names and descriptions to our schedule and class indexes. In tandem, we created analyzers that direct Opensearch’s tokenization and search strategy on these new search fields. Now we were able to perform queries that match phrases on our text fields, whereas we were previously limited to filtering on our internal tag IDs.</p><p>We wanted to start testing our new capabilities in a way that wouldn’t disrupt the existing user experience. The solution was to execute a new full-text search alongside our existing tag filter search — an <em>implicit </em>full-text search.<em> </em>The user would search as normal, but when a tag was selected as their search term, we would also execute a full-text search on the backend and the results would be combined with the tag filter results.</p><p>After 7 days of running an A/B experiment, we found that the variant (i.e., additional text fields indexed) had too many inaccurate results. We decided to remove class descriptions from the index because they were often the longest body of text and sometimes contained words that were irrelevant to the tag searched. We re-ran the experiment with the modified variant and found that our metrics–conversion from search to reservation (CVR) and click through from search to a venue or schedule page (CTR)–were now trending positive, giving us the green light to expand our capabilities to milestone two.</p><h3>Milestone Two: Phrase-based Search</h3><p>Once we’ve expanded the backend’s capability to match more terms, we felt confident building a brand new mode of searching on mobile: phrase-based search. Previously, when a user starts typing in the search field, we would return a list of suggestions based on our internal tag hierarchy (e.g., yoga, pilates).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Nn4wBkiVMIE6lez94ia_ww.png" /><figcaption>Search for yoga in Manhattan</figcaption></figure><p>This worked for obvious genres like yoga but it wasn’t very helpful when it didn’t map to something we already had a tag for. For example, searching for balayage, a type of hair highlighting treatment, used to yield no autocomplete suggestions and merely a few places that had “Balayage” in their name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7fX2v4SpK7dPMXZRhYqqIw.png" /><figcaption>Search for balayage yielded no autocomplete suggestions</figcaption></figure><p>It’s not that we don’t have balayage appointments on our platform, it’s that our own tagging system was too restrictive. This limited inventory discoverability and wasn’t an intuitive search experience. We therefore introduced a new row on the autocomplete screen that lets the user search for any arbitrary word or phrase even if we return no autocomplete results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KF3lrVAxENelL9zcxVvXlg.png" /><figcaption>New phrase search row at the top</figcaption></figure><p>When a user taps on that row, it will execute a search and return results that contain that word or phrase in various places (e.g., class name).</p><p>What if the inputted term returns no results? That’s likely to happen and is one of the reasons we had our previous implementation in place. However, we addressed this by adding a fallback feature, whereby we map ahead of time common words and phrases to underlying tags in our system in case we get no results back for the user’s original search term. This often happens with misspellings.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/932/1*Xa0KtfSt5qBqK7z5SVpMYg.gif" /><figcaption>Searching yoga with a typo</figcaption></figure><p>This allows us to have the flexibility of searching by any arbitrary text (and returning results in situations where we may not have prior) while mitigating the increase in zero-result states.</p><h3>Milestone Three: Multi-location Search</h3><p>In addition to phrase-based search, another frequently requested search function was the ability to view all the locations of a particular brand or studio chain at once. Let’s use Barry’s in New York City for example. Previously, our UI would only display a list of Barry locations and users would have to tap into each one to view class times. This proved tedious and cumbersome and we knew we could do better.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ve3k070fg1NyiWDas3DmoA.png" /><figcaption>Search for Barry’s in Manhattan (before)</figcaption></figure><p>We made search smarter by detecting when a user searches by a brand like Barry’s and displaying a new “See all locations” row. Tapping this will take you to search results filtered by just that brand. Now you can browse availability in a consolidated list and view on our map!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/932/1*7DGniR1EbksyDMTXjJ4uNQ.gif" /><figcaption>Search for Barry’s in Manhattan (after)</figcaption></figure><h3>Experimentation Setup and Results</h3><p>These features were gated behind Optimizely feature flags and experiments to control rollout and to ensure we didn’t harm our existing metrics. We used Optimizely’s <a href="https://www.optimizely.com/insights/feature-experimentation-demo/?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=nam_all_brandsearch_everg_na_na_conv&amp;utm_content=&amp;gad=1&amp;gclid=CjwKCAjwv-2pBhB-EiwAtsQZFFktoWOmS8RanpIcYETUtFhytOABcJDrxvksTK9273G3yJ14KNHcpBoCnfIQAvD_BwE">Full Stack</a> framework and mobile SDKs to integrate their platform with our code. The first experiment for milestone one was simply an A/B test to ensure our change to leverage additional metadata fared better than the control. In this stage, the key metrics we looked at were CVR, CTR, and recall (ability to retrieve more relevant results from all relevant results). Recall was measured by manually checking for relevancy and results volume using a sample set of frequent queries and calculating the mean average precision @ position K (Map@K).</p><p>After validating our new metadata hypothesis through improvements across CVR, CTR and recall, we ran an experiment for user-facing changes in milestones two and three that split users into four groups based on what metadata was used–<em>control</em>, <em>explicit_class</em>, <em>explicit_venue</em>, and <em>explicit_class_venue</em>. At this stage since the user experience was noticeably changing, we added usability scores and user research for qualitative feedback metrics. We found that <em>explicit_class</em>, the variant that only included class name metadata, performed the best in terms of CVR, CTR, and recall. However, the usability scores and user research sessions highlighted improvements needed in our multi-location and fallback search experience. For example, when users in the <em>explicit_class</em> variant were searching for ‘Barrys’ or ‘Crunch’ and pressing enter, we were taking them to a fallback search for the nearest tag-based genre ‘Barre’ and ‘Core’ instead of understanding their intent to look for those business locations.</p><p>After making tweaks to ensure user intent was being better handled (‘Barrys’ showed ‘Barry’s’ locations and ‘Crunch’ showed ‘Crunch Gym’ locations) we heard from users through user research sessions that the multi-location experience better met their expectations. Once we felt confident that we had addressed the major concerns, we fully rolled out the feature set so all of our mobile users could experience the new and improved search experience.</p><h3>Acknowledgements</h3><p>This work wouldn’t have been possible without close collaboration between backend engineers (Bree and Rachel), Android engineers (Rich and Ridwan), Madhu our product manager, Jenn our engineering manager, Josh for QA, and George our designer. We’re thrilled with the results and testimonials so far and I’m excited to see what further improvements to search we can make in the new year!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9150bcdf672b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/classpass-engineering/introducing-new-search-modalities-on-mobile-9150bcdf672b">Introducing New Search Modalities on Mobile</a> was originally published in <a href="https://medium.com/classpass-engineering">ClassPass Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Career Paths for Senior Engineers at ClassPass]]></title>
            <link>https://medium.com/classpass-engineering/career-paths-for-senior-engineers-at-classpass-15e7bd88f613?source=rss-71c5568dce47------2</link>
            <guid isPermaLink="false">https://medium.com/p/15e7bd88f613</guid>
            <category><![CDATA[engineering-manager]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[development-process]]></category>
            <category><![CDATA[engineering-management]]></category>
            <dc:creator><![CDATA[Ray Kim]]></dc:creator>
            <pubDate>Thu, 18 Aug 2022 15:39:57 GMT</pubDate>
            <atom:updated>2022-08-18T15:39:57.208Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iPH60M7LAnx5ybxVRDc9ng.png" /><figcaption>Individual contributor (top) and engineering manager (bottom) ladder</figcaption></figure><p>At ClassPass, senior engineers play a critical role in our engineering organization. They serve as tentpoles on our teams and mentors for more junior engineers. At ClassPass, for engineers below the senior level, career progression is fairly linear — in X years, an engineer should be performing at the next level. However, once an engineer hits the senior level at ClassPass, multiple paths open. We trust that they will spend their time in the best way possible, which means that, while there is less linearity to their career progression, there is also a great opportunity for impact.</p><p>We want to open our process to give candidates and others interested insight into our philosophy on engineering career growth. At a high level, senior engineers can grow in three ways. First, they can continue operating as a senior engineer indefinitely — the heart of a team. Second, they can become a staff engineer, pulling further away from shipping code directly to consumers so that they can influence the entire organization technically. Finally, they can become an engineering manager, leading a team of other engineers.</p><h3>Senior Engineer</h3><p>The first important distinction to make between a mid-level engineer and a senior engineer at ClassPass is that senior is a level at which an engineer can stay for the rest of their career — there is no expectation that they must become a staff engineer in X years. We believe that an experienced senior engineer has a distinct set of responsibilities compared to a staff engineer along with a distinct set of rewards. Of course, there is overlap but we need both roles.</p><p>A senior engineer’s primary job is to make their team the best it can be. At the role’s core is shipping great products and serving as a role model for other engineers on the team. This includes mentoring junior engineers on the team, coming up with ways to improve the team&#39;s workflow, and pitching in when others are out. Senior engineers primarily grow by taking on more complex projects. This might mean a project with ambiguous scope, shifting requirements, many stakeholders, or a combination of these. Staff engineers, on the other hand, aren’t primarily shipping great products nor do they grow from simply making their projects more complex. Their role is to think about our broader technical strategy and that will involve higher-leverage activities.</p><p>So how does one continue growing as a senior engineer who focuses on a team directly shipping impact to our consumers? One way is to continue growing their technical acumen — there are always projects with a larger scope to take on. They also might consider embodying one of several archetypes:</p><ul><li><strong>Tech lead</strong>: They balance coding, project management, unblocking and helping triage technical issues, reviewing technical documents, and delegating technical work to other engineers on their squad. They work closely with the engineering manager.</li><li><strong>Architect</strong>: They understand a core part of the squad’s codebase/product deeply and can leverage their expertise to lead large-scale technical initiatives in that area. For example, they may lead exploration in integrating a new vendor to expand a product capability.</li><li><strong>Solver</strong>: They jump into emergencies or interrupts and tackle tricky technical issues. They’re an expert at triaging and making the right technical call as quickly as possible. They also know when to dig deep and focus on a given problem for an extended period.</li><li><strong>Prototyper: </strong>They help extend the bandwidth of the engineering manager and determine the feasibility of speculative features or approaches.</li></ul><h3>Staff Engineer</h3><p>The second path a senior engineer might consider is becoming a staff engineer. A staff engineer is primarily focused on how they can leverage their expertise to have maximal organization-wide impact. They’re not only a technical leader on their team but also an important driver of wider engineering initiatives.</p><p>A high leverage activity is usually something where the results are worth a high multiple of the energy put into it. The highest leverage activities for staff engineers will typically fall into two different categories:</p><ul><li><strong>Technical</strong>: They design solutions that are resilient, scale easily, and make the work of others easier. They provide technical guidance to implement those solutions.</li><li><strong>People</strong>: They help grow others across the entire organization, whether that’s through 1:1 mentorship or leading a book club or paper reading session.</li></ul><p>Generally, our Staff engineers focus comparatively less of their time on directly shipping impactful products and more of their time on maximizing the impact all of engineering can have. If you’re looking to understand more about our staff engineering philosophy, a good starting point is Will Larson’s book <a href="https://staffeng.com/book">Staff Engineer: Leadership beyond the management track</a>.</p><h3>Engineering Manager</h3><p>The third path for a senior engineer is to become an engineering manager. It’s important to note that we view this as a lateral move onto a different career ladder and that it is one of three paths for continued growth as a senior engineer rather than the sole way of driving more impact. Simply put, their responsibilities move from building technical projects to leading technical people. Their day-to-day will look dramatically different. A typical week will include recurring 1:1s with the engineers they manage, syncs with the product manager and designer of their team, sprint meetings, syncs with other managers, technical deep dives, and interviews (sometimes technical, sometimes informational). They will also have impromptu calls to work through immediate concerns, take on interrupts so that engineers on their team aren’t distracted, and handle the administrative parts of the job (e.g., approving PTO and expenses). Because we require our engineering managers to be deeply technical, taking on interrupts may mean writing some code too!</p><p>Their performance is tied to their team’s output and not their own individual contribution. This is a fundamental shift in how they’ll think about their own productivity and will take some time to get used to. They won’t be “productive” if they create a lot of pull requests. Rather, they’ll be productive if their team continues to deliver projects that have a material impact on the business, while keeping the team healthy and growing.</p><p>Engineering management can be immensely fulfilling for some engineers and not a good fit for others. That’s why, at ClassPass, senior engineers who become engineering managers can transition back to being an engineer if it isn’t a good fit for them. We strongly believe that having great leaders, whether they are individual contributors or managers, is critical to our success and that skills learned through the experience of managing will continue to be useful to engineers who transition back to being individual contributors.</p><p>We hope this helps you better understand how ClassPass approaches career growth for more senior engineers. We place a lot of trust in our more senior roles and they have a lot of opportunity (and responsibility) to drive impact — whether that’s at the heart of a team shipping products, a technical leader helping the organization improve, or a manager working with a team.</p><p>You’re reading the ClassPass Engineering Blog, a publication written by the engineers at ClassPass where we’re sharing how we work and our discoveries along the way. You can also find us on Twitter at <a href="https://twitter.com/ClassPassEng">@ClassPassEng</a>.</p><p>If you like what you’re reading, you can learn more on our <a href="https://classpass.com/about/careers">careers website</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=15e7bd88f613" width="1" height="1" alt=""><hr><p><a href="https://medium.com/classpass-engineering/career-paths-for-senior-engineers-at-classpass-15e7bd88f613">Career Paths for Senior Engineers at ClassPass</a> was originally published in <a href="https://medium.com/classpass-engineering">ClassPass Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How the Upcoming Widget was Built]]></title>
            <link>https://medium.com/classpass-engineering/how-the-upcoming-widget-was-built-9822735996bd?source=rss-71c5568dce47------2</link>
            <guid isPermaLink="false">https://medium.com/p/9822735996bd</guid>
            <category><![CDATA[widget]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[apple]]></category>
            <dc:creator><![CDATA[Ray Kim]]></dc:creator>
            <pubDate>Fri, 29 Jan 2021 18:49:55 GMT</pubDate>
            <atom:updated>2021-01-29T18:49:55.393Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/576/1*_ZTntoXDiCUGy9vw2uzlvQ.png" /></figure><p>When iOS 14 launched this past fall, one of the most anticipated features was the ability to add widgets to your home screen. These widgets can replace an app’s icon and add interactivity and dynamic content in a larger visual footprint. For example, a weather widget can show you today’s high and low temperatures; a stocks widget can show you your portfolio’s return for the day. When this feature came out, I felt this would be the perfect opportunity for ClassPass to showcase its own widget. A natural use case would be displaying your upcoming class reservations.</p><p>Widgets are built using a new UI framework by Apple called <a href="https://developer.apple.com/documentation/swiftui/">SwiftUI</a>. Announced in 2019, SwiftUI took the iOS developer community by storm with its radically simpler approach to building UI components for iOS apps. Gone are the days of Storyboards (and their merge conflicts) and IBOutlets. Now, you can use the new Canvas Editor in Xcode to build UI components declaratively and see the code built in real-time–React developers will feel right at home!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vicw6xQ2WoD7rSn-bH5Ksg.png" /><figcaption>Canvas Editor in Xcode 12</figcaption></figure><p>This new syntax is powered by core language changes introduced in <a href="https://swift.org/blog/swift-5-1-released/">Swift 5.1</a> such as opaque return types, function builders, and property wrappers (formerly known as property delegates). For more, check out this great <a href="https://www.swiftbysundell.com/articles/the-swift-51-features-that-power-swiftuis-api/">overview</a> by John Sundell.</p><p>Ever since SwiftUI came out, our iOS team had been trying to find an opportunity to actually build something with it. Unfortunately, SwiftUI doesn’t support iOS 12 or below so we had to wait until we dropped support for iOS 12. Now that iOS 14 came out and the vast majority of users are on iOS 13 and up, we were able to drop support and finally see how SwiftUI works within our codebase! The widget was the perfect use case because it was net-new and could easily be worked on without interruptions from other developers.</p><p>In the rest of this article, I’ll dive deeper into widget specifics–how I fetched and displayed data and how SwiftUI allows for powerful widgets right out of the box with features such as Dark Mode and localization support.</p><h3>MVP: StaticConfiguration and App Groups</h3><p>In order to implement a widget, you need to use both WidgetKit and SwiftUI. Apple has great <a href="https://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui">documentation</a> on how to build a basic widget so I won’t go over every step here. The first major decision to make is whether you want your widget to use a StaticConfiguration or an IntentConfiguration.</p><p>StaticConfiguration is used for widgets that display data without direct user input (see example widget below).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/201/1*oNAwgVSUlCTYrRfIEPavUw.png" /><figcaption>Apple’s simply fetches publicly available data</figcaption></figure><p>An IntentConfiguration lets you build a widget that can be customized by the user. For example, an airline widget can take your flight and confirmation numbers as inputs to show you your upcoming flight. For our widget, I decided to use a StaticConfiguration for simplicity and leveraged the ClassPass app to pass upcoming reservations data.</p><p>First, we setup our configuration inside the main struct UpcomingWidget which acts as the skeleton:</p><pre>@main</pre><pre>struct UpcomingWidget: Widget {</pre><pre>   let kind: String = “UpcomingWidget”</pre><pre>   var body: some WidgetConfiguration {</pre><pre>      StaticConfiguration(kind: kind, provider: UpcomingProvider()) { entry in</pre><pre>        UpcomingWidgetEntryView(entry: entry)</pre><pre>      }</pre><pre>      .configurationDisplayName(“tab-upcoming”)</pre><pre>      .description(“upcoming-widget.description”)</pre><pre>      .supportedFamilies([.systemSmall])</pre><pre>   }</pre><pre>}</pre><p>A few things to note. UpcomingProvider conforms to <a href="https://developer.apple.com/documentation/widgetkit/timelineprovider">TimelineProvider</a> which controls when to display each snapshot, or entry, of our widget over time. UpcomingWidgetEntryView contains the UI components of our widget which I’ll go into more detail later in the article.<br>.configurationDisplayName() is a modifier that controls what the title within the widget selection screen will say and .description() controls the subtitle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*7eIqW3QxQfxmS2OCc0CCQg.png" /></figure><p>.supportedFamilies() is an important modifier to determine what widget sizes your widget code supports. There are three <a href="https://developer.apple.com/documentation/widgetkit/widgetfamily">sizes</a>: .systemSmall, .systemMedium, and .systemLarge. On an iPhone, .systemSmall will show up as a square that takes up about the same size as four app icons. Below are examples of .systemMedium and .systemLarge. One important distinction between small and medium/large widgets is that small widgets only have one tap target–the entire widget–whereas the medium and large widgets allow you to have multiple tap targets. For example, Spotify’s medium-sized widget can display your recent albums and the Weather app’s large-sized widget can display highs and lows for multiple days:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/391/1*o7PDW_HA96O33g1mGZyIHQ.png" /></figure><h4>Using App Groups Part 1: Passing JSON to the Widget</h4><p>There are a number of ways to load data into your widget. You can make network calls directly within your TimelineProvider or you can pass data from your main app using App Groups. In the upcoming reservations widget, I used both App Groups to pass reservation data and made a separate network call to load an image from a given image url. Passing data via App Groups is an easy way to get most of the data you’ll need for your widget. If you haven’t setup your app to use the App Group entitlement then I suggest reading Apple’s <a href="https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups">documentation</a>. You’ll need to create an App Group name such as group.com.yourapp which you’ll then use in both your app and your widget.</p><p>In order to pass data from your app to your widget, you’ll need to use <a href="https://developer.apple.com/documentation/widgetkit/widgetcenter">WidgetCenter</a>–an object that gives you access to all widgets for your app. For example, we want to update our widget when a user books a new class. In our UpcomingClassesViewController we make a network call to fetch the latest reservations for the user and then call our helper method writeToSharedContainer(_:[Reservation]). We then update our widget as follows (edited for brevity):</p><pre>// 1<br>@available(iOS 14.0, *)</pre><pre>func writeToSharedContainer(_ reservations: [Reservation]) {</pre><pre>   // 2<br>   guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: &quot;group.com.yourapp&quot;) else {</pre><pre>      return</pre><pre>   }</pre><pre>   let reservationUrl = url.appendingPathComponent(&quot;upcoming.json&quot;)</pre><pre>   do {</pre><pre>      let encoder = JSONEncoder()</pre><pre>      let dataToSave = try JSONEncoder().encode(reservations)</pre><pre>      try dataToSave.write(to: reservationUrl)<br>      // 3</pre><pre>      WidgetCenter.shared.reloadTimelines(ofKind: “UpcomingWidget”)</pre><pre>   } catch {</pre><pre>      return</pre><pre>   }</pre><pre>}</pre><ol><li>Since widgets are only supported in iOS 14, if your app also supports iOS 13 or lower, then you’ll need to wrap any code that uses WidgetCenter in an <a href="https://nshipster.com/available/">API availability attribute</a>.</li><li>This is how you access data that’s stored for a particular App Group. It’s very similar to using UserDefaults for local storage.</li><li>Call <a href="https://developer.apple.com/documentation/widgetkit/widgetcenter/reloadtimelines(ofkind:)">reloadTimelines(ofKind: String)</a>to update a specific widget. In the earlier code snippet for the widget, we had set kind to be &quot;UpcomingWidget&quot; so that it can be accessed here.</li></ol><h4>Using App Groups Part 2: Loading JSON from the Widget</h4><p>Now that we’ve written JSON to our cache, we can now load the contents within our TimelineProvider. In order to conform to TimelineProvider, you need to implement three methods. Here’s what my implementation looked like:</p><pre><br>func placeholder(in context: Context) -&gt; UpcomingEntry {</pre><pre>   UpcomingEntry(date: Date(), reservations: nil, image: nil)</pre><pre>}</pre><pre>func getSnapshot(in context: Context, completion: @escaping (UpcomingEntry) -&gt; Void) {</pre><pre>   let entry = UpcomingEntry(date: Date(), reservations: nil, image: nil)</pre><pre>   completion(entry)</pre><pre>}</pre><pre>func getTimeline(in context: Context, completion: @escaping (Timeline&lt;UpcomingEntry&gt;) -&gt; Void) {</pre><pre>   fetchReservationsAndImage { upcomingEntry in</pre><pre>      let timeline = Timeline(entries: [upcomingEntry], policy: .atEnd)</pre><pre>      completion(timeline)</pre><pre>   }</pre><pre>}</pre><p>The first method is used as a default template that Apple uses for certain situations such as first loading your widget. It’s similar to a placeholder asset for a watch complication in watchOS. It’s different in that for a widget we use a SwiftUI view to represent the placeholder.</p><p>The second method controls what you want your widget to look like during certain “transient moments” as defined by Apple. For example, Apple will fetch a snapshot of your widget when a user is viewing it in the widget selection menu. For the purposes of our widget, we don’t need to support multiple transient states–matching the placeholder state is sufficient.</p><p>The third method is where things get interesting. Here we can create our timeline, basically an array of timeline entries, to control when to show snapshots of our widget over time and with what data. This is where you want to make asynchronous network calls such as fetching an image. Above I have a helper function fetchReservationsAndImage(completion:) that encapsulates the logic for piecing together all the data we need. At a high level, we decode the JSON data from the app, fetch an image from the imageUrl field in the first reservation object, and then call the completion block with an UpcomingEntry object.</p><h4>UpcomingEntry: Managing State</h4><p>UpcomingEntry is a struct that conforms to <a href="https://developer.apple.com/documentation/widgetkit/timelineentry">TimelineEntry</a> and represents the state of the widget at a given date. This is also where you can optionally specify the entry’s relevance which is useful for when you have widgets organized in a stack. For the purposes of our MVP, I opted not to implement this but if you’re interested you should take a look at Apple’s <a href="https://developer.apple.com/documentation/widgetkit/timelineentryrelevance">documentation</a>.</p><p>A TimelineEntry serves as the view model for your SwiftUI views. This is where I represent discrete states that I want to map to specific views. The MVP version of the widget supports three states: logged out, nothing upcoming, and upcoming reservation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*6qaCH7OgNlLM9h6S1196BA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*ln2zZhAPGTNu1vWI_9cFWg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*KA2ZzWic-fIyQP8n4vOvDQ.png" /><figcaption>Left to right: Logged out, nothing upcoming, upcoming reservation</figcaption></figure><p>Given we get back an optional array of reservations (retrieval from App Group container could fail) , we need to map all cases into three.</p><pre>enum UpcomingEntryState {</pre><pre>   case upcomingReservation(startTime: Date, name: String, venueName: String, imageUrl: URL)</pre><pre>   case nothingUpcoming</pre><pre>   case loggedOut</pre><pre>}</pre><p>Using <a href="https://www.hackingwithswift.com/sixty/2/9/enum-associated-values">associated values</a>, we can succinctly pass specific data to the view using a lightweight enum. Let’s take a look at UpcomingWidgetEntryView to see how we then map our data-driven state to UI components.</p><h4>UpcomingWidgetEntryView: Mapping State to UI in SwiftUI</h4><p>Using our custom TimelineEntry as a view model, we can simply lay out our views. SwiftUI is incredibly modular and encourages reusable views to avoid excessive nesting. Given the designs for our MVP, I was able to use a shared container view and simply swap out its child view for one of three views: UpcomingReservationView(startTime:name:venueName:) , NothingUpcomingView(), and LoggedOutView() (edited for brevity):</p><pre>struct UpcomingWidgetEntryView: View {</pre><pre>   var entry: UpcomingEntry</pre><pre>   var body: some View {</pre><pre>      VStack(alignment: .leading) {</pre><pre>         HStack(alignment: .top) {<br>            // 1</pre><pre>            if let image = entry.image {</pre><pre>               Image(uiImage: image)</pre><pre>                  .resizable() // and other modifiers</pre><pre>            } else {</pre><pre>               Image(systemName: &quot;square.fill&quot;)</pre><pre>                  .resizable() // and other modifiers</pre><pre>                  .redacted(reason: .placeholder)</pre><pre>            }</pre><pre>            Spacer()</pre><pre>            Image(&quot;logo&quot;)</pre><pre>         }</pre><pre>         VStack(alignment: .leading) {<br>            // 2</pre><pre>            switch entry.state {</pre><pre>               case .upcomingReservation(let startTime, let name, let venueName, _):</pre><pre>                  UpcomingReservationView(startTime: startTime, name: name, venueName: venueName)</pre><pre>               case .nothingUpcoming:</pre><pre>                  NothingUpcomingView()</pre><pre>               case .loggedOut:</pre><pre>                  LoggedOutView()</pre><pre>            }</pre><pre>         }</pre><pre>      }</pre><pre>      .padding(16) // and other modifiers</pre><pre>   }</pre><pre>}</pre><ol><li>If our entry contains an image that we fetched from our timeline provider, then we use the Image(uiImage:) initializer to load the image; if it’s nil (either because it’s a placeholder, an error occurred, or the current entry state doesn’t display an image), we show a placeholder image using the handy <a href="https://developer.apple.com/documentation/mapkit/map/3663432-redacted">.redacted(reason: .placeholder)</a> modifier which will automatically display a placeholder courtesy of Apple. Note this also works for Text() views and can be a convenient way to have a built-in loading state for your UI. Since the designs for the widget show a square in the top left of the widget for all states, we can separate this logic out from each individual child view.</li><li>You can have switch statements inside VStack views to conditionally show one view over another. The only view we inject data into is UpcomingReservationView(startTime:name:venueName:) .</li></ol><h3>SwiftUI Freebies</h3><p>If you’ve never tried out SwiftUI, building a widget is a great way to get your feet wet. I won’t go into too much detail about the basics of SwiftUI syntax but I’ll point out two interesting features that make SwiftUI worth learning.</p><h4>Dark Mode</h4><p>First, Dark Mode support is built right in. When setting color for, say, the background of your widget, you use a special struct called <a href="https://developer.apple.com/documentation/swiftui/color">Color()</a> that roughly maps to UIColor in UIKit . The nice thing about Color() is that you can specify a custom color from Assets.xcassets where you can set custom color sets. For example, I have a color set called &quot;WidgetBackground&quot; that looks like this in Xcode:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/790/1*nvdtyPy0zPSYEIif_E48RQ.png" /></figure><p>You can specify what the particular color will look like in Dark Mode simply by modifying the color above where it says “Dark Appearance” just like any other color. Then in your view you can use it like this:</p><pre>var body: some View {</pre><pre>   VStack {</pre><pre>    …</pre><pre>   }</pre><pre>   .padding(16)</pre><pre>   .background(Color(“WidgetBackground”))</pre><pre>}</pre><p>There are also system colors such as Color(.systemRed) and even Color(.systemBackground) if you don’t want to deal with custom color sets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/338/1*7hggicCKrRdmV1LjEkT9XA.gif" /></figure><h4>Localization</h4><p>Adding support for multiple locales for your widget is incredibly straightforward thanks to built-in SwiftUI and WidgetKit components that support localization out of the box. If your app already supports multiple languages, then it’s the same process to use localizable strings in your widget: add localizable string key-value pairs to your existing Localizable.strings files and they’ll be accessible in the widget whenever you use Text() or modifiers that support localizable strings by default. For example, as mentioned earlier, <a href="https://developer.apple.com/documentation/widgetkit/intentconfiguration/configurationdisplayname(_:)-3ubj0">.configurationDisplayName()</a> and <a href="https://developer.apple.com/documentation/swiftui/widgetconfiguration/description(_:)-4q9pa">.description()</a> support localizable strings. Here are some of the key-value pairs in our Localizable.strings (English) file:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/681/1*hmaq-NRvQvRrRkU4j0ucBA.png" /><figcaption>Localizable strings</figcaption></figure><p>Add translations to all of your other Localizable.strings files and when used for .configurationDisplayName() and .description() it will show up as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*G4DZPDRR-T0unVCCxaJaCA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*OktNgsIs9RuhtwoxTLSDcg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*0pY284HldNC70QFI1-fBRw.png" /><figcaption>Left to right: Dutch, French, Portuguese (Portugal)</figcaption></figure><h3>Conclusion</h3><p>That was a whirlwind tour of how I got our first iOS 14 widget up and running while experimenting with SwiftUI in our codebase for the first time. Though there was a learning curve to the intricacies of how WidgetKit and SwiftUI work in tandem (I’ll spare you the codesigning horrors and other Xcode pitfalls), the enterprise was well worth it. There’s so much more we can do of course, such as automatically updating the widget for future reservations if there are multiple, supporting multiple widget sizes, and much more.</p><p>Thank you <a href="https://twitter.com/sanjaythehacker">Sanjay</a> for the iOS support and <a href="https://twitter.com/tomg">Tom</a> for creating beautiful designs to work off of. If you’re on iOS version 6.0.0 and up, try adding the widget (make sure to open the app first) and book a class! And if you’re an iOS developer, I hope this article helped you start on your own widgets!</p><p>You’re reading the ClassPass Engineering Blog, a publication written by the engineers at ClassPass where we’re sharing how we work and our discoveries along the way. You can also find us on Twitter at <a href="https://twitter.com/ClassPassEng">@ClassPassEng</a>.</p><p>If you like what you’re reading, you can learn more on our <a href="https://classpass.com/about/careers">careers website</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9822735996bd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/classpass-engineering/how-the-upcoming-widget-was-built-9822735996bd">How the Upcoming Widget was Built</a> was originally published in <a href="https://medium.com/classpass-engineering">ClassPass Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating a Fluid Scroll Experience on iOS]]></title>
            <link>https://medium.com/classpass-engineering/creating-a-fluid-scroll-experience-on-ios-faeb29be3bdb?source=rss-71c5568dce47------2</link>
            <guid isPermaLink="false">https://medium.com/p/faeb29be3bdb</guid>
            <category><![CDATA[mobile-apps]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Ray Kim]]></dc:creator>
            <pubDate>Mon, 24 Jun 2019 22:13:20 GMT</pubDate>
            <atom:updated>2019-06-26T15:37:25.429Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B2I_WV8F0GGoKAbQ8jJ4-g.png" /></figure><p>In the ClassPass app, by far the most common path users take once they open it is tapping the “Find a Class” tab at the bottom and then searching for classes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/764/1*dnNIO3-Kz35CEj0-3tgFrA.gif" /></figure><p>Our search experience is crucial because it’s the first impression users have about our product. We’re always looking for ways to improve this experience and recently began to roll out a redesign to better surface all the studios users can choose from. Working on this redesign has been the most challenging, fun, and fulfilling feature I’ve worked on thus far at ClassPass!</p><h3>Growing Pains</h3><p>One of the main reasons we decided to redesign search was because our existing list UI was unable to adequately reflect the growing number of studios available. The list shows you available classes offered today and, depending on your device size, may only show a handful of classes at specific times above the fold. As the number of studios on ClassPass increased, we noticed that new users weren’t able to browse studios quickly. We have a map view accessible via a Map button at the top so that users can view studios geographically but user testing videos showed that most people simply didn’t know there was a button up there to switch to a map. As a result, users’ first impressions would be shaped by the small subset of classes they saw on their screen.</p><p>I worked closely with Tom, our designer on the Search team, to build a more intuitive search experience that makes it easier for users to browse studios before they book specific classes. The first iteration was simply to test showing a portion of the map above the list of schedules that users can tap to switch to the full map view. We also added buttons on the map to make it easier to toggle between the two views.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/807/1*BQfQz-jRul-JdMRld_pIJA.png" /></figure><p>This was a great quick win and we got early positive feedback that surfacing the map right away gave users a better sense of how many studios were near their location.</p><p>As we invested more heavily into browsing by studios on the map, we started to look at complementary ways we could make it easier to search by studios rather than by classes. What if we added a studios list similar to the classes list and let users choose which list to view? Users would land on the studios list by default so that they can browse studios before diving into specific classes. We also wanted to improve the way users can switch between the list and map views. We looked at how other apps implemented map search interactions. If you’ve used Google Maps or Apple Maps, you’ve probably noticed that draggable list with rounded corners and the small gray handle in its center.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/1*XMV2JDEICkDKcjJx4TC1Uw.png" /><figcaption>Apple Maps</figcaption></figure><p>It’s easy to use and ergonomic since users can simply swipe up and down with their thumb to either focus on the list or the map. The next iteration for us was to try and build this for our app.</p><h3>First Implementation: Using a Container UIScrollView with a Content Inset</h3><p>As I researched how to build this from scratch (it’s not a built-in UI component on iOS), Sanjay, another iOS engineer at ClassPass, recommended <a href="https://blog.skagedal.tech/2018/08/04/bottom-sheet.html">skagedal’s article</a> on how to build an Apple Maps-like UI. It looked like the right approach–a UIScrollView with a custom contentInset with overrides on UIScrollViewDelegate methods to capture swipes and pan gestures. Our use case was a bit more complex because we weren’t simply dragging a list of directions like on Apple Maps–our scroll view would contain a segmented control component that switched between two types of lists, a UITableView for the studios list, and our existing classes list which lives inside a UIPageViewController. Despite our added complexity, I felt confident that I could apply this approach with our use case.</p><p>Turns out, I was <em>almost </em>right. After several days of building and perfecting it, I came close to the right UI but was left with one specific interaction that wasn’t as fluid as I would’ve liked–let’s call it the “continuous scroll” interaction. Compare the Apple Maps interaction with my first attempt below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/815/1*gbDDVMvDcdAxkCc4uiDQdQ.gif" /><figcaption>Apple Maps</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/815/1*Ec0OTwyp0QXdpinPx7JlzQ.gif" /><figcaption>Notice how it stops at the top and only works once the pan gesture finishes and a new one begins.</figcaption></figure><p>The problem is with how Apple handles pan gestures on scroll views that themselves contain scroll views. A <a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures/handling_pan_gestures">pan gesture</a> is when a user moves one or more fingers across the screen. Nested scroll views could clearly lead to some confusing interactions so Apple decided to control exactly what happens in ambiguous situations for us (sadly without documentation): when the user swipes up on a scroll view that is offset from its top whose internal scroll view is at its respective top (i.e., its contentOffset is 0), the pan gesture will be registered only by the parent scroll view <em>for as long as that pan gesture has not ended, failed or been cancelled</em>. This means that we can’t convert a pan gesture registered on the container UIScrollView to its internal UIScrollView in the same movement. Once we reach the container’s top, the container will either stop completely or bounce if you have bounce enabled. I tried playing around with <a href="https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate/1624208-gesturerecognizer">gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)</a> to recognize the pan gesture for both the container and its internal scroll view at the same time–I got close but still failed to achieve the right interaction.</p><h3>Second Implementation: Using a Container UIView with Custom UIPanGestureRecognizers</h3><p>I started to feel like this would be as close as I could get given the amount of time I had to implement it. Other mobile engineers, Tom, and I weighed our options and despite everything else looking great, we all felt that it was important that we got this interaction right and that it was as smooth as possible before we shipped it to all of our users.</p><p>Tom did a bit of research to help me out and came across a <a href="https://stackoverflow.com/questions/37967555/how-can-i-mimic-the-bottom-sheet-from-the-maps-app">StackOverflow thread</a> on various implementations people came up with. At first, I thought I had exhausted my research online on how people got this continuous scroll interaction right using a container UIScrollView. However, as I read through more and more implementations, I came across a few that solved this exact issue but in a completely different way. I realized I had been looking at the wrong posts while trying to troubleshoot because I was trying to make it work with a container UIScrollView.</p><p>I went through how they built their version of the interaction and found their solution to be deceptively simply: use a vanilla UIView as a container with a target selector method added to its internal scroll view’s UIPanGestureRecognizer. No UIScrollView, no contentInset. Here’s how I began to apply this for our use case:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b83514d64715c37b281a3c1ea5cdc16f/href">https://medium.com/media/b83514d64715c37b281a3c1ea5cdc16f/href</a></iframe><p>Implementing it this way meant scrapping my container UIScrollView approach completely. I decided I had to for the sake of making this work correctly.</p><h4>Continuous Scroll Part 1</h4><p>Apple’s API for <a href="https://developer.apple.com/documentation/uikit/uipangesturerecognizer">UIPanGestureRecognizer</a> provides access to five pan gesture states: .began, .changed, .failed, .ended, and .cancelled. Using these five states, we can control when to only drag the container view (e.g., while the container hasn’t reached the top) and when to only drag its internal scroll view. To achieve the effect of dragging only the container view, you need two things. First, while the pan gesture is in the .changed state, update the container view’s frame to a calculated offset between the min and max vertical offsets you want for your draggable list (in our case we had to offset it from a header).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/347ad6ccbef5a8eb1d1527c2c510c27e/href">https://medium.com/media/347ad6ccbef5a8eb1d1527c2c510c27e/href</a></iframe><p>Second, capture the internal UIScrollView&#39;s offset through its delegate method <a href="https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619392-scrollviewdidscroll">scrollViewDidScroll(_:</a>) and pass the scroll view through to ContainerViewController. In our code, the internal scroll view was technically a UITableView but it doesn’t matter becauseUITableView inherits from UIScrollView and we still have access to its delegate methods!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/198a62dba6e8f14d45767ed1390ebd60/href">https://medium.com/media/198a62dba6e8f14d45767ed1390ebd60/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/48c9ebf49d8b983df55ec54f00c391b4/href">https://medium.com/media/48c9ebf49d8b983df55ec54f00c391b4/href</a></iframe><p>By setting this scroll view’s contentOffset.y to 0for a specific range, we can keep the internal scroll view from scrolling <em>while using the pan gesture to move the container view</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-zeu7RRYRfO91nPAGqlgkw.gif" /></figure><h4>Continuous Scroll Part 2</h4><p>Once we’ve reached the top, we want to stop updating the container view’s offset and start scrolling the internal scroll view. First, because we’ve set the internal scroll view’s contentOffset.y value to be 0 only for a specific range, the scroll view should scroll normally when not in that range. Second, in handlePan(_:), we check whether the internal scroll view’s contentOffset is greater than 0 (i.e., the internal scroll view is now scrolling) and return before calling the update frame code from earlier.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/059baff75a53a94f992b640c14887142/href">https://medium.com/media/059baff75a53a94f992b640c14887142/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zk7BLYiORKLNV2-bqC0DfA.gif" /></figure><p>With these two parts, we have the foundation for a continuous scroll!</p><p>There’s of course a lot more that goes into getting this entire interaction right. There’s the map parallax, the swipe snapping points, multiple internal scroll views…the list goes on. Yet, none of that was worth the effort if we didn’t solve this interaction. Now, we could finally ship something that was not only functional but delightful to use.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/764/1*nYMsPflCxt88TZdRWLQ5BA.gif" /></figure><h3>Conclusion</h3><p>First, I’d like to say thanks to <a href="http://twitter.com/tomg">Tom</a> and <a href="https://twitter.com/sanjaythehacker">Sanjay</a> for their guidance and support. Without them, it would’ve taken me a much longer time to get this feature right. I’d also like to thank my team for giving me the opportunity to work on such an impactful feature. From a mobile engineer’s perspective, there’s simply no other part of the app I could work on that has as much impact as working on improving the search experience. It’s by far the most important function of the mobile app and making it as polished and easy to use as possible is key to making it not only highly functional but also enjoyable to use. Thanks for reading!</p><p>You’re reading the ClassPass Engineering Blog, a publication written by the engineers at ClassPass where we’re sharing how we work and our discoveries along the way. You can also find us on Twitter at <a href="https://twitter.com/ClassPassEng">@ClassPassEng</a>.</p><p>If you like what you’re reading, you can learn more on our <a href="https://classpass.com/about/careers">careers website</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=faeb29be3bdb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/classpass-engineering/creating-a-fluid-scroll-experience-on-ios-faeb29be3bdb">Creating a Fluid Scroll Experience on iOS</a> was originally published in <a href="https://medium.com/classpass-engineering">ClassPass Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Better Programmatic Components Using Closures]]></title>
            <link>https://medium.com/@rckim77/better-programmatic-components-using-closures-50d1086f8b79?source=rss-71c5568dce47------2</link>
            <guid isPermaLink="false">https://medium.com/p/50d1086f8b79</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift-programming]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Ray Kim]]></dc:creator>
            <pubDate>Wed, 25 Apr 2018 15:29:49 GMT</pubDate>
            <atom:updated>2018-04-25T15:29:49.643Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FN0dc82u-swvRz2PXMZEYA.png" /></figure><p><em>TL;DR: Use closures to create modular and concise UI components in code.</em></p><p>There are countless ways to implement UI components in your app. Using Storyboards is a popular option, but there are times you’ll want to lay them out programmatically. In this post, I’ll show you iterative implementations of building the same UI, a simple login screen, programmatically. First, I’ll show you the simplest approach that’s quick to write but hard to maintain. Second, I’ll show you a better approach using private helper methods. Third, I’ll show you the best way to implement UI using closures. If you haven’t used closures before, you’ll quickly realize how powerful they are after reading this!</p><h3>Implementation #0</h3><p>Before we dive into the programmatic approaches, I wanted to briefly touch on Storyboards. They’re ubiquitous in beginner iOS tutorials because they’re easy to visualize and understand. They’re also surprisingly powerful. You can drag and drop components, customize almost all aspects of a UI component (e.g., font size, background color) in real-time, and test what your UI will look like on various device sizes and orientations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IQ5I4zbrz2ZDQuhCrAY98Q.png" /><figcaption>Tip: Use Preview in Interface Builder to view your layout on multiple devices!</figcaption></figure><p>However, there are also times when you want to create your UI programmatically, either partially or completely. For me, it was working with other iOS developers who preferred building UI exclusively in code. Storyboard files can definitely become a headache really quick when working on a shared iOS codebase. As I’ve been writing programmatic UILabel, UIButton, and other core UI components, I of course wanted to learn what are best practices for implementing them. Below are three examples of implementing a simple login screen that illustrate considerations when writing programmatic UI code.</p><h3>Implementation #1</h3><p>Let’s say we wanted to create this screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/740/1*AzgtGsLORSrrKh8pe1LjxQ.png" /></figure><p>Here we have a title label, a caption label, and two buttons. How can we make this programmatically? Well, the simplest approach is to just write it all in viewDidLoad() :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/36156eb0d5eacbc95f5cfb7c8920101a/href">https://medium.com/media/36156eb0d5eacbc95f5cfb7c8920101a/href</a></iframe><p>This is truly the simplest way to go about this, but that’s where the benefits end. What if I wanted another button? You’d have to name it and write a lot of repetitive code. What if I need to rename a label? Then you have to find and replace all instances of that name. I didn’t even include the layout code and you can see how this gets long very quickly! Not only that, it pollutes the viewDidLoad() function which we want to avoid as our project grows. Maybe we can use private helper functions?</p><h3>Implementation #2</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c27b6153b5e51ec41b77c3a005ab001d/href">https://medium.com/media/c27b6153b5e51ec41b77c3a005ab001d/href</a></iframe><p>Now viewDidLoad() looks way cleaner! We refactored our label and button implementations into helper functions that are both private (to restrict access) and reusable (e.g., createLabel(text:textColor:font:) is used to create both title and caption labels). This is better, but there are still some weaknesses.</p><p>First, if we want our UI components to have lots of customizations, the init parameters start to become lengthy and unscalable. In the code snippet above, the createButton(text:textColor:font:backgroundColor:) method has four inputs, one of which has an optional default value. As our buttons get more complicated, we want to weigh the benefit of reusable, helper functions versus bloated, catch-alls that are hard to understand at first glance. Though this function follows the DRY principle, we sacrifice readability (linters will probably flag this as being too long if we add another parameter).</p><h3>Implementation #3</h3><p>In our third implementation, we’re going to use something different: <em>closures</em>. Closures are encapsulated blocks of functionality that pop up in a number of scenarios. In fact, you’ve been using closures all along–functions (global and nested) are a kind of closure! It’s just a more abstract term for it. A closure expression, for instance, can be stored as a variable and executed when read.</p><p>Closures are awesome and worth reading up on (see <a href="https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID94">Apple’s docs)</a>. You can do a lot with them and keep your code modular. We’re going to use them to create our UI components and I’ll explain why this is a great approach:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9d26061445d519daf3db359e0ae2107a/href">https://medium.com/media/9d26061445d519daf3db359e0ae2107a/href</a></iframe><p>First, notice how concise our viewDidLoad() is now! We don’t need bloated function calls anymore to create customized buttons. Second, notice how readable and concise the closures are: each of them instantiates itself and returns itself in curly brackets. Closures in Swift are smart about inferring types–we’re using their syntactic sugar to the max here–so we can remove the boilerplate that comes with explicitly writing out the type signature for closures (read <a href="http://fuckingswiftblocksyntax.com/">this post</a> for details). Third, we can easily rename labels and buttons–all we do is rename the variable! Fourth, the implementation inside the closures are pretty generic and can be copy and pasted for other use cases easily. Over time, you can build out your own library of UI components that you reuse for multiple iOS apps. The possibilities are endless!</p><p>I hope you learned a thing or two about closures and why they’re the way to go for programmatically creating UI components. If you want to test it out yourself, I put up a demo app with the closures implementation <a href="https://github.com/rckim77/ProgrammaticComponents">here</a>. I was inspired by this <a href="https://blog.bobthedeveloper.io/swift-lazy-initialization-with-closures-a9ef6f6312c">blog post</a> so check it out to learn more. Thanks for reading!</p><p><em>For more on iOS development, check out my article for Capital One’s public engineering blog titled </em><a href="https://medium.com/capital-one-developers/javascript-manipulation-on-ios-using-webkit-2b1115e7e405"><em>JavaScript Manipulation on iOS Using WebKit</em></a><em> and </em><a href="https://twitter.com/rckim77"><em>follow me</em></a><em> on Twitter.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=50d1086f8b79" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript Manipulation on iOS Using WebKit]]></title>
            <link>https://medium.com/capital-one-tech/javascript-manipulation-on-ios-using-webkit-2b1115e7e405?source=rss-71c5568dce47------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b1115e7e405</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[capital-one]]></category>
            <category><![CDATA[webkit]]></category>
            <dc:creator><![CDATA[Ray Kim]]></dc:creator>
            <pubDate>Thu, 12 Apr 2018 13:01:02 GMT</pubDate>
            <atom:updated>2018-04-24T17:07:13.278Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5xfDLydkDpaJ4QiGSubMOg.jpeg" /></figure><p>As iOS developers, there are times we want to include web content inside our iOS apps. We may want to load content from a website that pairs with a native app version, or we may want to let the user open links without having to open another browser. Prior to iOS 8, we’d have to use a <a href="https://developer.apple.com/documentation/uikit/uiwebview">UIWebView</a> which was clunky, leaked memory, and difficult to debug. After iOS 8, however, Apple deprecated UIWebView for <a href="https://developer.apple.com/documentation/webkit/wkwebview">WKWebView</a> and introduced the modern <a href="https://developer.apple.com/documentation/webkit">WebKit API</a>. The new framework dramatically improved both the performance and flexibility of adding web content into iOS apps, giving developers more control and more power. It also drastically improved communication with JavaScript natively.</p><p>In this post, I’ll show you examples of how to inject scripts into your webpages and receive data to do things like change the background of the webpage or call native functions directly from JavaScript.</p><h3>WKWebView</h3><p>First announced at <a href="https://developer.apple.com/videos/play/wwdc2014/206/">WWDC 2014</a>, WKWebView was a game-changer for rendering web content in iOS apps. It utilizes Core Animation and hardware acceleration so that webpages could scroll at 60fps. Apple developers ripped out the old JavaScript engine and replaced it with Nitro–the same engine that underlies Safari. It also includes the same built-in gestures for zooming and navigating backwards and forwards as the ones in Safari. It’s super easy to create too! Let’s start with a simple example of what a WKWebView looks like out of the box.</p><p>All you need to do to get started is create a WKWebView object, add constraints, and pass in a URLRequest to load a webpage:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8b91364f7cddcf8e819e4826b4612d63/href">https://medium.com/media/8b91364f7cddcf8e819e4826b4612d63/href</a></iframe><p>If you run this in the simulator, you should see something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/525/1*1aKQPRiLDReTL8NU-6tEIg.png" /></figure><p>Note that you can only load URLs that are secure by default (i.e., only HTTPS connections). You can add the “App Transport Security Settings” key to your Info.plist to override this for development purposes. Then, under App Transport Security Settings, add the key “Allow Arbitrary Loads” and set its value to “YES”:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/445/1*NhFromyV-nH30f71ERLUhQ.png" /></figure><p>This will bypass the HTTPS requirement so you can test using localhost or using an HTTP connection. Keep in mind, however, that this is only meant to be used for development and not for production. You should always abide by Apple’s <a href="https://developer.apple.com/library/content/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html">security standards</a> for loading web content securely over industry-standard protocols.</p><h3>WKUserContentController</h3><p>So, we’ve loaded web content in our app using just a few lines of code. What if we want to, say, modify the webpage in our app? We can instantiate a WKWebView object like before, but this time pass in a new configuration object of type <a href="https://developer.apple.com/documentation/webkit/wkwebviewconfiguration">WKWebViewConfiguration</a>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ab75040210ccd145d5f3c04a5c64e0e6/href">https://medium.com/media/ab75040210ccd145d5f3c04a5c64e0e6/href</a></iframe><p>Here, there’s a whole host of properties you can tinker with when the web view is initialized. For instance, you can control whether the page renders incrementally, which media types require touch gestures before playback, whether HTML5 videos can be displayed picture-in-picture, or how to communicate with loaded scripts. WKWebViewConfiguration has a property called userContentController that lets you pass in a <a href="https://developer.apple.com/documentation/webkit/wkusercontentcontroller">WKUserContentController</a> object. This object injects JavaScript using <a href="https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537448-adduserscript">addUserScript(_:)</a> and listens to message handlers via <a href="https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-add">add(_:name:)</a>. If you’re a web developer, this is similar to what browser plugins like <a href="https://developer.chrome.com/extensions/getstarted">Chrome extensions</a> do with loaded web content.</p><h3>User Scripts</h3><p>The <a href="https://developer.apple.com/documentation/webkit/wkuserscript">WKUserScript</a> object, when added to the userContentController, allows developers to take JavaScript and inject it into a webpage. Here’s a simple example of adding a script to change the background color of the Google web page from above:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6e23b2ec973d93b68b8ee1b050dde213/href">https://medium.com/media/6e23b2ec973d93b68b8ee1b050dde213/href</a></iframe><p>The init method takes in three parameters:</p><ol><li><em>source</em>: Pass in a string representation of your JavaScript as your source.</li><li><em>injectionTime</em>: Specify whether the JavaScript loads at document start or at document end. If you pass in <a href="https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/1537575-atdocumentstart">WKUserInjectionTime.atDocumentStart</a>, your script will run right after the document element has been created but before any of the document has been parsed. If you pass in <a href="https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/1537798-atdocumentend">WKUserInjectionTime.atDocumentEnd</a>, then your script will run after the document is finished parsing but before any subresources (e.g., images) have loaded. This corresponds with when the DOMContentLoaded event is fired.</li><li><em>forMainFrameOnly</em>: Specify whether your script runs in all frames or just in the main frame.</li></ol><p>For your source, you can simply pass in a string or, if the script is more complex, load it from a local file in Xcode. To do this, add your JavaScript file to Xcode, get the path to the file, and initialize a string with the contents of the file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b6b562f0feedc4b2bd92d418c31fbb88/href">https://medium.com/media/b6b562f0feedc4b2bd92d418c31fbb88/href</a></iframe><p>This is a brief overview of what it’s like to add a basic user script to a web view at init time. What else can you write a user script to do? Well, it can do anything a normal script on the webpage can do–modify document structure, listen to events like onload, load external resources (e.g., images, XMLHTTP requests). It can also communicate back to your app using script messages using the <a href="https://developer.apple.com/documentation/webkit/wkscriptmessagehandler">WKScriptMessageHandler</a> protocol.</p><h3>Script Messages</h3><p>While user scripts may let you inject JavaScript code into your webpage, script messages let you call native code from JavaScript. To do this, there are a few steps on the iOS side:</p><ol><li>For each handler you want to add, call <a href="https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-add">add(_:name:)</a> on your WKUserContentController object. The name parameter will be important later.</li><li>Have your view controller conform to the WKScriptMessageHandler protocol.</li><li>Implement the required function <a href="https://developer.apple.com/documentation/webkit/wkscriptmessagehandler/1396222-usercontentcontroller">userContentController(_ :didReceive:)</a>.</li></ol><p>A message handler is a listener that will fire and return data once some JavaScript event completes. For example, you can have a handler to parse JSON data fetched from a URL. By including these message handlers into your WKUserContentController object, your web view will define a new function window.webkit.messageHandlers.<em>name</em>.postMessage(<em>messageBody</em>) that can be called in all frames. Here’s a simple example of adding a message handler called “test” that, when, called in your script tag, will print “Hello, world!”:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/23e45c1d1906a1d99f593b62d274d09e/href">https://medium.com/media/23e45c1d1906a1d99f593b62d274d09e/href</a></iframe><p>Then, your JavaScript should include the following call somewhere:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a2e055e7c3f1e1e89df5b971f445ccc5/href">https://medium.com/media/a2e055e7c3f1e1e89df5b971f445ccc5/href</a></iframe><p>For the message body, you can post any JSON object you’d like and your iOS app will capture it as a <a href="https://developer.apple.com/documentation/webkit/wkscriptmessage">WKScriptMessage</a> object that <em>automatically converts the JSON object into native Swift types</em>. For instance, if you pass JSON that looks like this…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c19cd35171d0a7f600ac1014b3a8444b/href">https://medium.com/media/c19cd35171d0a7f600ac1014b3a8444b/href</a></iframe><p>…you can get the age by typecasting like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/46b219dda5ad61e3649d7e32b9e9edcc/href">https://medium.com/media/46b219dda5ad61e3649d7e32b9e9edcc/href</a></iframe><p>Super cool!</p><p>Though this will work perfectly in most cases, Apple may convert your JSON objects into native types that you might not expect. Per <a href="https://developer.apple.com/documentation/webkit/wkscriptmessage/1417901-body">Apple’s documentation</a>, “<em>Allowed types are </em><a href="https://developer.apple.com/documentation/foundation/nsnumber"><em>NSNumber</em></a><em>, </em><a href="https://developer.apple.com/documentation/foundation/nsstring"><em>NSString</em></a><em>, </em><a href="https://developer.apple.com/documentation/foundation/nsdate"><em>NSDate</em></a><em>, </em><a href="https://developer.apple.com/documentation/foundation/nsarray"><em>NSArray</em></a><em>, </em><a href="https://developer.apple.com/documentation/foundation/nsdictionary"><em>NSDictionary</em></a><em>, and </em><a href="https://developer.apple.com/documentation/foundation/nsnull"><em>NSNull</em></a><em>.” </em>This means that a Boolean type, for instance, will be converted into 0 for false, and 1 for true.</p><h3>Advanced JavaScript Usage</h3><p>Adding the window.webkit.messageHandler functions into your JavaScript code is pretty slick, right? Well we can do even better.</p><p>Let’s say that you wanted to create an iOS and Android app simultaneously where both apps loaded the same webpage and the same JavaScript code. JavaScript that includes these message handler functions wouldn’t compile if it’s loaded in anything but a WKWebView object (you can try loading it in a Chrome browser and you’ll get an error). That means you’d need specific separate JavaScript code for iOS and Android. Not very DRY, is it? Instead, what if we injected the message handler functions directly into the webpage on init? That way, we can reuse the same web pages without duplication and decouple the client from the server. Below is an example of injecting the same message handler from above, but this time injecting it via user scripts:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/631e1bf1f820d7d6e18508efd5e03e39/href">https://medium.com/media/631e1bf1f820d7d6e18508efd5e03e39/href</a></iframe><p>If all goes well, you should see Xcode’s console print “Hello, world!” You can see a working demo of this <a href="https://github.com/rckim77/WKWebViewDemoApp">here</a>.</p><p>This only scratches the surface of what you can do with user scripts and message handlers. I’ve even used this approach to create my own native callbacks from JavaScript libraries by injecting message handlers into the callback functions and relaying the data to my app. The sky’s the limit!</p><h3>Conclusion</h3><p>WebKit offers a powerful suite of tools for iOS developers to manipulate JavaScript directly inside a webview in a native app without focusing on boilerplate code. You can inject JavaScript directly in the webpage using user scripts in just a single line of code . You can also send data to your app from JavaScript by conforming to the WKScriptMessageHandler protocol, adding the name of your message handler, and implementing userContentController(_ :didReceive:).</p><p>There’s so much you can do with JavaScript inside your iOS apps–even beyond loading web content. If you want to continue exploring, I suggest reading up on Apple’s <a href="https://developer.apple.com/documentation/javascriptcore">JavaScriptCore</a> framework for running JavaScript directly inside your app (FYI this is, in part, what powers frameworks such as React Native) and the recent addition of <a href="https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller">SFSafariViewController</a> for mimicking the user experience of Safari even closer with AutoFill, Fraudulent Website Detection, and more.</p><p>I hope you learned a thing or two about WebKit–thanks for reading!</p><p><em>DISCLOSURE STATEMENT: These opinions are those of the author. Unless noted otherwise in this post, Capital One is not affiliated with, nor is it endorsed by, any of the companies mentioned. All trademarks and other intellectual property used or displayed are the ownership of their respective owners. This article is © 2018 Capital One.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b1115e7e405" width="1" height="1" alt=""><hr><p><a href="https://medium.com/capital-one-tech/javascript-manipulation-on-ios-using-webkit-2b1115e7e405">JavaScript Manipulation on iOS Using WebKit</a> was originally published in <a href="https://medium.com/capital-one-tech">Capital One Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>