<?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 Nathan Gitter on Medium]]></title>
        <description><![CDATA[Stories by Nathan Gitter on Medium]]></description>
        <link>https://medium.com/@nathangitter?source=rss-9ae0f3e98b90------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*CAe2O6cOMkysJDRdCip1mA.jpeg</url>
            <title>Stories by Nathan Gitter on Medium</title>
            <link>https://medium.com/@nathangitter?source=rss-9ae0f3e98b90------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 12:14:25 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@nathangitter/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[What I Learned Making 5 ARKit Prototypes]]></title>
            <link>https://medium.com/@nathangitter/what-i-learned-making-five-arkit-prototypes-7a30c0cd3956?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/7a30c0cd3956</guid>
            <category><![CDATA[arkit]]></category>
            <category><![CDATA[prototyping]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[augmented-reality]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Sun, 09 Sep 2018 18:24:30 GMT</pubDate>
            <atom:updated>2018-09-10T23:13:06.299Z</atom:updated>
            <content:encoded><![CDATA[<h4>Five Concepts for Designing Augmented Reality Apps</h4><p>Imagine a future where everyone is wearing “AR glasses”, and digital content isn’t limited to screens. Would that future look like this?</p><h3>Nathan Gitter on Twitter</h3><p>Imagining future AR interactions with wearables. 😎⌚️ Using #ARKit image detection to recognize images displayed on an Apple Watch, showing more information in AR. Built with #ARKit2 on #iOS12. https://t.co/2PETNbRO3Q</p><p>Maybe. It raises some questions:</p><ol><li>If we can display content next to the watch, does the watch need a screen?</li><li>Do we need a physical watch at all?</li><li>How would a user interact with the content? Voice commands? Hand gestures?</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pFwND5VvyZtC_BYpuOPgrw.png" /><figcaption>ARKit prototype of a future weather app.</figcaption></figure><h3>Prototyping the Future</h3><p>These are good questions to ask and help us solve future UX problems. By materializing an idea as a prototype, we can experience it firsthand and explore possible improvements.</p><p>I built five prototypes using image tracking in ARKit 2, and would like to share what I’ve learned. Hopefully the following concepts help you design better AR experiences.</p><h3>Concept #1: Screen Space vs World Space</h3><p>When designing phone-based AR experiences, we need to think about where we place visual elements—should they be placed “on the screen” or “in the world”?</p><p>Here’s my prototype of a potential future shopping experience, where physical packaging comes to life in AR.</p><h3>Nathan Gitter on Twitter</h3><p>Image tracking in #ARKit2. So many potential applications! https://t.co/tSrpupjIXU</p><p>The video of the rotating AirPods is placed in the world, fixed to the position of the box. It’s placed in <strong>world space</strong>. The buttons at the bottom are placed on the screen, unmoving even when the camera moves. They’re placed in <strong>screen space</strong>.</p><p>Generally, visual elements should be placed in world space when they act like real objects, and should be placed in screen space for easy reading or control.</p><p>The Measure app on iOS 12 does a great job of finding the right balance between screen space and world space. The measurements are always in screen space, rotated to match the lines, and can transition to a larger, expanded format when tapped.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*KGE9hoFvqfwI1iGJ57Ctfw.gif" /></figure><h3>Concept #2: Setup Costs</h3><p>AR has the ability to bring static media to life. In this prototype, I added videos on top of a print newspaper to make it more engaging.</p><h3>Nathan Gitter on Twitter</h3><p>Bringing newspapers to life with #ARKit. 📰📱 Animated weather maps and sports highlights are just the beginning! Built with #ARKit2 on #iOS12. https://t.co/JVth2FQCFw</p><p>The experience feels magical—straight out of Harry Potter. 🧙‍♂️</p><p>However, it suffers from the high setup costs of current AR apps. In order for a user to have this experience with a newspaper, they need to switch contexts, turning their attention from the newspaper to their phone.</p><p>On top of this context switch, they need to:</p><ol><li>Download the app.</li><li>Launch it.</li><li>Allow camera access.</li><li>Physically move to position the camera or initialize AR.</li></ol><p>These added steps dictate that AR content should be as meaningful and useful as possible. A newspaper with a special section explaining a concept in AR would be more compelling than a passive layer over the reading experience, and therefore more likely to motivate users to overcome the high setup cost.</p><h3>Concept #3: Real World Context</h3><p>AR content can be more compelling when it’s directly augmenting what the user is already experiencing.</p><p>This prototype identifies key figures in historical artwork, allowing the viewer to learn more about the paintings.</p><h3>Nathan Gitter on Twitter</h3><p>Exploring paintings in AR! 🖼📱 Look at artwork to identify key figures, and tap them to learn more. Built with #ARKit on #iOS12. #ARKit2 #AugmentedReality https://t.co/1pgSEr0iW4</p><p>By highlighting the key figures in world space, the user continues to view the painting while learning more about it. The user is already physically viewing the painting—the app enhances their current experience.</p><p>Instead of switching contexts between a museum brochure (or a museum brochure app) and the painting, AR delivers additional information seamlessly.</p><h3>Concept #4: Interaction Models</h3><p>In this prototype, the user is able to see into the past. Artwork in this public graffiti park is constantly covered up, but this app lets the user peel back layers of paint to reveal past artwork.</p><h3>Nathan Gitter on Twitter</h3><p>Time travel in AR! ⏳✈️ The artwork in this public graffiti park changes daily. With the power of #ARKit, it&#39;s possible to see into the past. Built with #ARKit2 on #iOS12. https://t.co/APa2Nj8jdC</p><p>The user can navigate forward or backward in time by tapping on the left or right sides of the screen. While great for demo purposes, without a prompt, the user experience is difficult.</p><p>Here are a few alternative interactions:</p><ol><li>Provide explicit controls in screen space similar to scrubbing through a video.</li><li>Use gestures to directly manipulate the position of the images in world space, swiping left and right between them.</li><li>Determine the image to show based on the relative camera angle, similar to a <a href="https://en.wikipedia.org/wiki/Lenticular_printing">lenticular print</a>.</li></ol><p>The best option for an AR interaction model depends a lot on the user’s context. In this case, the ground is rough and uneven, which might make it difficult to move around while looking through the camera. Direct manipulation might not make sense if the images of the artwork don’t align precisely.</p><p>Because the experience varies so much on the particular use case, it’s important to prototype early and often to determine the best interaction model. Oftentimes you won’t know what works best until you try it.</p><h3>Concept #5: The Future</h3><p>Until AR headsets are widely adopted, most AR applications will be experienced via mobile devices. While there are a lot of current constraints, it can be valuable to think about future possibilities.</p><p>Here’s the prototype from the beginning of this post, allowing watch apps to display more information in AR.</p><h3>Nathan Gitter on Twitter</h3><p>Imagining future AR interactions with wearables. 😎⌚️ Using #ARKit image detection to recognize images displayed on an Apple Watch, showing more information in AR. Built with #ARKit2 on #iOS12. https://t.co/2PETNbRO3Q</p><p>This is not currently practical—holding a phone to look at another screen doesn’t work too well.</p><p>However, exploring one step further than current technology can lead to new ideas today. What if we could use the digital crown on an Apple Watch to control some other kind of AR experience, or use haptic feedback to provide additional information about the surrounding world?</p><h3>The Power of Prototypes</h3><p>My goal with these prototypes is to clearly show others the potential of augmented reality. AR has an incredible number of use cases beyond placing virtual IKEA furniture in your living room.</p><p>Prototypes are powerful because they inspire. They should feel like a glimpse into tomorrow. They should set an aspirational goal—a vision for the future.</p><h3>Takeaways</h3><ol><li>Consider which elements live in screen space versus world space.</li><li>Make your content compelling—AR apps have additional setup costs.</li><li>Augment the user’s current real-world context.</li><li>Think through various interaction models—prototype whenever possible.</li><li>Imagine future possibilities—it will be here sooner than you think.</li></ol><h3>Additional Resources</h3><p>If you want to learn more about AR design (and specifically designing ARKit apps), I highly recommend these resources:</p><ol><li><a href="https://developer.apple.com/videos/play/wwdc2018/805/">WWDC18 Presentation “Creating Great AR Experiences”</a></li><li><a href="https://developer.apple.com/videos/play/wwdc2018/808/">WWDC18 Presentation “Prototyping for AR”</a></li><li><a href="https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/augmented-reality/">Apple’s Human Interface Guidelines</a></li></ol><p>If you enjoyed this post, please leave some claps. 👏👏👏</p><p><strong>You can clap up to 50 times</strong>, so get clicking/tapping! 😉</p><p>If you like this kind of stuff, you should follow me on Twitter. I only post high-quality tweets. <a href="https://twitter.com/nathangitter">twitter.com/nathangitter</a></p><p>Thanks to <a href="https://twitter.com/dokun24">David Okun</a> for revising drafts of this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7a30c0cd3956" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Fluid Interfaces]]></title>
            <link>https://medium.com/@nathangitter/building-fluid-interfaces-ios-swift-9732bb934bf5?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/9732bb934bf5</guid>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[interaction-design]]></category>
            <category><![CDATA[user-experience]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Sun, 05 Aug 2018 18:29:19 GMT</pubDate>
            <atom:updated>2018-08-05T18:29:19.640Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>How to create natural gestures and animations on iOS</strong></h4><p>At WWDC 2018, Apple designers presented a talk titled <a href="https://developer.apple.com/videos/play/wwdc2018/803/">“Designing Fluid Interfaces”</a>, explaining the design reasoning behind the gestural interface of iPhone X.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EZJGlfbTCPSEq7Exwjla1Q.png" /><figcaption>Apple’s WWDC18 presentation “Designing Fluid Interfaces”</figcaption></figure><p>It’s my favorite WWDC talk ever—I highly recommend it.</p><p>The talk provided some technical guidance, which is exceptional for a design presentation, but it was pseudo-code, leaving a lot of unknowns.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m_arQ47qnUvIFPNCHRxt7Q.png" /><figcaption>Some Swift-like code from the presentation.</figcaption></figure><p>If you try implement these ideas, you might notice a gap between inspiration and implementation.</p><p>My goal is to bridge this gap by providing working code examples of every major topic in the presentation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*zvcJzQnHtJRrDhvfV9XaYw.gif" /><figcaption>The eight (8) interfaces we will create. Buttons, springs, custom interactions, and more!</figcaption></figure><p>Here’s an outline of what we’ll cover:</p><ol><li>A brief summary of the “Designing Fluid Interfaces” talk.</li><li>Eight fluid interfaces, the design theory behind them, and the code to build them.</li><li>Applications for designers and developers.</li></ol><h3>What are fluid interfaces?</h3><p>A fluid interface might also be called “fast”, “smooth”, “natural”, or “magical”. It’s a frictionless experience that just feels “right”.</p><p>The WWDC presentation talks about fluid interfaces as “an extension of your mind” and “an extension of the natural world”. An interface is fluid when it behaves according to the way people think, not the way machines think.</p><h3>What makes them fluid?</h3><p>Fluid interfaces are responsive, interruptible, and redirectable. Here’s an example of the swipe-to-go-home gesture on iPhone X:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*XxdPbsgL9qeY4QXr1pztfw.gif" /><figcaption>Apps can be closed during their launch animation.</figcaption></figure><p>The interface immediately reacts to the user’s input, can be stopped at any point in the process, and can even change course midway.</p><h3>Why do we care about fluid interfaces?</h3><ol><li>Fluid interfaces improve the user’s experience, making every interaction feel quick, lightweight, and meaningful.</li><li>They give the user a feeling of control, which builds trust with your app and your brand.</li><li>They are hard to build. A fluid interface is difficult to copy and can be a competitive advantage.</li></ol><h3>The Interfaces</h3><p>For the remainder of this post, I will show you how to build eight (8) interfaces which cover all the major topics in the presentation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*989Lsw_y9JcZsJrAVyxEEQ.png" /><figcaption>Icons representing the eight (8) interfaces we will build.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*slFD9J80nOOOjm9dsn6aGQ.png" /></figure><h3>Interface #1: Calculator Button</h3><p>This is a button that mimics the behavior of buttons in the iOS calculator app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*h-Y4Y6K8uxu1mZ6NYst4MA.gif" /></figure><h4>Key Features</h4><ol><li>Highlights instantly on touch.</li><li>Can be tapped rapidly even when mid-animation.</li><li>User can touch down and drag outside of the button to cancel the tap.</li><li>User can touch down, drag outside, <em>drag back in</em>, and confirm the tap.</li></ol><h4>Design Theory</h4><p>We want buttons that feel responsive, acknowledging to the user that they are functional. In addition, we want the action to be cancellable if the user decides against their action after they touched down. This allows users to make quicker decisions since they can perform actions in parallel with thought.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ccdkb04pc02QvnfYJtum8g.png" /><figcaption>Slides from the WWDC presentation showing how gestures in parallel with thought make actions faster.</figcaption></figure><h4>Critical Code</h4><p>The first step to create this button is to use a UIControl subclass, not a UIButton subclass. A UIButton would work fine, but since we are customizing the interaction, we won’t need any of its features.</p><pre>CalculatorButton: UIControl {<br>    public var value: Int = 0 {<br>        didSet { label.text = “\(value)” }<br>    }<br>    private lazy var label: UILabel = { ... }()<br>}</pre><p>Next, we will use UIControlEvents to assign functions to the various touch interactions.</p><pre>addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter])</pre><pre>addTarget(self, action: #selector(touchUp), for: [.touchUpInside, .touchDragExit, .touchCancel])</pre><p>We group the touchDown and touchDragEnter events into a single “event” called touchDown , and we can group the touchUpInside, touchDragExit, and touchCancel events into a single event called touchUp.</p><p>(For a description of all availableUIControlEvents, check out <a href="https://developer.apple.com/documentation/uikit/uicontrolevents?language=objc">the documentation</a>.)</p><p>This gives us two functions to handle the animations.</p><pre>private var animator = UIViewPropertyAnimator()</pre><pre>@objc private func touchDown() {<br>    animator.stopAnimation(true)<br>    backgroundColor = highlightedColor<br>}</pre><pre>@objc private func touchUp() {<br>    animator = UIViewPropertyAnimator(duration: 0.5, curve: .easeOut, animations: {<br>        self.backgroundColor = self.normalColor<br>    })<br>    animator.startAnimation()<br>}</pre><p>On touchDown, we cancel the existing animation if needed, and instantly set the color to the highlighted color (in this case a light gray).</p><p>On touchUp, we create a new animator and start the animation. Using a UIViewPropertyAnimator makes it easy to cancel the highlight animation.</p><p>(Side note: This is not the exact behavior of the buttons in the iOS calculator app, which allow a touch that began in a different button to activate it if the touch was dragged inside the button. In most cases, a button like the one I created here is the intended behavior for iOS buttons.)</p><h3>Interface #2: Spring Animations</h3><p>This interface shows how a spring animation can be created by specifying a “damping” (bounciness) and “response” (speed).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*S0s0LiggTJm1U44lC4kcfg.gif" /></figure><h4>Key Features</h4><ol><li>Uses “design-friendly” parameters.</li><li>No concept of animation duration.</li><li>Easily interruptible.</li></ol><h4>Design Theory</h4><p>Springs make great animation models because of their speed and natural appearance. A spring animation starts incredibly quickly, spending most of its time gradually approaching its final state. This is perfect for creating interfaces that feel responsive—they spring to life!</p><p>A few additional reminders when designing spring animations:</p><ol><li>Springs don’t have to be springy. Using a damping value of 1 will create an animation that slowly comes to rest without any bounciness. Most animations should use a damping value of 1.</li><li>Try to avoid thinking about duration. In theory, a spring never fully comes to rest, and forcing a duration on the spring can cause it to feel unnatural. Instead, play with the damping and response values until it feels right.</li><li>Interruption is critical. Because springs spend so much of their time close to their final value, users may think the animation has completed and will try to interact with it again.</li></ol><h4>Critical Code</h4><p>In UIKit, we can create a spring animation with a UIViewPropertyAnimator and a UISpringTimingParameters object. Unfortunately, there is no initializer that just takes a damping and response. The closest we can get is the UISpringTimingParameters initializer that takes a mass, stiffness, damping, and initial velocity.</p><pre>UISpringTimingParameters(mass: CGFloat, stiffness: CGFloat, damping: CGFloat, initialVelocity: CGVector)</pre><p>We would like to create a convenience initializer that takes a damping and response, and maps it to the required mass, stiffness, and damping.</p><p>With a little bit of physics, we can derive the equations we need:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G_83X45IJ6J8Cedkvue_WA.png" /><figcaption>Solving for the spring constant and damping coefficient.</figcaption></figure><p>With this result, we can create our own UISpringTimingParameters with exactly the parameters we desire.</p><pre>extension UISpringTimingParameters {<br>    convenience init(damping: CGFloat, response: CGFloat, initialVelocity: CGVector = .zero) {<br>        let stiffness = pow(2 * .pi / response, 2)<br>        let damp = 4 * .pi * damping / response<br>        self.init(mass: 1, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity)<br>    }<br>}</pre><p>This is how we will specify spring animations for all other interfaces.</p><h4>The Physics Behind Spring Animations</h4><p>Want to go deeper on spring animations? Check out this incredible post by Christian Schnorr: <a href="https://medium.com/ios-os-x-development/demystifying-uikit-spring-animations-2bb868446773">Demystifying UIKit Spring Animations</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NPFOJlbdIyjPXLYU4nJxUQ.png" /></figure><p>After reading his post, spring animations finally clicked for me. Huge shout-out to Christian for helping me understand the math behind these animations and for teaching me how to solve second-order differential equations.</p><h3>Interface #3: Flashlight Button</h3><p>Another button, but with much different behavior. This mimics the behavior of the flashlight button on the lock screen of iPhone X.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*nrzZVlSrZ7hhrxRe_Sl_bA.gif" /></figure><h4>Key Features</h4><ol><li>Requires an intentional gesture with 3D touch.</li><li>Bounciness hints at the required gesture.</li><li>Haptic feedback confirms activation.</li></ol><h4>Design Theory</h4><p>Apple wanted to create a button that was easily and quickly accessible, but couldn’t be triggered accidentally. Requiring force pressure to activate the flashlight is a great choice, but lacks affordance and feedback.</p><p>In order to solve those problems, the button is springy and grows as the user applies force, hinting at the required gesture. In addition, there are two separate vibrations of haptic feedback: one when the required amount of force is applied, and another when the button activates as the force is reduced. These haptics mimic the behavior of a physical button.</p><h4>Critical Code</h4><p>To measure the amount of force being applied to the button, we can use the UITouch object provided in touch events.</p><pre>override func touchesMoved(_ touches: Set&lt;UITouch&gt;, with event: UIEvent?) {<br>    super.touchesMoved(touches, with: event)<br>    guard let touch = touches.first else { return }<br>    let force = touch.force / touch.maximumPossibleForce<br>    let scale = 1 + (maxWidth / minWidth - 1) * force<br>    transform = CGAffineTransform(scaleX: scale, y: scale)<br>}</pre><p>We calculate a scale transform based on the current force, so that the button grows with increasing pressure.</p><p>Since the button could be pressed but not yet activated, we need to keep track of the button’s current state.</p><pre>enum ForceState {<br>    case reset, activated, confirmed<br>}</pre><pre>private let resetForce: CGFloat = 0.4<br>private let activationForce: CGFloat = 0.5<br>private let confirmationForce: CGFloat = 0.49</pre><p>Having the confirmation force be slightly lower than the activation force prevents the user from rapidly activating and de-activating the button by quickly crossing the force threshold.</p><p>For haptic feedback, we can use UIKit’s feedback generators.</p><pre>private let activationFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)</pre><pre>private let confirmationFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)</pre><p>Finally, for the bouncy animations, we can use a UIViewPropertyAnimator with the custom UISpringTimingParameters initializers we created before.</p><pre>let params = UISpringTimingParameters(damping: 0.4, response: 0.2)<br>let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params)<br>animator.addAnimations {<br>    self.transform = CGAffineTransform(scaleX: 1, y: 1)<br>    self.backgroundColor = self.isOn ? self.onColor : self.offColor<br>}<br>animator.startAnimation()</pre><h3>Interface #4: Rubberbanding</h3><p>Rubberbanding occurs when a view resists movement. An example is when a scrolling view reaches the end of its content.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*y0jRo2TeJ9VtCZPmQxyRLw.gif" /></figure><h4>Key Features</h4><ol><li>Interface is always responsive, even when an action is invalid.</li><li>De-synced touch tracking indicates a boundary.</li><li>Amount of motion lessens further from the boundary.</li></ol><h4>Design Theory</h4><p>Rubberbanding is a great way to communicate invalid actions while still giving the user a sense of control. It softly indicates a boundary, pulling them back into a valid state.</p><h4>Critical Code</h4><p>Luckily, rubberbanding is straightforward to implement.</p><pre>offset = pow(offset, 0.7)</pre><p>By using an exponent between 0 and 1, the view’s offset is moved less the further it is away from its resting position. Use a larger exponent for less movement and a smaller exponent for more movement.</p><p>For a little more context, this code is usually implemented in a UIPanGestureRecognizer callback whenever the touch moves. The offset can be calculated with the delta between the current and original touch locations, and the offset can be applied with a translation transform.</p><pre>var offset = touchPoint.y - originalTouchPoint.y<br>offset = offset &gt; 0 ? pow(offset, 0.7) : -pow(-offset, 0.7)<br>view.transform = CGAffineTransform(translationX: 0, y: offset)</pre><p>Note: This is not how Apple performs rubberbanding with elements like scroll views. I like this method because of its simplicity, but there are more complex functions for different behaviors.</p><h3>Interface #5: Acceleration Pausing</h3><p>To view the app switcher on iPhone X, the user swipes up from the bottom of the screen and pauses midway. This interface re-creates this behavior.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*GMqctAhbjqpmWmAtsKVeDg.gif" /></figure><h4>Key Features</h4><ol><li>Pause is calculated based on the gesture’s acceleration.</li><li>Faster stopping results in a faster response.</li><li>No timers.</li></ol><h4>Design Theory</h4><p>Fluid interfaces should be fast. A delay from a timer, even if short, can make an interface feel sluggish.</p><p>This interface is particularly cool because its reaction time is based on the user’s motion. If they quickly pause, the interface quickly responds. If they slowly pause, it slowly responds.</p><h4>Critical Code</h4><p>In order to measure acceleration, we can track the most recent values of the pan gesture’s velocity.</p><pre>private var velocities = [CGFloat]()<br>private func track(velocity: CGFloat) {<br>    if velocities.count &lt; numberOfVelocities {<br>        velocities.append(velocity)<br>    } else {<br>        velocities = Array(velocities.dropFirst())<br>        velocities.append(velocity)<br>    }<br>}</pre><p>This code updates the velocities array to always have the last seven velocities, which are used to calculate the acceleration.</p><p>To determine if the acceleration is great enough, we can measure the difference between the first velocity in our array against the current velocity.</p><pre>if abs(velocity) &gt; 100 || abs(offset) &lt; 50 { return }<br>let ratio = abs(firstRecordedVelocity - velocity) / abs(firstRecordedVelocity)<br>if ratio &gt; 0.9 {<br>    pauseLabel.alpha = 1<br>    feedbackGenerator.impactOccurred()<br>    hasPaused = true<br>}</pre><p>We also check to make sure that the motion has a minimum displacement and velocity. If the gesture has lost more than 90% of its velocity, we consider it to be paused.</p><p>My implementation is not perfect. In my testing it seems to work pretty well, but there is an opportunity for a better heuristic to measure acceleration.</p><h3>Interface #6: Rewarding Momentum</h3><p>A drawer with open and closed states that has bounciness based on the velocity of the gesture.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*Wwh583M_4qLWg8Pb16mNeA.gif" /></figure><h4>Key Features</h4><ol><li>Tapping the drawer opens it without bounciness.</li><li>Flicking the drawer opens it <em>with</em> bounciness.</li><li>Interactive, interruptible, and reversible.</li></ol><h4>Design Theory</h4><p>This drawer shows the concept of rewarding momentum. When the user swipes a view with velocity, it’s much more satisfying to animate the view with bounciness. This makes the interface feel alive and fun.</p><p>When the drawer is tapped, it animates without bounciness, which feels appropriate, since a tap has no momentum in a particular direction.</p><p>When designing custom interactions, it’s important to remember that interfaces can have different animations for different interactions.</p><h4>Critical Code</h4><p>To simplify the logic of tapping versus panning, we can use a custom gesture recognizer subclass that immediately enters the began state on touch down.</p><pre>class InstantPanGestureRecognizer: UIPanGestureRecognizer {<br>    override func touchesBegan(_ touches: Set&lt;UITouch&gt;, with event: UIEvent) {<br>        super.touchesBegan(touches, with: event)<br>        self.state = .began<br>    }<br>}</pre><p>This also allows the user to tap on the drawer during its motion to pause it, similar to tapping on a scroll view that’s currently scrolling. To handle taps, we can check if the velocity is zero when the gesture ends and continue the animation.</p><pre>if yVelocity == 0 {<br>    animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)<br>}</pre><p>To handle a gesture with velocity, we first need to calculate its velocity relative to the total remaining displacement.</p><pre>let fractionRemaining = 1 - animator.fractionComplete<br>let distanceRemaining = fractionRemaining * closedTransform.ty<br>if distanceRemaining == 0 {<br>    animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)<br>    break<br>}<br>let relativeVelocity = abs(yVelocity) / distanceRemaining</pre><p>We can use this relative velocity to continue the animation with the timing parameters that include a little bit of bounciness.</p><pre>let timingParameters = UISpringTimingParameters(damping: 0.8, response: 0.3, initialVelocity: CGVector(dx: relativeVelocity, dy: relativeVelocity))</pre><pre>let newDuration = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters).duration</pre><pre>let durationFactor = CGFloat(newDuration / animator.duration)</pre><pre>animator.continueAnimation(withTimingParameters: timingParameters, durationFactor: durationFactor)</pre><p>Here we are creating a new UIViewPropertyAnimator to calculate the time the animation should take so we can provide the correct durationFactor when continuing the animation.</p><p>There are more complexities related to reversing the animation that I am not going to cover here. If you want to learn more, I wrote a full tutorial for this component: <a href="http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/">Building Better iOS App Animations</a>.</p><h3>Interface #7: FaceTime PiP</h3><p>A re-creation of the picture-in-picture UI of the iOS FaceTime app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*zHlr_QAPv7YpEF5wb6YZAQ.gif" /></figure><h4>Key Features</h4><ol><li>Lightweight, airy interaction.</li><li>Projected position is based on UIScrollView&#39;s deceleration rate.</li><li>Continuous animation that respects the gesture’s initial velocity.</li></ol><h4>Critical Code</h4><p>Our end goal is to write something like this.</p><pre>let params = UISpringTimingParameters(damping: 1, response: 0.4, initialVelocity: relativeInitialVelocity)</pre><pre>let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params)</pre><pre>animator.addAnimations {<br>    self.pipView.center = nearestCornerPosition<br>}</pre><pre>animator.startAnimation()</pre><p>We would like to create an animation with an initial velocity that matches the velocity of the pan gesture and animate the pip to the nearest corner.</p><p>First, let’s calculate the initial velocity.</p><p>To do this, we need to calculate a relative velocity based on the current velocity, current position, and target position.</p><pre>let relativeInitialVelocity = CGVector(<br>    dx: relativeVelocity(forVelocity: velocity.x, from: pipView.center.x, to: nearestCornerPosition.x),<br>    dy: relativeVelocity(forVelocity: velocity.y, from: pipView.center.y, to: nearestCornerPosition.y)<br>)</pre><pre>func relativeVelocity(forVelocity velocity: CGFloat, from currentValue: CGFloat, to targetValue: CGFloat) -&gt; CGFloat {<br>    guard currentValue - targetValue != 0 else { return 0 }<br>    return velocity / (targetValue - currentValue)<br>}</pre><p>We can split the velocity into its x and y components and determine the relative velocity for each.</p><p>Next, let’s calculate the corner for the PiP to animate to.</p><p>In order to make our interface feel natural and lightweight, we are going to project the final position of the PiP based on its current motion. If the PiP were to slide and come to a stop, where would it land?</p><pre>let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue<br>let velocity = recognizer.velocity(in: view)<br>let projectedPosition = CGPoint(<br>    x: pipView.center.x + project(initialVelocity: velocity.x, decelerationRate: decelerationRate),<br>    y: pipView.center.y + project(initialVelocity: velocity.y, decelerationRate: decelerationRate)<br>)<br>let nearestCornerPosition = nearestCorner(to: projectedPosition)</pre><p>We can use the deceleration rate of a UIScrollView to calculate this resting position. This is important because it references the user’s muscle memory for scrolling. If a user knows about how far a view scrolls, they can use that previous knowledge to intuitively guess how much force is needed to move the PiP to their desired target.</p><p>This deceleration rate is also quite generous, making the interaction feel lightweight—only a small flick is needed to send the PiP flying all the way across the screen.</p><p>We can use the projection function provided in the “Designing Fluid Interfaces” talk to calculate the final projected position.</p><pre>/// Distance traveled after decelerating to zero velocity at a constant rate.<br>func project(initialVelocity: CGFloat, decelerationRate: CGFloat) -&gt; CGFloat {<br>    return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate)<br>}</pre><p>The last piece missing is the logic to find the nearest corner based on the projected position. To do this we can loop through all corner positions and find the one with the smallest distance to the projected landing position.</p><pre>func nearestCorner(to point: CGPoint) -&gt; CGPoint {<br>    var minDistance = CGFloat.greatestFiniteMagnitude<br>    var closestPosition = CGPoint.zero<br>    for position in pipPositions {<br>        let distance = point.distance(to: position)<br>        if distance &lt; minDistance {<br>            closestPosition = position<br>            minDistance = distance<br>        }<br>    }<br>    return closestPosition<br>}</pre><p>To summarize the final implementation: We use UIScrollView&#39;s deceleration rate to project the pip’s motion to its final resting position, and calculate the relative velocity to feed it all into UISpringTimingParameters.</p><h3>Interface #8: Rotation</h3><p>Applying the concepts from the PiP interface to a rotation animation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*jL07YlwI-5skQGkc4W8OeQ.gif" /></figure><h4>Key Features</h4><ol><li>Uses projection to respect the gesture’s velocity.</li><li>Always ends in a valid orientation.</li></ol><h4>Critical Code</h4><p>The code here is very similar to the previous PiP interface. We will use the same building blocks, except swapping the nearestCorner function for a closestAngle function.</p><pre>func project(...) { ... }</pre><pre>func relativeVelocity(...) { ... }</pre><pre>func closestAngle(...) { ... }</pre><p>When it’s time to finally create the UISpringTimingParameters, we are required to use a CGVector for the initial velocity even though our rotation only has one dimension. In any case where the animated property has only one dimension, set the dx value to the desired velocity and set the dy value to zero.</p><pre>let timingParameters = UISpringTimingParameters(<br>    damping: 0.8,<br>    response: 0.4,<br>    initialVelocity: CGVector(dx: relativeInitialVelocity, dy: 0)<br>)</pre><p>Internally the animator will ignore the dy value and use the dx value to create the timing curve.</p><h3>Try it yourself!</h3><p>These interfaces are much more fun on a real device. To play with these interfaces yourself, the demo app is <a href="https://github.com/nathangitter/fluid-interfaces">available on GitHub</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7gS4SLe571r7RZvpps3X9A.png" /><figcaption>The fluid interfaces demo app, available on GitHub!</figcaption></figure><h3>Practical Applications</h3><h4>For Designers</h4><ol><li>Think about interfaces as fluid mediums of expression, not collections of static elements.</li><li>Consider animations and gestures early in the design process. Layout tools like Sketch are fantastic, but don’t offer the full expressivity of the device.</li><li>Prototype with developers. Get design-minded developers to help you prototype animations, gestures, and haptics.</li></ol><h4>For Developers</h4><ol><li>Apply the tips from these interfaces to your own custom components. Think about how they might be combined in new and interesting ways.</li><li>Educate your designers about new possibilities. Many are not aware of the full power of 3D touch, haptics, gestures, and spring animations.</li><li>Prototype with designers. Help them see their designs on a real device, and create tools to help them design more effectively.</li></ol><p>If you enjoyed this post, please leave some claps. 👏👏👏</p><p><strong>You can clap up to 50 times</strong>, so get clicking/tapping! 😉</p><p>Please share the post with your iOS designer / iOS developer friends on your social media outlet of choice.</p><p>If you like this kind of stuff, you should follow me on Twitter. I only post high-quality tweets. <a href="https://twitter.com/nathangitter">twitter.com/nathangitter</a></p><p>Thanks to <a href="https://twitter.com/dokun24">David Okun</a> for revising drafts of this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9732bb934bf5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Exploring Apps Without Jailbreaking]]></title>
            <link>https://medium.com/@nathangitter/exploring-apps-without-jailbreaking-e932904f9863?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/e932904f9863</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[user-interface]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Tue, 24 Apr 2018 14:03:18 GMT</pubDate>
            <atom:updated>2018-04-24T17:00:43.082Z</atom:updated>
            <content:encoded><![CDATA[<h4>Five simple techniques to learn how other apps are built</h4><p>The Medium iOS app is a native app with a fake navigation bar and the Product Hunt app is built with React Native.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OW-khVXV7oFfBpwdtOD_hw.png" /><figcaption>The Medium iOS app (left) and the Product Hunt iOS app (right).</figcaption></figure><p>How do I know? Unless I wrote the code myself or asked the developers who did, I can be confident based on a few simple tests—no jailbreaking required.</p><p>Want to learn how it’s done?</p><h3>Background</h3><p>In the “early days” of the web, it was easy to learn how any site was built. By viewing the source in a browser, the underlying code could be exposed for anyone to see, remix, and reuse. As the web progresses and frameworks increase the complexity of sites, this is nearly impossible now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F1PatoKhttsUn6yowfwjDA.png" /><figcaption>Using Chrome to inspect the HTML of a Medium article.</figcaption></figure><p>Apps have the same problem, but worse. Apps are compiled, which means the original source code has been translated from a human-friendly format to a computer-friendly format.</p><p>While there are tools to de-compile iOS apps, they require a jailbroken device, special tools, and coding expertise. I’m going to share tactics that don’t require any hacker skills—all you need is the app installed on your device.</p><h3>The Key Idea</h3><p>Our strategy is simple: push apps to their limits in hopes of breaking them. If we see how they break, we can infer how they work.</p><p>We are going to try to answer the following:</p><ol><li>Is the app native? If not, is it a web view? React Native? PhoneGap? Unity? Some kind of hybrid?</li><li>What UI elements are being used? Mainly out-of-the-box components or something custom? How are they used to achieve the desired effect?</li></ol><h3>The Experiments</h3><p>To gather data, we are going to perform five tests. I will explain how to perform each test, what to look for, and what to conclude from the results.</p><p>We will be testing:</p><ol><li>Button touch states 👆</li><li>Interactive navigation gestures 🔙</li><li>VoiceOver 🔊</li><li>Dynamic type 🔎</li><li>Airplane mode ✈️</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mji7eJHwKQKQBh82Tv2obw.jpeg" /></figure><h3>Experiment #1: Button Touch States 👆</h3><p>A button may seem simple. You tap on it, and something happens. However, not all buttons are created equal.</p><p>We are going to test the edge cases of button interaction—the behavior when the user doesn’t simply tap the button.</p><p>New iOS developers are often surprised by the complexity of interaction on a UIButton (the default button component on iOS). There are nine events that occur at various points in the interaction.</p><ol><li>touchDown</li><li>touchDownRepeat</li><li>touchDragInside</li><li>touchDragOutside</li><li>touchDragEnter</li><li>touchDragExit</li><li>touchUpInside</li><li>touchUpOutside</li><li>touchCancel</li></ol><p>(Learn more about UIControlEvents in the <a href="https://developer.apple.com/documentation/uikit/uicontrolevents">developer documentation</a>.)</p><p>Almost all buttons perform an action on touchUpInside (when the user releases their touch inside the bounds of the control). Most buttons have a special state when the user touches down.</p><p>The real differentiating factor is how buttons handle the touchDragExit and touchDragEnter events. How do buttons respond when the user touches down on the button, then without lifting their finger, drags outside of the button and then back in?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*o9vaFZNIOoJOyRbe9QvU6A.gif" /><figcaption>Testing a standard button in the iOS simulator.</figcaption></figure><p>Standard UIButton&#39;s have some common behaviors:</p><ol><li>The “touch area” when dragging back into the button is larger than the button’s bounds.</li><li>There is an animation on touchDragEnter and touchDragExit.</li></ol><p>However, customized native buttons often lose these default animations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*7WEjgmPpcWb1RJU_7Vk3VQ.gif" /><figcaption>A custom button with no animations.</figcaption></figure><h4>An Example</h4><p>Let’s try an example with the Medium app. If you’re reading this on the Medium iOS app, you can follow along at home!</p><p>Let’s try this fancy-looking button in the bottom right:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/458/1*LjDIPzIsPupqBavlayV06g.png" /></figure><p>If you tap on the button, then while still holding, move your finger out and back in, you’ll notice the hand icon switching between its light and dark state.</p><p>(My next post: “How I growth-hacked my way to 100k claps” 😉)</p><h4>React Native Buttons</h4><p>React Native buttons are pretty easy to spot. They often have a slow fade animation, and it’s applied to <strong>everything</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*TRzUveN7gJy-QCCo-p-pEA.gif" /><figcaption>Button animation in Facebook’s F8 app. This is a common effect in React Native apps.</figcaption></figure><p>React Native apps usually make heavy use of scroll views, which can make this button behavior difficult to test, since dragging the button also scrolls the view.</p><p>While on the topic of React Native, another big giveaway is the touch states on “cells”. Traditional table cells apply a solid background color, while React Native cells usually apply a highlight effect similar to their buttons.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*kDDB-EtlYgMR_yENeMmg4Q.gif" /><figcaption>Left: React Native cell behavior. Right: Native cell behavior.</figcaption></figure><h4>Web View Buttons</h4><p>Of the PhoneGap apps I downloaded to test, ~95% of the buttons had no touch states at all, and the remaining ~5% had a touch down state, but didn’t have any effect when dragging out or back in.</p><h4>Conclusions from button touch states</h4><p>It’s important to keep in mind that these button behaviors are easily overridable. Exhibiting a particular behavior does not imply a cause—it’s just a clue in a certain direction.</p><p>You’ll have to get a “feel” for buttons over time, but it’s one of the easiest ways to start making educated guesses about how an app is built. (This technique can also be used to determine whether an interactive element is a button or some other kind of control.)</p><h3>Experiment #2: Interactive navigation gestures 🔙</h3><p>Since iOS 7, users have been able to navigate to the previous screen by swiping the left edge of the display. This gesture is especially fun because it’s interactive, meaning it can be scrubbed back and forth.</p><p>This behavior comes for free when using a standard UINavigationController on iOS. For one reason or another, many apps forgo the standard behavior and end up with a missing, broken, or <a href="https://medium.com/@nathangitter/designing-jank-free-apps-9f66d43b9c87">janky</a> navigation transition.</p><p>Let’s try it on the Medium app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*DXaY3wngOmbDnygRsGR_5w.gif" /><figcaption>Comparing navigation transitions on the Medium app (left) and the App Store app (right).</figcaption></figure><p>Unlike a standard navigation transition, the Medium app moves the navigation bar with the rest of the screen. Normally the navigation bar stays constant and any labels cross-fade.</p><p>Additionally, the dark overlay on the previous screen is much darker in the Medium app, leading me to believe that the transition has been overridden, or more likely, is a totally custom component.</p><p>I personally think it looks really good, and understand there are major design and development benefits gained from this approach.</p><h4>React Native Navigation</h4><p>From a development perspective, navigation is more difficult in React Native. As a result, React Native apps tend to use custom navigation transitions instead of the standard “push” and “pop” of UINavigationController.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*xkAtEig66JoJISlBcl3YCw.gif" /><figcaption>A custom transition in Facebook’s F8 app.</figcaption></figure><p>Default modal presentations on iOS are not interactive, and don’t have a scaling effect on the screen that’s re-appearing.</p><p>Here’s another example of a custom transition in React Native.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*iOqkUpe_3TDIvt_JqSYo-A.gif" /><figcaption>A navigation transition in Facebook’s F8 app.</figcaption></figure><p>There’s no shadow or dark overlay, but the real giveaway is the animation timing. It’s hard to see in this gif, but after I release my touch, the animation completes much slower than normal.</p><p>Just like button touch states, this is something you can get a “feel” for over time by testing many navigation transitions.</p><h4>Conclusions from interactive navigation gestures</h4><p>This is one of my favorite tests since it can reveal more about an app than just how the navigation bar works. If the gesture breaks an app, it’s possible to learn about more than just the navigation transition.</p><p>However, just like button touch states, navigation transitions can be overridden. In practice, navigation transitions are less likely to be heavily customized since it requires significant development effort.</p><h3>Experiment #3: VoiceOver 🔊</h3><p>You want superpowers? VoiceOver will give you superpowers.</p><p>VoiceOver is Apple’s version of a screen reader. Meant for low-vision users, this accessibility option reads the user interface aloud.</p><p>VoiceOver has another effect we are more interested in: it displays a black box around the currently selected element.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7B6BZBbp-amooMt5ZOMvpA.png" /><figcaption>Voice over selecting elements in the App Store and Weather apps.</figcaption></figure><p>This allows us to break a screen into its parts. Instead of guessing how a screen is built, we can just have VoiceOver tell us! Sometimes it will even read aloud the type of element (“button”, “date picker”, etc).</p><p>If you haven’t played with VoiceOver before, it is worth learning. The basics:</p><ol><li>Drag on the screen to select elements.</li><li>Double-tap anywhere on the screen to “tap” the selected element.</li><li>Swipe left and right to quickly jump between elements.</li></ol><p>Let’s investigate the Medium app with VoiceOver.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_wvOl8sGA-2RjevOJcBzpA.png" /><figcaption>Using VoiceOver to select the title of a post in the Medium app.</figcaption></figure><p>Most of the elements act as expected. VoiceOver simply reads the content of the selection or the name of the element. However, there are a few unusual behaviors.</p><p>On the home screen, selecting the title of a post only reads half of the title. First it says, “Color Contrast Crash C”, then selecting the bottom of the title reads “Course for Interface Design”. There must be something custom about the layout of the labels that makes VoiceOver think the title is split into multiple labels, one per line. (My guess is they built a workaround for labels with custom line spacing, which usually requires overriding the attributedString property, and can lead to complications later.)</p><p>Selecting the description, we can see the power of VoiceOver to reveal hidden information. To most users, the label only says “There are an estimated 285 million…”. But VoiceOver tells us more: “There are an estimated 285 million people in the world who are visually impaired. This number includes anyone from legally blind to those”. In this case, all that data is stored in the label, but visually cut off.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F7iiah_J_N0A%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D7iiah_J_N0A&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F7iiah_J_N0A%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a1b8732a47f3ba46099cc04646dfe3f8/href">https://medium.com/media/a1b8732a47f3ba46099cc04646dfe3f8/href</a></iframe><p>If you’re lucky, you can use this to access information you otherwise shouldn’t be able to access.</p><p>Here’s another fun one. On the “bookmarks” tab, if you have no bookmarks, there is an invisible label. It says “To bookmark stories, tap on the bookmark icon anywhere and it will be added to this list.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o-X2hCfV1rWjXIRWdWOa_g.png" /><figcaption>Using VoiceOver to select an invisible label in the Medium app.</figcaption></figure><p>My guess is that a developer quickly hid this label temporarily, with the assumption that it might be shown in the future. (Or maybe I’m just being A/B tested.)</p><h4>Non-native Apps</h4><p>VoiceOver works well for web view-based apps as well. If you hear words like “link” or “heading level one”, you’re in a web view.</p><p>Additionally, text can be split in various odd ways based on styling (because of its HTML representation), and elements may not be grouped naturally.</p><p>Games (built with Unity, SpriteKit, etc) generally do not have any VoiceOver support at all.</p><h4>Conclusions from VoiceOver</h4><p>VoiceOver provides the most reliable evidence of any test. It shows the visual bounds of elements, and reads invisible attributes. It’s a treasure trove of data about any screen.</p><p>As you use VoiceOver more, you’ll learn the default phrasing for various UI elements, and start to notice when it’s different.</p><p>As with any of these tests, VoiceOver is not 100% reliable. All of the VoiceOver text and bounding boxes are configurable by the developer. Apps optimized for VoiceOver tend to display less rich information about how the app works since the developer has fixed issues that cause it to break.</p><p>(Pro tip: Setting VoiceOver as your “Accessibility Shortcut” makes it easy to toggle on and off while testing.)</p><h3>Experiment #4: Dynamic type 🔎</h3><p>Similar to VoiceOver, dynamic type is an accessibility setting for low-vision users. It modifies the text size throughout the system.</p><p>We want to use dynamic type to break layouts. And with the new “Accessibility Sizes” which are absolutely gigantic, this is easier than ever.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KmwvxTP9Q2KyLfTqwo54MQ.png" /><figcaption>The “Larger Text” settings screen with the maximum type size.</figcaption></figure><p>Dynamic type can be activated in Settings &gt; Accessibility &gt; Larger Text. This can also be added as a widget to the control center in iOS 11 for easier access.</p><p>Unfortunately the Medium app doesn’t support dynamic type, so we are going to use the App Store app instead.</p><p>I set the text size to the maximum, and was able to find one broken layout—an advertisement on the search screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IsqwosbqCtJVJADUySBb3A.png" /><figcaption>The App Store search screen with the largest type setting (left), and the default (right).</figcaption></figure><p>The clipping text “22K” is pretty great, but it doesn’t reveal too much about the layout, since the layout is adjusted for large type (seen by the elements in a stack instead of being side-by-side).</p><p>My favorite part here is the light blue “Ad” button. Instead of a nice rounded rectangle, we get a funky stretched shape.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q-v6oAigHDVBWNfBgzgmXQ.png" /><figcaption>The “Ad” button with a large type setting.</figcaption></figure><p>My guess is that this blue box is drawn as a custom path with a hard-coded radius. Usually controls don’t resize with dynamic type (see the “GET” button as an example), so there’s something custom going on here.</p><h4>Conclusions from dynamic type</h4><p>Some apps simply don’t support dynamic type. Even if they do, they might not support the larger accessibility sizes.</p><p>When it works, dynamic type can stress-test layouts. Some of this information is viewable already with VoiceOver, but it can help to verify theories. Generally apps that support dynamic type have also tested dynamic type, which reduces the chance of revealing useful information.</p><h3>Experiment #5: Airplane mode ✈️</h3><p>Another simple test is to enable Airplane mode. Airplane mode disables Wi-Fi and cellular connection, which causes network requests to immediately fail. By disabling network connections in various situations, we can see how apps break.</p><p>In the Medium app, if you load the home page, turn on Airplane mode, and select a post, the post will still load. In fact, the entire post is still readable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uKDEsrYBp0PRfIVlq8aLoA.png" /><figcaption>The Medium app with Airplane mode on. The content loads, but images do not.</figcaption></figure><p>From this, we know that the app pulls the content for entire posts when it loads the previews (and does some caching).</p><p>The App Store app lazily loads images as well. Turning on Airplane mode after loading an app page and scrolling to the bottom reveals blank spaces where the loaded images belong.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ayzVeFPBdoN9UwaPIKdSsQ.png" /><figcaption>The App Store app with Airplane mode on. Images (even on the same page) appear to lazily load.</figcaption></figure><p>Most modern apps depend heavily on network connections to download content and allow interactivity, so this will break most apps.</p><h4>React Native and Non-Native Apps</h4><p>Of the React Native apps I tested, most immediately responded to a lack of internet connection by removing all content on the screen and displaying a custom “no connection” message.</p><p>For webview-based apps, most didn’t respond. There was no indication that loading was occurring, or that it failed.</p><h4>Conclusions from airplane mode</h4><p>Unfortunately, airplane mode doesn’t give too many definitive answers on how the app is built, as most apps have some kind of fallback when no connection is available.</p><p>Want to dive deeper? You can learn a lot about other apps by observing their network traffic. Charles Proxy for iOS is a great way to get insight, but requires some knowledge of HTTP networking.</p><h3>Takeaways</h3><p>While it may seem impossible to determine how an app is built, there are some ways to make educated guesses. By studying edge cases, we can reveal the inner workings of the larger system.</p><p>Our learnings can inform the design and development of our own apps. Being aware of other approaches helps us make better decisions in the future.</p><p>In a world of closed-source apps with a limited ability to tinker, hopefully these techniques help some people discover (or rediscover) the joy of learning how things work.</p><p>Enjoyed the story? Leave some claps 👏👏👏 here on Medium and share it with your iOS design/dev friends. Want to stay up-to-date on the latest in mobile app design/dev? Follow me on Twitter: <a href="https://twitter.com/nathangitter">twitter.com/nathangitter</a></p><p>Thanks to <a href="https://twitter.com/dokun24">David Okun</a> for revising drafts of this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e932904f9863" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Designing Jank-Free Apps]]></title>
            <link>https://medium.com/@nathangitter/designing-jank-free-apps-9f66d43b9c87?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/9f66d43b9c87</guid>
            <category><![CDATA[apps]]></category>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ux]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Mon, 05 Mar 2018 14:13:04 GMT</pubDate>
            <atom:updated>2018-03-05T14:13:04.774Z</atom:updated>
            <content:encoded><![CDATA[<h4>How Designers Can Help Prevent Visual Glitches in iOS Apps</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n45GZ5y53mqR02NrPhzizw.jpeg" /></figure><p>With the release of iOS 11, there was widespread concern that Apple’s software quality was declining. Many users experienced performance issues, software bugs, and visual glitches.</p><p>Here’s an example of a visual glitch in the Messages app, caused by force-touching to “pop” a message followed immediately by tapping the back button.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*h8O2GVoN7svzsBoqOrVgvw.gif" /><figcaption>Notice the double navigation animation and lack of keyboard animation.</figcaption></figure><p>These kinds of visual errors are not new to iOS 11, mobile apps, or user interfaces. As apps grow in complexity and user expectations rise, these seemingly small bugs can have a large impact on the final product. They make apps feel janky.</p><h3>What is Jank?</h3><p>Jank refers to visual glitches that are unexpected and distracting. Usually they arise because of extra animations or missing animations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*e64xqaPYPdYE2J-A9eIhZQ.gif" /><figcaption>In the Medium app, the toolbar icons have an extra animation (growing and moving right) when the keyboard re-appears after cancelling a photo upload.</figcaption></figure><p>An app can be “janky” or exhibit “jankiness” if it seems to be behaving incorrectly. Besides visual glitches, jankiness could refer to unresponsive buttons, inconsistent loading behavior, or awkward gestures.</p><p>In this post, I am going to focus solely on visual glitches.</p><h3>Motivation</h3><p>If you are a designer, you may not feel empowered to fix app jank—it’s a code problem, right?</p><p>Designers still play a critical role. Certain user interface elements are particularly janky, especially when pushed to their limits. If designers know which elements cause jank, and more importantly, the limitations of those elements, they can create designs that are less likely to result in janky apps.</p><p>We are going to discuss some common sources of jank in iOS apps and explore ways to design apps that can prevent these issues from arising althogether.</p><h3>Navigation Transitions</h3><p>Let’s start with a common user interface element: the navigation bar.</p><p>Here is a normal, happy navigation bar:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*rIasbsuW2y7PRCW9iws8ww.gif" /><figcaption>Smooth transition when tapping the back button and when driven interactively.</figcaption></figure><p>Here is a janky, sad navigation bar:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*KGQpsuB-sjcrcaX0OmIFJQ.gif" /><figcaption>Extra animation of black region on the right side; lack of animation when the back button is pressed.</figcaption></figure><p>What’s wrong? Why is this navigation bar so <strong>janky</strong>?</p><p>In this case, the “Home” screen’s navigation bar is translucent, while the “New Screen”’s navigation bar is opaque.</p><p>The navigation bar provided by Apple is limited and volatile—only certain behaviors are supported. If a developer wants to modify the navigation bar in an unsupported way, there’s no guarantee that it will work correctly in all cases.</p><h4><strong>Navigation Transition Design Considerations</strong></h4><p>Many common navigation bar treatments are not fully supported: hiding the default 1px gray underline, hiding the back button text, adding extra views below the navigation bar, and allowing a back gesture from anywhere on the screen.</p><p>How can designers, who may not be familiar with the nuances of the UINavigationController API, design around these issues?</p><p>The main tip is to preserve design consistency across screens. If each screen has a new navigation bar style, you’re asking for trouble.</p><h3>Keyboards</h3><p>In my experience, keyboards are the most common source of jank. Both examples at the top of this post are keyboard-related.</p><p>Here’s another example. Notice how the circle animation works fine when picking images, but is incorrect when using the standard keyboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*a49DyEMw4Fy9Fp_Qq-hMdA.gif" /><figcaption>Keyboard-related jank in Snapchat.</figcaption></figure><h4>Keyboard Design Considerations</h4><p>Anytime a keyboard is involved, you should keep the screen as simple as possible. Half of the screen real estate is gone anyway, so don’t pack the remaining space full of buttons.</p><p>Be wary of animations that occur alongside the keyboard animation, and don’t assume you have full control over the keyboard. Hiding the keyboard without an animation is possible, but might cause complications later down the road.</p><p>Also consider third-party and international keyboards. Just because your design works well with the standard English iOS keyboard doesn’t mean it will work for everyone. You should assume the height of the keyboard is unknown and may change at any time.</p><h3>Multi-State Animations</h3><p>I love multi-state animations. They are incredibly fun to design and build. However, animations that have many states tend to cause jank.</p><p>Here’s an example of a multi-state animation gone wrong:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/356/1*otFffvXouZPTz4RcxfnxwA.gif" /><figcaption>App Store button with an unintended animation.</figcaption></figure><p>This button has multiple states—it normally transforms into a spinner and animates to represent a download in progress.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*v8pRsQTd160Sa2Kt0z943A.gif" /><figcaption>Even the “normal” behavior can be janky. (recorded in the TestFlight app)</figcaption></figure><p>From this, we can make an educated guess about where the spinning bug originated. Notice when the spinner first appears, but before the blue progress starts, the nearly-full gray circle is spinning in place. Due to some error in the code, the “open” button above thinks it’s in this intermediate spinning state.</p><p>The real issue here is too much state. When animations get into unintended states, they will exhibit jank.</p><h4>Multi-State Animation Design Considerations</h4><p>Multi-state animations increase complexity. If you are designing an element that animates between multiple states, make sure to consider all state transitions and edge cases. Something that seems simple on the surface might be much more complex than originally thought.</p><h3>De-Janking a Design</h3><p>Now that we know some common sources of jank in iOS apps, let’s use our new perspective to evaluate a design.</p><p>Here’s a rough and incomplete design I made a while ago. It was exploring the idea of a simple presentation app. The user could enter the “data” for each slide, and the presentation would be automatically rendered and themed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rZnFjenyBocmeaF6wPlN1w.png" /></figure><p>The main focus is the middle screen, where the user can add new items, enter text, re-arrange slides, swipe horizontally between slides, and expand a preview of the rendered slide.</p><p>Looks pretty good, right? <strong>No. This design is a one-way ticket to Jankville.</strong></p><p>Let’s count the potential sources of jank:</p><ol><li>The varying navigation bars and transitions between them.</li><li>The slide preview which always stays above the keyboard.</li><li>The interactive and state-based animation of expanding the slide preview.</li><li>The scroll position and keyboard animations when the user adds many lines of text.</li><li>The state-based animation selecting the type of text (“Title”, “Subtitle”, “Bullet”, etc.)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_IeRhr6IZJKXo10vpPpdaw.png" /></figure><p>Most importantly, these are not all independent—we would need to handle the side-effects of all possible combinations.</p><p>It’s absolutely possible to build this, but the developer would spend a sizable amount of time fixing animation glitches and state conflicts. This app will likely require an intense attention to detail, care, and ability to fix the issues that arise.</p><p>Here’s a new design I quickly mocked up:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dZSpnKJzSq7QwvmMHDiHTw.png" /></figure><p>The middle “My Presentation” screen has been expanded to three screens: the first for organizing slides, the second for organizing items, and the third for editing a single item.</p><p>This reduces possible sources of jank by leveraging default iOS elements and encapsulating potentially problematic elements. Instead of a custom expanding button animation to select the type of item, the user can select from a default picker. The keyboard is only used on the final screen, and never needs to hide since there’s only one text field.</p><p>This second design might not feel as unique, but it’s more likely to feel familiar and work as expected.</p><h3>Note About Custom Designs</h3><p>I don’t mean to discourage building custom user interfaces or pushing the boundaries of what’s possible. I love making experiences that feel fresh—it’s what makes iOS design and development fun.</p><p>But too often I seen custom designs that only make it 90% of the way to completion. Due to a variety of reasons, the last 10% falls short, and jankiness remains in otherwise well-designed apps.</p><p>If designers are aware of the most common pitfalls, they can help developers avoid them, which results in better experiences for everyone.</p><h3>Takeaways</h3><p>Designers can help prevent jank by knowing its causes and modifying designs accordingly.</p><p>Here are a few practical steps to reduce jank:</p><ol><li>Get developer input early. An experienced iOS developer can quickly estimate the complexity of building a design and inform you about potential issues.</li><li>Simplify designs! It’s easy to hide complexity by assuming transitions and animations will work themselves out. By considering all possible cases and transitions, you will naturally move towards simpler solutions.</li></ol><p>Hopefully you learned some of the reasons why some apps feel janky, and more importantly, feel empowered to solve jank through design!</p><p>Enjoyed the story? Leave some claps 👏👏👏 here on Medium and share it with your iOS design/dev friends. Want to stay up-to-date on the latest in mobile app design/dev? Follow me on Twitter here: <a href="https://twitter.com/nathangitter">https://twitter.com/nathangitter</a></p><p>Thanks to <a href="https://twitter.com/dokun24">David Okun</a> for revising drafts of this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9f66d43b9c87" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Your App Looks Better in Sketch]]></title>
            <link>https://medium.com/@nathangitter/why-your-app-looks-better-in-sketch-3a01b22c43d7?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/3a01b22c43d7</guid>
            <category><![CDATA[sketch]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Wed, 31 Jan 2018 14:45:37 GMT</pubDate>
            <atom:updated>2018-02-03T19:13:52.739Z</atom:updated>
            <content:encoded><![CDATA[<h4>Exploring rendering differences between Sketch and iOS</h4><h3>Spot The Difference</h3><p>Can you spot the differences between these two images?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y4jskGqLNFIK_XnJD2ivcw.jpeg" /></figure><p>If you look hard enough, you might notice a few subtle differences:</p><p>The image on the right:</p><ol><li>Has a larger shadow.</li><li>Has a darker gradient.</li><li>Has the word “in” on the top line of the paragraph.</li></ol><p>The image on the left is a screenshot from Sketch, and the image on the right is a reproduction on iOS. These differences arise when the graphics are rendered. They have the exact same font, line spacing, shadow radius, colors, and gradient attributes — all of the constants are identical.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*nVZjiFK-DJllaBRrep5W2Q.gif" /></figure><p>As you can see, some aspects of the original design can be lost during the conversion from the design file to real code. We’re going to explore some of these details so you can know what to watch for and how to fix them.</p><h3>Why We Care</h3><p>Design is critical to a successful mobile app. Especially on iOS, users are accustomed to apps that work well and look good.</p><p>If you’re a mobile app designer or developer, you know how important small details are to the end user experience. High-quality software can only come from people who care deeply about their craft.</p><p>There are many reasons why apps might not look as good as their original designs. We’re going to investigate one of the more subtle reasons — differences in rendering between Sketch and iOS.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MOcAlyqfmddQ0Ytpjw6ORA.jpeg" /></figure><h3>Lost in Translation</h3><p>Certain types of user interface elements have noticeable differences between Sketch and iOS. We are going to explore the following elements:</p><ol><li>Typography</li><li>Shadows</li><li>Gradients</li></ol><h3>1. Typography</h3><p>Typography can be implemented in various ways, but for this test I am going to use labels (“Text” element in Sketch, UILabel in iOS).</p><p>Let’s look at some of the differences:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1hmlpwlESTIIh7jOHL57Ug.jpeg" /></figure><p>The biggest difference in the example above is the location of line breaks. The third grouping of text starting with “This text is SF Semibold” breaks after the word “25” in the design, but after the word “points” in the app. This same problem occurs with the paragraph of text—the line breaks are inconsistent.</p><p>Another smaller difference is that the leading (line spacing) and tracking (character spacing) are slightly larger in Sketch.</p><p>It’s easier to see these differences when they are directly overlaid:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*kLWEbWg31g1H4Gw06uYPQg.gif" /></figure><p>What about other typefaces? Replacing San Francisco with Lato (a widely used free font), we get the following results:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*-HuZDeMf9cc9H2Q3aIYDkw.gif" /></figure><p>Much better!</p><p>There are still some differences in leading and tracking, but these are generally small. Be careful though—if the text needs to align with other elements like background images, these small offsets can be noticeable.</p><h4>How To Fix</h4><p>Some of these issues are related to the default iOS font: San Francisco. When iOS renders the system font, it automatically includes tracking based on the point size. This automatically-applied tracking table is available <a href="https://developer.apple.com/fonts/">on Apple’s website</a>. There is a <a href="https://github.com/kylehickinson/Sketch-SF-UI-Font-Fixer">Sketch plugin</a> called “SF Font Fixer” which reflects these values in Sketch. I highly recommend it if your design uses San Francisco.</p><p>(Side Note: Always remember to make the text box wrap tightly around text in Sketch. This can be done by selecting the text and toggling between “Fixed” and “Auto” alignment, then resetting the width of the text box. If there is any extra spacing, this can easily lead to incorrect values being entered into the layout.)</p><h3>2. Shadows</h3><p>Unlike typography which has universal layout rules, shadows are less well-defined.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5KfDKJNuPB_dTDI9XDX2hA.jpeg" /></figure><p>As we can see in the image above, shadows in iOS are larger by default. In the examples above, this makes the most difference on the top edges of the rectangles.</p><p>Shadows are tricky because the parameters between Sketch and iOS are not the same. The biggest difference is that there is no concept of “spread” on a CALayer, although this can be overcome by increasing the size of the layer that contains the shadow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0DdS1KFBq89nKNn_dWnfTg.jpeg" /></figure><p>Shadows can vary wildly in their difference between Sketch and iOS. I’ve seen some shadows with the exact same parameters look great in Sketch but be nearly invisible when running on a real device.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*6lznpdyRVwU1kS77-6qeug.gif" /></figure><h4>How To Fix</h4><p>Shadows are tricky and require manual adjustment to match the original design. Oftentimes, the shadow radius will need to be lower and the opacity will need to be higher.</p><pre>// old<br>layer.shadowColor = UIColor.black.cgColor<br>layer.shadowOpacity = 0.2<br>layer.shadowOffset = CGSize(width: 0, height: 4)<br>layer.shadowRadius = 10</pre><pre>// new<br>layer.shadowColor = UIColor.black.cgColor<br>layer.shadowOpacity = 0.3<br>layer.shadowOffset = CGSize(width: 0, height: 6)<br>layer.shadowRadius = 7</pre><p>The required changes vary based on size, color, and shape — here, we only need a few minor adjustments.</p><h3>3. Gradients</h3><p>Gradients prove to be troublesome as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gmw_KgTd_o2BNIbsmEDIXw.jpeg" /></figure><p>Of the three gradients, only the “orange” (top) and “blue” (bottom right) differ.</p><p>The orange gradient looks more horizontal in Sketch, but more vertical in iOS. As a result, the overall color of the gradient is darker in the final app than the design.</p><p>The difference is more noticeable in the blue gradient—the angle is more vertical in iOS. This gradient is defined by three colors: light blue in the bottom left corner, dark blue in the middle, and pink in the top right corner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*4D59Cblav3cAaA4OZS0ATQ.gif" /></figure><h4><strong>How To Fix</strong></h4><p>The start and ending points may need to be adjusted if the gradient is angled. Try offsetting the startPoint and endPoint of your CAGradientLayer slightly to account for these differences.</p><pre>// old<br>layer.startPoint = CGPoint(x: 0, y: 1)<br>layer.endPoint = CGPoint(x: 1, y: 0)</pre><pre>// new<br>layer.startPoint = CGPoint(x: 0.2, y: 1)<br>layer.endPoint = CGPoint(x: 0.8, y: 0)</pre><p>There’s no magic formula here*—the values need to be adjusted and iterated until the results visually match.</p><p>*Jirka Třečák posted <a href="https://medium.com/@JiriTrecak/as-for-the-gradients-there-actually-is-a-magic-formula-89055944b52a">an excellent response</a> with links explaining how the gradient rendering works. Check it out if you want to dive deep into more code!</p><h3>See For Yourself</h3><p>I built a demo app to easily see these differences on a real device. It includes the examples above, along with source code and original Sketch file so you can tweak the constants to your heart’s content.</p><p>This is a great way to increase awareness within your team—just hand them your phone and they can see for themselves. Simply touch anywhere on the screen to toggle bewteen the images (similar to the gifs above).</p><p>Get the open-source demo app here: <a href="https://github.com/nathangitter/sketch-vs-ios">https://github.com/nathangitter/sketch-vs-ios</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CkGRiP4ZvKpBEHdw_4dwdQ.jpeg" /><figcaption>The Sketch vs iOS Demo App — Try it Yourself!</figcaption></figure><h3>Takeaways</h3><p>Don’t assume that equal values imply equal results. Even if the numbers match, the visual appearance may not.</p><p>At the end of the day, there needs to be iteration after any design is implemented. Good collaboration between design and engineering is crucial for a high-quality end product.</p><p>Enjoyed the story? Leave some claps 👏👏👏 here on Medium and share it with your iOS design/dev friends. Want to stay up-to-date on the latest in mobile app design/dev? Follow me on Twitter here: <a href="https://twitter.com/nathangitter">https://twitter.com/nathangitter</a></p><p>Thanks to <a href="https://medium.com/u/b1f8a567bafc">Rick Messer</a> and <a href="https://twitter.com/dokun24">David Okun</a> for revising drafts of this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a01b22c43d7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reverse-Engineering the iPhone X Home Indicator Color]]></title>
            <link>https://medium.com/@nathangitter/reverse-engineering-the-iphone-x-home-indicator-color-a4c112f84d34?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/a4c112f84d34</guid>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[iphone]]></category>
            <category><![CDATA[apple]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Fri, 29 Dec 2017 16:28:10 GMT</pubDate>
            <atom:updated>2018-01-07T04:04:53.425Z</atom:updated>
            <content:encoded><![CDATA[<p>I noticed an unusual behavior of the iPhone X home indicator while working on <a href="https://itunes.apple.com/app/id1312458558">my most recent app</a>. The app’s background near the home indicator is purple. When the app launches, the home indicator is very light gray.</p><p>But something odd happened when I pressed the app’s “share” button, which opened a default iOS activity view (aka “share sheet”). When I hit the “cancel” button to close the activity view, the home indicator animated to a dark gray color.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*iX9nWGVdoJVmIE8dqsgMtg.gif" /><figcaption>Home indicator starts light, then a share sheet passing makes it dark.</figcaption></figure><p>Even though the background color was exactly the same, the light-colored activity view passing underneath caused the home indicator to change color. The only way to get the home indicator back to its original color was to leave the app and come back.</p><p>I had never seen this before, and it prompted my curiosity.</p><p>What determines the color of the home indicator and why it does it behave like this? The answer is surprisingly complex. Let’s take a deep dive and see what we can learn!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KdedwHiZuQFXgq1pDSKYtg.png" /></figure><h3>Home Indicator Basics</h3><p>In September 2017, Apple introduced its newest iteration of mobile phone: iPhone X. The new design replaced the iconic home button with on-screen gestures. To go home, the user simply swipes up from the bottom of the screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*0_cNERSO6g3EOaPZjfj1qA.gif" /><figcaption><a href="https://www.apple.com/iphone-x/">https://www.apple.com/iphone-x/</a></figcaption></figure><h3>The Home Indicator’s Purpose</h3><p>To create the affordance of being able to swipe up from the bottom of the screen, Apple added a small horizontal bar known as the home indicator. The home indicator is always present except for the home screen and in any apps that request it to be temporarily hidden (full-screen video, games, etc.).</p><p>The home indicator serves another purpose: protecting the bottom edge of the screen from conflicting user interface elements and gestures. Because the user needs to be able to swipe up from the bottom of the screen at any time, best practices now dictate that developers should avoid place conflicting gestures or buttons in the bottom edge of the display.</p><p>By placing a bar at the bottom, user interface elements in the same location <em>look wrong</em>—there’s a visual conflict between the bar and other elements. In this sense, the home indicator “protects” this region of the screen from designers or engineers that are unaware of the functionality of the iPhone X.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KH36CO1oCscC_2pSSF9oig.jpeg" /><figcaption>It doesn’t take a UI designer to see something is wrong here.</figcaption></figure><p>Now that we’re all on the same page, let’s get back to our original question: “What color is the home indicator?”</p><h3>Part 1 — The Beginning</h3><p>On September 13, 2017, I answered a <a href="https://stackoverflow.com/a/46199029/6658553">Stack Overflow question</a> asking how to change the color of the home indicator.</p><p>At the time, the iPhone X hadn’t been publicly released, but the latest version of Xcode included an iPhone X simulator. Running a simple test app in the simulator revealed that the home indicator’s color was based on the color of the content below it.</p><p>The new APIs for the iPhone X were released alongside this same version of Xcode, and there was no public API available to modify the color of the home indicator (which is still the case at the time of writing this post, and probably will always be the case).</p><p>This made my Stack Overflow answer simple and straightforward: it is not possible to modify the color, and you shouldn’t worry about it, since it’s out of your control and guaranteed to be visible.</p><p>Because I anticipated this to be a common question, I included some screenshots of the home indicator on top of various background colors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SMdLdrRo4KrN5xJ27B7d6A.png" /><figcaption>Some home indicator color examples from my Stack Overflow answer.</figcaption></figure><p>This was good enough for me. The home indicator maintains its visibility by sampling the color of the view below it, and picking a gray color that provides sufficient contrast.</p><h3>Part 2 — The Plot Thickens</h3><p>It turns out the home indicator’s color is not that simple. Some further observations poked holes into my “solid-color function” theory.</p><h4>Observation #1: Multiple Colors</h4><p>The first observation is that the home indicator can have multiple colors, similar to a gradient. In the following example, the left side of the screen is black, and the right is white. The home indicator adjusts for this by adopting a lighter color over the dark background, and a darker color over the light background.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F5TpLz_NSrXs9Vxn2ItcGQ.jpeg" /><figcaption>If you look close enough, you can see the transition from gray to black. (iOS simulator)</figcaption></figure><p>The home indicator can be multiple colors at the same time, and smoothly transitions between them. This smooth transition is updated in real time if any of the views behind the home indicator change.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*oBOAQuDI8ZBd5wj40IsBSw.gif" /><figcaption>Watch the home indicator’s color change as the white view passes underneath. (iOS simulator)</figcaption></figure><p>In the example above, a small white view is moving back and forth behind the home indicator. The section of the home indicator that covers the white view becomes pure black and smoothly transitions to gray.</p><p>This behavior is similar to a UIVisualEffectView, which applies a blur over existing content. The home indicator is most likely taking a sample of the nearby colors in order to get the blend effect seen in the image above.</p><p>(Besides looking good, this functionality might help prevent burn-in on the OLED display.)</p><h4>Observation #2: Same Background, Different Home Indicator Color</h4><p>As I mentioned at the beginning of this post, I noticed an unusual behavior when a share sheet passed beneath the home indicator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*iX9nWGVdoJVmIE8dqsgMtg.gif" /><figcaption>Home indicator starts light, share sheet passing makes it dark. (real iPhone X)</figcaption></figure><p>This was the most surprising observation — there is not a simple 1-to-1 mapping between background colors and home indicator colors. At this point I was determined to learn more via experimentation.</p><h3>Part 3 — The Investigation Begins</h3><p>My first order of business was to determine the formula for the home indicator color on the iOS simulator. From my previous observations, the iOS simulator’s behavior was more predictable than a real device.</p><p>I created a new iOS app as a laboratory for my future experiements. The app was simple—all I needed was an easy way to change the background color behind the home indicator. A slider and stepper control the background color’s gray value, which is displayed as a large number in the center of the screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JynC59ICgDdQfyL1KJSTPw.png" /><figcaption>The app created for testing the home indicator.</figcaption></figure><p>My goal was to determine the home indicator color for every possible gray background color. I could graph this data and see if a formula applied to it. Since there were only 256 possibilities, I took the manual approach, using macOS’s built-in “Digital Color Meter” app to get the home indicator color for each value.</p><p>I graphed the results. It was not a linear function, an exponential function, or any kind of “nice” function you might see in math class.</p><p>The graph was … odd.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0kjrbGSJYS7v-cU1CEu33w.png" /></figure><p>This was not what I was expecting.</p><p>It was a step function but with uneven steps. It had a few distinct sections: a period of (relatively) light gray, two big steps, a series of small steps, steps in the reverse direction, and a period of pure black.</p><p>The most unusual part was that the home indicator color was not always decreasing. There was a period (around rgb 170–190) where it became lighter as the background became lighter.</p><p>Why did the graph look like this? What would the same experiment have similar results on a real device? I needed to know.</p><h3>Part 4 — The Investigation Continues</h3><p>My next task was to perform the same experiment on a real device. It was immediately obvious that the results were going to be dramatically different.</p><p>To collect data on a real device, I used the same app from before. I streamed a live preview of the iPhone screen to my computer via QuickTime. This removed any discoloration from the True Tone display, as well as allowed me to use the Digital Color Meter app to easily inspect the colors.</p><p>Another factor added to the complexity on a real device—the red, green, and blue values were not always the same. On the simulator, the RGB values were identical, resulting in colors like RGB(54, 54, 54). On a real device, they were almost never the same, but were very close, resulting in colors like RGB(211, 209, 212). When recording the results, I took the average of the individual RGB values.</p><p>Here are the results, compared with the previous simulator data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MsXWzAJf5-cKO9mXphrW0Q.png" /></figure><p>The colors on a real device (red line) follow a similar trend to those on the simulator (blue line), except offset by a significant margin. The simulator home indicator is always very dark, while the device home indicator is either very light or very dark.</p><p>The graph for the real device is noisy. Overall it follows a smooth trend, but jumps up and down and looks rough. This is more than just a side-effect of my averaging, and the noise is consistent. If the experiment is repeated, the noise follows the exact same pattern.</p><p>However, the graph above doesn’t tell the whole story.</p><p>The values presented above were gathered by starting the background completely black and incrementing the RGB values one at a time (going from 0 to 255). <strong>When the values are gathered in the opposite direction, the results are different.</strong></p><p>At a certain point, the home indicator color “falls off the cliff” in its transition from light to dark or dark to light, and animates for a brief period of time, as shown in the previous gif with the purple background color. Depending on whether the background starts light or dark, the cliff occurs in a different place.</p><p>Let’s take a look at a new graph comparing the results from “going up” (black to white) and “going down” (white to black).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4LMLR49xu8lfwtjkMPwLDw.png" /></figure><p>The blue line above is the same as the previous graph’s red line. Its data points were collected left to right (0 to 255, “going up”). The orange line is the same data, but collected in the opposite direction (255 to 0, “going down”). The red line represents the points where the home indicator and the view background are the same color.</p><p>The “going up” and “going down” lines follow a similar path, but have a different noise pattern. Interestingly, they have the exact same noise pattern in the darkest range (0–80).</p><p>From this graph we can tell that the “cliffs” occur when the color of the home indicator is coming too close to the color of the background. It even appears as if the “going up” and “going down” lines are being pushed away by the red line, actively trying to resist becoming exactly the same color as the background. At a certain point, the home indicator flips to being very dark or very light.</p><p>This explains the shift in color in the app with the purple background. The purple color must be in a region between the two cliffs. Based on the previous color of the home indicator, it could either be light or dark. When the white activity view animates behind the home indicator as it is dismissed, the home indicator transitions from its light state to its dark state, and re-settles at the dark-equivelevnt value for the purple background color.</p><h3>Part 5 — The Investigation Becomes Colorful</h3><p>All the tests up to this point have used grayscale backgrounds. How would the results differ if we used colored backgrounds instead?</p><p>I repeated the same experiment, but instead of modifying the gray color, I modified the hue on an HSB color scale. I kept the saturation (S) and brightness (B) at their maximum values to get the most vibrant and distinct colors. I only tested these colors “going up”, which in this case means from hue 0 (red) to hue 255 (red) in rainbow order.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iNAnsagcyIKBN1wJ-oiP6g.png" /></figure><p>The first observation is that there are two cliffs — once when the color becomes yellow, and again when the color becomes dark blue. This occurs because of the relative “brightness” of the colors.</p><p>The next observation is the difference between the simulator and a real device. The colors follow the same general trends, but the real device’s home indicator color is noisier and can reach brighter values.</p><p>These are fascinating findings so far. Aside from testing every possible background color, there’s not much else we can discover from observation alone. Now I was curious exactly how the home indicator is implemented—is it a UIView? CALayer? UIVisualEffectView? Something else? What is making it behave in this way?</p><p>Let’s find out.</p><h3>Part 6 — We Need to Go Deeper!</h3><p>At this point I turned to my friend and <a href="https://twitter.com/ian_mcdowell">iOS expert Ian McDowell</a>. He was able to point me in the right direction—using the iPhone X Simulator and Xcode’s debugging tools to find the home indicator.</p><p>The iOS “home screen” is actually an app called SpringBoard. We can attach a debugger to the Springboard app running in the iPhone X simulator and use the “Debug View Hierarchy” option in order to inspect the views that make up the home screen, including the home indicator.</p><p>If you want to follow along at home, here is the process:</p><ol><li>Launch an iPhone X simulator.</li><li>In Xcode, select Debug &gt; Attach to Process… &gt; SpringBoard.</li><li>When SpringBoard is running, select the Debug View Heirarchy button.</li></ol><p>Deep in the view hierarchy we find an MTLumaDodgePillView which is a subview of an SBHomeGrabberView. Sounds like we found the home indicator!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4PaLH4oQd1JwNTlNJu6dhQ.png" /><figcaption>We found the home indicator!</figcaption></figure><p>The name MTLumaDodgePillView makes sense. It confirms our observed behavior of the home indicator, that its color contrasts the background based on its current brightness.</p><p>Can we go deeper yet?</p><p>SpringBoard has another cool feature: a hidden debug menu. It turns out there’s an entire section for modifying properties of the home indicator. In this debug menu, the home indicator is called a “grabber”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0PoYExN9yUep1ytIzgAWug.jpeg" /><figcaption>So many fun sliders to play with. 😍</figcaption></figure><p>This debug menu mainly contains visual and animation settings. It is most likely used to collaborate between design and engineering within Apple. Engineering builds the home indicator, provides hooks to all the internal parameters, lets designers fiddle with them until they are content, and then engineers use the settings for the final product.</p><p>Luckily for us, we can access this menu and see the results in the simulator.</p><p>I first played around with the visual appearance of the home indicator. There are sliders for the width and height in various states.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PQnQ8JxLWKjPGPtrUM7yeg.jpeg" /><figcaption>Some alternate home indicator sizes.</figcaption></figure><p>The other settings are more difficult to test, as they don’t seem to apply to the simulator. There are settings for a “luma threshold” for light and dark, as well as parameters for the animation between states. This confirms the “cliffs” where the color would dramatically animate between light and dark—there are pre-defined thresholds based on the luminosity of the background.</p><p>I was unable to determine why the simulator behaves so differently from a real device. My guess is that the simulator is using a different combination of settings, or that certain settings only take effect on real hardware.</p><p>Want to learn more about reverse engineering on iOS? Sash Zats posted <a href="http://blog.zats.io/2017/12/27/iPhone-X-home-button/">an amazing in-depth article about the home indicator</a>. Check it out if you want to dive into more code!</p><p>This marks the end of the adventure of home indicator discovery. I hope it was as educational for you as it was for me!</p><h3>High-level Takeaways</h3><ol><li>The home indicator’s color is determined by the system and cannot be modified directly.</li><li>The home indicator’s color is determined by the content underneath, and it is not always a solid color.</li><li>The home indicator on the simulator is <strong>not</strong> an accurate representation of the home indicator on a real device.</li><li>The home indicator animates to its new color(s) when the content underneath changes.</li><li>The home indicator is either in a “light” or “dark” state.</li></ol><h3>But… Why?</h3><p>Why bother investigating the home indicator if its appearance is out of our control?</p><p>There is a practical application for these learnings: If a screen in your app has a background color in the middle range where the home indicator could be either light or dark, you may prefer one style over the other. If the status bar is white, for example, it may look more visually balanced if the home indicator is white as well. Being aware of the home indicator’s nuanced behavior can help ensure that it doesn’t accidentally animate between light and dark when it could be distracting to the user.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R5Bz9NKj3ON0S1GwONcOxA.jpeg" /><figcaption>Prefer one over the other?</figcaption></figure><p>In a previous example, the white share sheet animating behind the home indicator was enough to change the home indicator’s style.</p><p>If I wanted to prevent this, I could pin a view behind the home indicator between the safe area and the bottom edge of the display. When the share sheet is dismissed, I could give it a darker background color (maybe black with 40% alpha) and add a fade animation so it’s less noticeable.</p><p>This same tactic could be used to set the color of the home indicator—forcing it over one of the “cliffs”. In the vast majority of cases, the home indicator should be left alone to do what it wants. Most iPhone X users have probably already forgotten it’s even there.</p><h3>The Real Lesson</h3><p>Hopefully this brief investigation into the color of the home indicator helps us appreciate the complexity of simple design. “It’s just a black/white bar!” is far from the truth. The amount of care and attention to detail that went into the home indicator is worth appreciating.</p><p>Taking something simple, investigating its internal complexities, and pondering its design helps us learn about the creative process. By combining design and engineering, we can make better products that are simple and delightful to use.</p><p>Enjoyed the story? Leave some claps 👏 here on Medium and share it with your iOS design/dev friends. Want to stay up-to-date on the latest in mobile app design/dev? Follow me on Twitter here: <a href="https://twitter.com/nathangitter">https://twitter.com/nathangitter</a></p><p>Thanks to <a href="https://twitter.com/ian_mcdowell">Ian McDowell</a> and <a href="https://twitter.com/dokun24">David Okun</a> for helping revise previous drafts of this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a4c112f84d34" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Local Reasoning in Swift]]></title>
            <link>https://medium.com/@nathangitter/local-reasoning-in-swift-6782e459d?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/6782e459d</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[swift-programming]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Sat, 25 Nov 2017 16:20:53 GMT</pubDate>
            <atom:updated>2018-01-17T01:12:44.421Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DRj_JULmDCGHQ9ObnpLzXQ.png" /><figcaption>It’s all about keeping related code together.</figcaption></figure><p>Swift is an incredibly expressive and powerful programming language. We’re going to explore some of Swift’s language features that can make your code easier to read.</p><h3><strong>Better Buttons</strong></h3><p>Let’s take a look at a common iOS pattern that prints a message when a button is pressed.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/df53616301c3fc54be1cd21f56fb6b5a/href">https://medium.com/media/df53616301c3fc54be1cd21f56fb6b5a/href</a></iframe><p>If you’ve done any iOS tutorial, it’s very likely you’ve seen this before. The selector pattern is used throughout UIKit, and it’s not necessarily bad practice. However, we can do better.</p><p>The issue arises when there is a lot of code between the selector declaration and the button’s action. If a developer is reading this for the first time, it’s quite common for them to lose track of what your functions do at a high level.</p><p>The two lines of code are loosely coupled. They require the reader to jump between them to fully understand the button.</p><p>Let’s try a new approach, using a closure to assign an action to the button.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/da447a086cac61a0992eb2363ff04f86/href">https://medium.com/media/da447a086cac61a0992eb2363ff04f86/href</a></iframe><p>Don’t worry — this isn’t possible with UIKit out of the box, but it’s easy to make a UIButton work like this.</p><p>In addition to containing the first snippet in one function, we’ve used a closure to more clearly communicate what happens when a button is tapped. This makes it easier to comprehend what this button does.</p><p>Implementing a closure for your button-tapped action is quite straightforward. Create a custom UIButton subclass with an action property, which is a closure. We hook up the button’s selectors internally, and execute the closure in the target function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9e78457ba2d989975d594127cc13e014/href">https://medium.com/media/9e78457ba2d989975d594127cc13e014/href</a></iframe><p>This solution can expand to include closures for a UIButton&#39;s other actions as well, such as touchDown, touchDragExit, and more.</p><h3>Local Reasoning</h3><p>Keeping relevant information together improves local reasoning. Local reasoning is the idea that the reader can make sense of the code directly in front of them, without going on a journey discovering how the code works.</p><p>At WWDC 2016, <a href="https://developer.apple.com/videos/play/wwdc2016/419/">Apple engineers gave a great talk</a> titled “Protocol and Value Oriented Programming in UIKit Apps”, where they discussed the concept of local reasoning. Their presentation covered protocols and how to leverage their benefits over traditional inheritance-based solutions.</p><p>It’s hard to call a code organization strategy invalid, as they often vary based on personal preference. However, I am proposing that the best form of code organization is the one that allows the reader to accurately reason about the code while minimizing the distance traveled through the codebase.</p><h3>Lazy Closures</h3><p>Let’s take a look at another common Swift design pattern that focuses on improving local reasoning.</p><p>A common practice is to perform setup work in the viewDidLoad function on a UIViewController subclass.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7e6b45d30610ab5dfeec090386108470/href">https://medium.com/media/7e6b45d30610ab5dfeec090386108470/href</a></iframe><p>Like the previous button example, this works just fine.</p><p>Notice that we’re split between a stored property declaration and an overridden function. This is the issue we want to focus on — finding all the code related to the view often requires a search.</p><p>Instead, we can use a closure to initialize the view.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43b6a629c3db8cb47e71b5480351e2d9/href">https://medium.com/media/43b6a629c3db8cb47e71b5480351e2d9/href</a></iframe><p>The closure is marked as lazy so self can be accessed within the closure. This allows the properties to reference any constants declared on self and set target actions for any controls.</p><p>This strategy works with storyboard outlets as well. Instead of using a closure, use the didSet property observer which is called when the outlet is set by the storyboard.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/97af160b85f49bfabf9c8d7d35a736b6/href">https://medium.com/media/97af160b85f49bfabf9c8d7d35a736b6/href</a></iframe><p>This is especially useful for view properties that cannot be set in the storyboard like corner radii, gradients, etc. It localizes our changes to the view itself.</p><p>Instead of spreading the code throughout our file, we can keep it together in one place.</p><h3>Protocol Conformance Extensions</h3><p>Protocols are used throughout the Swift Standard Library and Cocoa Touch API’s. They give a compile-time guarantee that an object has certain properties or functions.</p><p>Here is another common pattern, implementing a collection view’s delegate and data source in a view controller.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/91d06e0dfc16993917e7157bca0e163d/href">https://medium.com/media/91d06e0dfc16993917e7157bca0e163d/href</a></iframe><p>Once again, this works. However, delegate functions are often quite long, and tend to bloat the size of the controlling object implementing them. It’s hard to tell when a property or function is fulfilling protocol requirements, and which protocol it belongs to.</p><p>Instead, let’s declare protocol conformance in an extension to better group related code together.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/196097d84114a028112d838663f823e6/href">https://medium.com/media/196097d84114a028112d838663f823e6/href</a></iframe><p>This not only groups the code for each protocol, but also displays the name of the protocol directly above the implementations. At the type declaration, this decreases cognitive load, since the conformances no longer need to be written after the type name.</p><p>Protocol conformances that are not needed to understand what an object does (like CustomStringConvertible) can be placed after the type’s definition, where more important details about the type need to be understood first.</p><p>The implementation of the Swift Standard Library makes heavy use of protocol conformance in extensions since it’s easier to determine which functions belong to which protocol. Take a look at the <a href="https://github.com/apple/swift/blob/dac3264477d8513fa7e2b181f3b7da0f26a38bba/stdlib/public/core/Optional.swift#L270">open-source repo on GitHub</a> and see for yourself.</p><h3>Takeaways</h3><p>We looked at three common patterns in Swift code and alternatives that leverage Swift’s features to improve local reasoning.</p><p>When writing code, it’s important to think from the perspective of the person reading it. This includes coworkers, teammates, and even your future self. Good local reasoning allows others to understand and modify code more easily, improving its maintainability and flexibility.</p><p>While many of these techniques could be considered “code style”, they serve a specific purpose to improve the quality of the code. There are some parallels to user experience design. In this case, the user interface is the text that comprises the code, and the user is the developer. If we take a user-centric approach to writing code, we can make it easier to understand and modify in the future.</p><p>Do you have any other places where you could give local reasoning a try in Swift? Let me know in the responses below!</p><p>Enjoyed this post? Give some claps for this story here on Medium and follow me on Twitter! <a href="https://twitter.com/nathangitter">https://twitter.com/nathangitter</a></p><p>Also thanks to @dokun24 for giving feedback on a draft of this post. You can follow him on Twitter here: <a href="https://twitter.com/dokun24">https://twitter.com/dokun24</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6782e459d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Better iOS App Animations]]></title>
            <link>https://medium.com/swiftkickmobile/building-better-ios-app-animations-swift-uiviewpropertyanimator-ca05728b1fa4?source=rss-9ae0f3e98b90------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca05728b1fa4</guid>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[ux]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Nathan Gitter]]></dc:creator>
            <pubDate>Mon, 11 Sep 2017 16:40:55 GMT</pubDate>
            <atom:updated>2018-07-09T17:12:46.734Z</atom:updated>
            <content:encoded><![CDATA[<h4>Creating a popup menu with UIViewPropertyAnimator</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*kRluHyEw7GG4Usj4.gif" /><figcaption>The final animation we are going to build.</figcaption></figure><p>Animations are key to a quality user experience. They serve a wide variety of purposes, including directing user attention and connecting user actions to results on screen.</p><p>Animations make the experience of your app unique — they can enable a level of responsiveness and interactivity not possible in other mediums. To build better animations, they need to convey an improved sense of direct connection between user interaction and visual changes. One way to accomplish this is to make animations fully interactive.</p><h3>Why Create Interactive Animations?</h3><p>Interactive animations have been around since the introduction of the iPhone. The world’s first look at the original iPhone was the classic “slide to unlock” screen, where the user directly moved the slider to unlock the device. This interactive animation was immediately intuitive for those who had never used a multi-touch device before.</p><p>Interactive animations give the user more control over the user interface. Direct manipulation is a natural interaction model, especially on mobile devices. It connects their actions to on-screen animations and gives them full control over the completion or cancellation of their actions.</p><p>They also look great. Users often associate how an app looks with how well it works, so if it looks good, they are more likely to forgive other shortcomings.</p><p>In this tutorial, we will be building an interactive popup animation in Swift with UIViewPropertyAnimator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZfG62dMYpzp1QnpA1lGmnw.png" /></figure><h3>Introduction to UIViewPropertyAnimator</h3><p>UIViewPropertyAnimator was added to UIKit in iOS 10, and improved slightly in iOS 11. It provides a UIView-level object-oriented API to create animations.</p><p>Here is a simple example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*e97QYCCvMS4eljuW.gif" /></figure><p>Using traditional UIView animations, you might write something like this:</p><pre>UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseOut], animations: {<br>    self.myView.transform = CGAffineTransform(translationX: 50, y: 0)<br>    self.myView.alpha = 0.5<br>}, completion: nil)</pre><p>Using the new UIViewPropertyAnimator, you can write this instead:</p><pre>let animator = UIViewPropertyAnimator(duration: 1, curve: .easeOut, animations: {<br>    self.myView.transform = CGAffineTransform(translationX: 50, y: 0)<br>    self.myView.alpha = 0.5 })<br>animator.startAnimations()</pre><p>The code is very similar. With UIViewPropertyAnimator, you first create an animator object and then call startAnimation() instead of calling a static method on the UIView class.</p><p>UIViewPropertyAnimator becomes more useful as the animation increases in complexity. Let’s take a look at a more complex animation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*zuOUD4VBS9f9ypRY.gif" /><figcaption>Showing the animation is srubbable.</figcaption></figure><p>This animation begins when the view is panned, can be scrubbed in either direction, and animates to its final position once the pan is finished.</p><p>Before looking at the code, it’s important to understand the state machine backing UIViewPropertyAnimator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/894/0*DNfgAqFoHtgPIHnB.png" /><figcaption>State diagram for UIViewPropertyAnimator.</figcaption></figure><p>An animator can be in one of three possible states: inactive, active, and stopped. An animator is initialized in an inactive state but moves to the active state when started or paused. When the animation is completed, it moves back to the inactive state. If an animation has been started, and is paused, it remains in the active state and does not undergo a state transition.</p><p>Let’s see how to use a UIPanGestureRecognizer alongside a UIViewPropertyAnimator to create the animation above.</p><pre>var animator = UIViewPropertyAnimator()</pre><pre>private func handlePan(recognizer: UIPanGestureRecognizer) {<br>    switch recognizer.state {<br>    case .began:<br>        animator = UIViewPropertyAnimator(duration: 3, curve: .easeOut, animations: {<br>            myView.transform = CGAffineTransform(translationX: 275, y: 0) myView.alpha = 0 })<br>        animator.startAnimation()<br>        animator.pauseAnimation()<br>    case .changed:<br>        animator.fractionComplete = recognizer.translation(in: myView).x / 275<br>    case .ended:<br>        animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)<br>    default:<br>        ()<br>    }<br>}</pre><p>Note that pauseAnimation() is called immediately after startAnimation(). Because our animation begins on a pan gesture, the user is most likely to scrub the animation first before releasing their tap. When the animation is paused, set the fractionComplete property to move the view along with the user’s touch.</p><p>If we tried to do this with standard UIView animations, we would need a lot more logic than what is listed in the example above. UIView animations don’t provide an easy way to directly control the completion percentage of the animation, or allow us to easily pause and continue the animation to completion.</p><h3>Let’s Build a Popup Menu!</h3><p>We are going to build a fully interactive, interruptible, scrubbable, and reversible popup menu in 10 steps. (If you prefer to work backward from the final code instead, a link to the final repository is available at the end of this post.)</p><p>For simplicity, all views will be created and modified in code (not in storyboard, although this code would work just as well with views created in the storyboard). Also, all code will be placed in the ViewController.swift file.</p><h3>Step #1: Tap to open and close.</h3><p>First, let’s make our popup view animate between its open and closed state. No fancy tricks here, just the basics of UIViewPropertyAnimator we learned previously.</p><pre>private enum State {<br>    case closed<br>    case open<br>}</pre><pre>extension State {<br>    var opposite: State {<br>        switch self {<br>        case .open:<br>            return .closed<br>        case .closed:<br>            return .open<br>        }<br>    }<br>}</pre><pre>class ViewController: UIViewController {</pre><pre>    private lazy var popupView: UIView = {<br>        let view = UIView()<br>        view.backgroundColor = .gray<br>        return view<br>    }()</pre><pre>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        layout()<br>        popupView.addGestureRecognizer(tapRecognizer)<br>    }</pre><pre>    private var bottomConstraint = NSLayoutConstraint()</pre><pre>    private func layout() {<br>        popupView.translatesAutoresizingMaskIntoConstraints = false<br>        view.addSubview(popupView)<br>        popupView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true<br>        popupView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true<br>        bottomConstraint = popupView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 440) bottomConstraint.isActive = true<br>        popupView.heightAnchor.constraint(equalToConstant: 500).isActive = true<br>    }</pre><pre>    private var currentState: State = .closed</pre><pre>    private lazy var tapRecognizer: UITapGestureRecognizer = {<br>        let recognizer = UITapGestureRecognizer()<br>        recognizer.addTarget(self, action: #selector(popupViewTapped(recognizer:)))<br>        return recognizer<br>    }()</pre><pre>    @objc private func popupViewTapped(recognizer: UITapGestureRecognizer) {<br>        let state = currentState.opposite<br>        let transitionAnimator = UIViewPropertyAnimator(duration: 1, dampingRatio: 1, animations: {<br>            switch state {<br>            case .open:<br>                self.bottomConstraint.constant = 0<br>            case .closed:<br>                self.bottomConstraint.constant = 440<br>            }<br>            self.view.layoutIfNeeded()<br>        })<br>        transitionAnimator.addCompletion { position in<br>            switch position {<br>            case .start:<br>                self.currentState = state.opposite<br>            case .end:<br>                self.currentState = state<br>            case .current:<br>                ()<br>            }<br>            switch self.currentState {<br>            case .open:<br>                self.bottomConstraint.constant = 0<br>            case .closed:<br>                self.bottomConstraint.constant = 440<br>            }<br>        }<br>        transitionAnimator.startAnimation()<br>    }<br>}</pre><p>The relevant animation code is in the popupViewTapped function, which is called when the view is tapped. We simply create an animator, set its animations to modify the value of a constraint, and start the animator.</p><p>We introduce a State enum to indicate whether the popup is open or closed. It also has a computed opposite property, which returns the opposite of the current state. We could have implemented this with a boolean flag instead, but this is easier to reason about, especially once our animation code gets more complex.</p><p>One thing to point out — we are manually updating the value of the constraint when the animation is complete. This should be handled automatically by the animator, but explicitly setting them fixes some edge-case bugs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/0*R5WtUSr-zLrLTw18.gif" /><figcaption>Tap to open and close.</figcaption></figure><h3>Step #2: Add a pan gesture.</h3><p>To make our animation interactive, we are going to introduce a second gesture recognizer, a pan gesture recognizer. This will allow the user to start and interrupt the animation by swiping on the popup view.</p><pre>@objc private func popupViewPanned(recognizer: UIPanGestureRecognizer) {<br>    switch recognizer.state {<br>    case .began:<br>        animateTransitionIfNeeded(to: currentState.opposite, duration: 1.5)<br>        transitionAnimator.pauseAnimation()<br>    case .changed:<br>        let translation = recognizer.translation(in: popupView)<br>        var fraction = -translation.y / popupOffset<br>        if currentState == .open { fraction *= -1 }<br>        transitionAnimator.fractionComplete = fraction<br>    case .ended:<br>        transitionAnimator.continueAnimation(withTimingParameters: nil, durationFactor: 0)<br>    default:<br>        ()<br>    }<br>}</pre><p>This code is very similar to the previous example, except that the animation can be interrupted. We have refactored our animation code into a function named animateTransitionIfNeeded, which runs all of the code that was previously inside our popupViewTapped function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/592/0*nwQVn36wkxTZwOC_.gif" /><figcaption>The view can be dragged, but still has some issues.</figcaption></figure><h3>Step #3: Record the animation progress to fix the interruption offset.</h3><p>One problem: when the animation is interrupted, it is offset from the user’s touch. This is due to the pan handler not considering the current progress of the animation. To fix this, we need to record the fractionComplete of the animator, and use this as our baseline when calculating a pan offset.</p><p>We will need a property to store the current progress of the animation:</p><pre>private var animationProgress: CGFloat = 0</pre><p>When the pan gesture is in its began state, we record the current progress of the animation:</p><pre>animationProgress = transitionAnimator.fractionComplete</pre><p>In the pan gesture’s changed state, we add the animation progress to the calculated fraction:</p><pre>transitionAnimator.fractionComplete = fraction + animationProgress</pre><p>Now the pan gesture works as expected, and tracks the users finger more naturally.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*9NI_2gw8VWBmzM6p.gif" /><figcaption>Interrupting the animation works more like users expect.</figcaption></figure><h3>Step #4: Introduce a custom instant pan gesture.</h3><p>The interruption behavior works, but is awkward. In order for the pan to be recognized, the user must tap on the screen and then move their finger in any direction. We would prefer the behavior to act like a scroll view, which allows the user to “catch” the view with only a touch down. Currently, the tap gesture and pan gesture are only fired on touch up and touches moved, respectively. In order to fire an event on touch down, we can create our own custom gesture recognizer.</p><pre>class InstantPanGestureRecognizer: UIPanGestureRecognizer {<br>    override func touchesBegan(_ touches: Set&lt;UITouch&gt;, with event: UIEvent) {<br>        if (self.state == UIGestureRecognizerState.began) { return }<br>        super.touchesBegan(touches, with: event)<br>        self.state = UIGestureRecognizerState.began<br>    }<br>}</pre><p>This pan gesture subclass enters the began state on touch down. It allows us to replace both of our previous gesture recognizers. The “tap” is now an “instant pan” that ends right after it begins. By using this custom gesture recognizer, we can improve the behavior of our previous tap/pan solution as well as simplify our logic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/580/0*DZ7B8t-Qm0JdM5eh.gif" /><figcaption>Interruption occurs immediately, similar to a scroll view.</figcaption></figure><p>Note: In order to subclass a UIGestureRecognizer, you’ll need to include this import at the top of the file:</p><pre>import UIKit.UIGestureRecognizerSubclass</pre><h3>Step #5: Use the pan velocity to reverse animations.</h3><p>One remaining problem is that the popup doesn’t respect which way the view is “thrown”. If we tap on the closed popup, catch it mid-animation, and swipe back down, it will continue to animate open.</p><p>To solve this, we can conditionally reverse the animator. This will be based on a few factors: the current state of our popup, whether the animator is currently reversed, and the velocity of the pan gesture.</p><p>The ended case of the pan gesture handler now looks like this:</p><pre>let yVelocity = recognizer.velocity(in: popupView).y<br>let shouldClose = yVelocity &gt; 0</pre><pre>if yVelocity == 0 {<br>    transitionAnimator.continueAnimation(withTimingParameters: nil, durationFactor: 0)<br>    break<br>}</pre><pre>switch currentState {<br>case .open:<br>    if !shouldClose &amp;&amp; !transitionAnimator.isReversed {<br>        transitionAnimator.isReversed = !transitionAnimator.isReversed<br>    }<br>    if shouldClose &amp;&amp; transitionAnimator.isReversed {<br>        transitionAnimator.isReversed = !transitionAnimator.isReversed<br>    }<br>case .closed:<br>    if shouldClose &amp;&amp; !transitionAnimator.isReversed {<br>        transitionAnimator.isReversed = !transitionAnimator.isReversed<br>    }<br>    if !shouldClose &amp;&amp; transitionAnimator.isReversed {<br>        transitionAnimator.isReversed = !transitionAnimator.isReversed<br>    }<br>}</pre><pre>transitionAnimator.continueAnimation(withTimingParameters: nil, durationFactor: 0)</pre><p>This logic may seem complex at first, but it can be derived by considering all the possible cases.</p><p>In the changed case of the pan gesture handler, we need to respect the isReversed property of the animator:</p><pre>let translation = recognizer.translation(in: popupView)<br>var fraction = -translation.y / popupOffset<br>if currentState == .open { fraction *= -1 }<br>if transitionAnimator.isReversed { fraction *= -1 }<br>transitionAnimator.fractionComplete = fraction + animationProgress</pre><p>Now our animation can be reversed! If the user wants to close the popup mid-animation, it’s easy and intuitive for them to do so.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/0*-kDhhwGiiRR0jopv.gif" /><figcaption>The animation is reversible.</figcaption></figure><h3>Step #6: Animate the corner radius.</h3><p>In iOS 11, a CALayer&#39;s corner radius is animatable without the need for a CABasicAnimation. This means we can update a view’s corner radius in an animation block, and it will just work!</p><pre>self.popupView.layer.cornerRadius = 20</pre><p>We can also specify which corners to round. In this case, we only want the top left and top right corners to be rounded.</p><pre>view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]</pre><p>Now the top two corners are animated alongside our original animation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*JmhnwIrTzpPmCwQh.gif" /><figcaption>The top left and right corners animate alongside the other animations.</figcaption></figure><h3>Step #7: Make it prettier!</h3><p>Our gray popup view works well, but could use some visual improvements. Let’s add a background image, an overlay view, a title label, a subtle shadow, and some sample reviews.</p><p>This tutorial is not going to cover the implementation of these additional views. If you would like to see how they are created, check out the full source code at the bottom of the post.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*XSt6sNt6dE86QWOQ.gif" /><figcaption>Now our animation looks more like it would in a real app.</figcaption></figure><p>Much nicer. 😎</p><h3>Step #8: Animate the label.</h3><p>The “Reviews” label looks great when the popup is closed, but when opened, it fails to stand out from the rest of the content. We would like to give the label a larger font size and darker color when the popup is open. This label transition needs to be animated since our popup is fully interactive and scrubbable.</p><p>There is no built-in way to animate a label’s color or font style. Our solution is a simple workaround: cross-fade the labels.</p><p>To smooth the animation, we need to animate the scale and translation of each label so they overlap perfectly during the entire length of their animation.</p><p>Inside of our animation block, we modify the label’s alpha and transform:</p><pre>switch state {<br>case .open:<br>    // other animations here ...<br>    self.closedTitleLabel.transform = CGAffineTransform(scaleX: 1.6, y: 1.6).concatenating(CGAffineTransform(translationX: 0, y: 15))<br>    self.openTitleLabel.transform = .identity<br>    self.openTitleLabel.alpha = 1<br>    self.closedTitleLabel.alpha = 0<br>case .closed:<br>    // other animations here ...<br>    self.closedTitleLabel.transform = .identity<br>    self.openTitleLabel.transform = CGAffineTransform(scaleX: 0.65, y: 0.65).concatenating(CGAffineTransform(translationX: 0, y: -15))<br>    self.openTitleLabel.alpha = 0<br>    self.closedTitleLabel.alpha = 1<br>}</pre><p>Now the labels appear as if they are morphing into each other. With the correct alignment, it looks like there is only a single label.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*YQ9pHhPL38z-eeiq.gif" /><figcaption>The “Reviews” label smoothly transitions between states.</figcaption></figure><h3>Step #9: Refactor for multiple animators.</h3><p>The label animation works well, but the timing could be improved to smooth the transition further. In order to modify the timing curve of the label animations, we need additional animators. A UIViewPropertyAnimator can only have one timing curve, so in order to use multiple timing curves, we need to coordinate multiple animators.</p><p>We need to refactor our code a bit to support any number of animators. To do this we will create an array of animators.</p><pre>private var runningAnimators = [UIViewPropertyAnimator]()</pre><p>Whenever we create a new animator, we add it to the array of running animators.</p><pre>runningAnimators.append(transitionAnimator)</pre><p>Whenever an animation finishes, we will remove it from the array. To make the rest of the code work with multiple animators, anything applied to the transitionAnimator is applied to the entire array.</p><h3>Step #10: Add new animators for the label alpha.</h3><p>With our new infrastructure, we can create two new animators: one to animate the new label in, and another to animate the old label out. The benefit of using multiple animators is that each can have their own timing curve.</p><pre>let inTitleAnimator = UIViewPropertyAnimator(duration: duration, curve: .easeIn, animations: {<br>    switch state {<br>    case .open:<br>        self.openTitleLabel.alpha = 1<br>    case .closed:<br>        self.closedTitleLabel.alpha = 1<br>    }<br>})<br>inTitleAnimator.scrubsLinearly = false</pre><p>We set the animator’s scrubsLinearly property to false so that the fractionComplete of the animation gets mapped to the ease-in timing curve, instead of a linear timing curve. Generally animations that follow the user’s finger should follow a linear timing curve, which is why this property is true by default.</p><p>(Note: scrubsLinearly is only available on iOS 11+)</p><p>The difference is subtle but will allow the animation to be customized further in the future. Getting this transition exactly right is important when the user has full control over the animation and can scrub it to any point.</p><p>Here is our final animation! The user can start the animation with a tap or swipe, can interrupt the animation, and can reverse the animation. Pretty cool for a relatively small amount of code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*VoiNuRUEZjgjR5c5.gif" /><figcaption>The label animation is subtly different, and now everything is complete!</figcaption></figure><h3>When should I use UIViewPropertyAnimator?</h3><p>With so many animation API’s available in iOS, when is it best to use UIViewPropertyAnimator?</p><p>The differentiator is interruptibility. If you want to “catch” a view mid-flight, scrub it, and continue or reverse the animation, UIViewPropertyAnimator is the best option. Some other animation tactics are interruptible, such as animating views alongside a scroll view’s contentOffset property, but these animations are restricted to the timing curve of the scroll view.</p><p>Sometimes it doesn’t make sense for an animation to be interruptible. Only make an animation interruptible when the user can tap on the view during its animation. The animation should have a long enough duration that the user could react quickly enough to tap it, and the animated view should have a large tap target. The popup animation above is a perfect example of meeting these requirements.</p><h3>Conclusion</h3><p>Hopefully, you learned something new about interactive animations! The full source code for the popup animation is linked below.</p><p>When designing and building apps in the future, consider how interactive animations can improve the user experience. Instead of designing every screen to remain static and only respond to taps, think about ways to make the user interface animated and interactive.</p><h3>Links and Resources</h3><p>Full source code is available on GitHub:<br> <a href="https://github.com/nathangitter/interactive-animations">https://github.com/nathangitter/interactive-animations</a></p><p>Great WWDC 2017 Presentation about interactive animations:<br> <a href="https://developer.apple.com/videos/play/wwdc2017/230/">https://developer.apple.com/videos/play/wwdc2017/230/</a></p><p>Official UIViewPropertyAnimator Documentation:<br> <a href="https://developer.apple.com/documentation/uikit/uiviewpropertyanimator">https://developer.apple.com/documentation/uikit/uiviewpropertyanimator</a></p><h3>About SwiftKick Mobile</h3><p>SwiftKick Mobile is a mobile application design and development agency in Austin, TX. Need help with your next app? Reach out to <a href="http://www.swiftkickmobile.com/contact/">hello@swiftkickmobile.com</a></p><p><em>Originally published at </em><a href="http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/"><em>www.swiftkickmobile.com</em></a><em> on September 11, 2017.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca05728b1fa4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swiftkickmobile/building-better-ios-app-animations-swift-uiviewpropertyanimator-ca05728b1fa4">Building Better iOS App Animations</a> was originally published in <a href="https://medium.com/swiftkickmobile">SwiftKick Mobile</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>