1 line
382 KiB
JSON
1 line
382 KiB
JSON
{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","updated":1569829987669,"continuation":"16d635b7080:1d8b:90d684ff","items":[{"keywords":["Apple","Mac","Xcode"],"originId":"https://indiestack.com/?p=858","fingerprint":"6a684f62","id":"FQCNcmB2/bN37jEqK6evDxyNDr+PE839IP+rONrBVrE=_16d8128a555:4da02:18991ffa","author":"Daniel Jalkut","summary":{"direction":"ltr","content":"With the release of macOS 10.15 fast-approaching, more and more Mac developers will be scurrying to ensure their apps are notarized. This is the process by which binary applications are submitted to Apple for cryptographic seal-of-approval indicating that the app meets minimum requirements for safety, and shows no obvious signs of being malware. Apple offers … <a href=\"https://indiestack.com/2019/09/notarization-provider-ids/\">Continue reading <span>Notarization Provider IDs</span> <span>→</span></a>"},"alternate":[{"href":"https://indiestack.com/2019/09/notarization-provider-ids/","type":"text/html"}],"crawled":1569829987669,"title":"Notarization Provider IDs","published":1568560761000,"origin":{"streamId":"feed/http://indiestack.com/feed/","htmlUrl":"https://indiestack.com","title":"Indie Stack"},"content":{"direction":"ltr","content":"<p>With the release of macOS 10.15 fast-approaching, more and more Mac developers will be scurrying to ensure their apps are notarized. This is the process by which binary applications are submitted to Apple for cryptographic seal-of-approval indicating that the app meets minimum requirements for safety, and shows no obvious signs of being malware.</p>\n<p>Apple offers substantial documentation about <a href=\"https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution\">notarizing your apps</a>. Many developers will find that Xcode automatically notarizes the app as part of the built-in process for archiving an app for release. For those of us with existing, automated command-line build & release processes, there is a separate guide just for us:</p>\n<p>\n<a href=\"https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/customizing_the_notarization_workflow\">Customizing the Notarization Workflow</a></p>\n<p>The steps for automating notarization involve running the “altool” command from Terminal. Everything in the guide linked above should work perfectly <em>unless you’re a member of more than one development team</em>. If you have more than one team associated with your Apple ID, the back-end at Apple doesn’t know which one it should notarize on behalf of. You’ll see an error message like this:</p>\n<pre>\nError: Your Apple ID account is attached to other iTunes providers. You will need to specify which provider you intend to submit content to by using the -itc_provider command. Please contact us if you have questions or need help. (1627)</pre>\n<p>Here’s where things get fun: what the heck is your ITC provider ID? It’s not listed anywhere obvious on the Apple developer site or in Xcode, and can’t be obtained from <em>the very tool that is asking for it</em>. I came across a message from the ever-helpful Quinn in the <a href=\"https://forums.developer.apple.com/message/350431#350431\">Apple Developer Forums</a>. It details a method for locating the provider ID by running a command-line tool, iTMSTransporter, from deep within Apple’s Application Loader app.</p>\n<p>Application Loader has since been eliminated from Xcode 11, so if you’re running with modern tools, you’ll be hard pressed to find it. Fear not, the binary is preserved deep within the Xcode app bundle itself:</p>\n<pre>\n% xcrun -f iTMSTransporter\n/Users/daniel/Applications/Xcode/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter\n</pre>\n<p>All that said, here is a surefire list of steps for obtaining your ITC Provider ID, or as it’s described in the altool man page, your ASC Provider Shortcode.</p>\n<ol>\n<li>Create a new App-Specific Password from your <a href=\"https://appleid.apple.com/account/home\">Apple ID management page</a>.</li>\n<li>From Terminal, invoke iTMSTransporter with the following options:\n<pre>\nxcrun iTMSTransporter -m provider -u <yourAppleID> -p <yourAppSpecificPassword>\n</pre>\n</li>\n<li>At your discretion, revoke the App-Specific Password you created for this process.</li>\n</ol>\n<p>NOTE: These instructions apply if you are using Xcode 11. If you’re still using Xcode 10, you’ll need to dig up the iTMSTransporter binary from within Application Loader.app. Instead of “xcrun iTMSTransporter” above, it will be something like <em>/path/to/Application Loader.app/Contents/itms/bin/iTMSTransporter</em>.</p>\n<p>If all goes well, you should see a list of your Apple development teams, including the Long Name and Short Name. The <em>Short Name</em> is what you need to pass whenever altool requires an ITC or ASC Provider ID.</p>"},"visual":{"url":"http://b.vimeocdn.com/ts/442/609/442609877_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Blog"],"originId":"https://www.weheartswift.com/?p=141167","fingerprint":"ddc1f0fc","id":"l+udnvvnAeQSl5COMMeF43kFMnjb8UK43/2J+Rysa8Q=_16d8127c266:4d99c:18991ffa","author":"Florian Marcu","summary":{"direction":"ltr","content":"<p>It’s been 6 years since Apple launched the first version of Swift programming language. During these years, both the language itself and the ecosystem around it have evolved tremendously. And so did the online resources and options on how to learn Swift. In this article, we are taking a look at a different approach to learning Swift, which is often…</p>\n<p><a href=\"https://www.weheartswift.com/how-to-learn-swift-programming/\">Read more</a></p>\n<p>The post <a rel=\"nofollow\" href=\"https://www.weheartswift.com/how-to-learn-swift-programming/\">How to Learn Swift Programming Practically</a> appeared first on <a rel=\"nofollow\" href=\"https://www.weheartswift.com\">We ❤ Swift</a>.</p>"},"alternate":[{"href":"https://www.weheartswift.com/how-to-learn-swift-programming/","type":"text/html"}],"crawled":1569829929574,"title":"How to Learn Swift Programming Practically","published":1568276656000,"origin":{"streamId":"feed/http://www.weheartswift.com/feed/","htmlUrl":"https://www.weheartswift.com","title":"We ❤ Swift"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/fhKs50EFS0SJPjSjfCR7lXwcMfs=/0x0:2040x1360/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59667903/acastro_180508_1777_google_IO_0002.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d8126f6ee:1c4e:90d684ff","keywords":["Content, Content Strategy"],"originId":"https://alistapart.com/article/the-untapped-power-of-vulnerability-transparency-in-content-strategy/","fingerprint":"173b0f1c","title":"The Untapped Power of Vulnerability & Transparency in Content Strategy","author":"by Travis McKnight","summary":{"content":"\n<p>In marketing, transparency and vulnerability are unjustly stigmatized. The words conjure illusions of being frightened, imperfect, and powerless. And for companies that shove carefully curated personas in front of users, little is more terrifying than losing control of how people perceive the brand.</p>\n\n\n<p>Let’s shatter this illusioned stigma. <em>Authentic vulnerability and transparency are strengths masquerading as weaknesses</em>. And companies too scared to embrace both traits in their content forfeit bona fide user-brand connections for often shallow, misleading engagement tactics that create fleeting relationships.</p>\n\n\n<p>Transparency and vulnerability are closely entwined concepts, but each one engages users in a unique way. Transparency is how much information you share, while vulnerability is the truth and meaning behind your actions and words. Combining these ideas is the trick to creating empowering and meaningful content. You can’t tell true stories of vulnerability without transparency, and to be authentically transparent you must be vulnerable.</p>\n\n\n<p>To be vulnerable, your brand and its content must be brave, genuine, humble, and open, all of which are traits that promote long-term customer loyalty. And if you’re transparent with users about who you are and about your business practices, you’re courting <a href=\"https://www.labelinsight.com/Transparency-ROI-Study\">94 percent of consumers</a> who say they’re more loyal to brands that offer complete openness and <a href=\"https://martechtoday.com/report-customers-are-more-loyal-to-companies-that-are-transparent-on-social-media-223053\">89 percent of people</a> who say they give transparent companies a second chance after a bad experience.</p>\n\n\n<p>For many companies, being completely honest and open with their customers—or employees, in some cases—only happens in a crisis. Unfortunately for those businesses, using vulnerability and transparency only as a crisis management strategy diminishes how sincere they appear and can <a href=\"https://hbr.org/2018/01/sorry-is-not-enough\">reduce customer satisfaction</a>.</p>\n\n\n<p>Unlocking the potential of being transparent and vulnerable with users isn’t a one-off tactic or quick-fix emergency response tool—it’s a commitment to intimate storytelling that embraces a user’s emotional and psychological needs, which builds a meaningful connection between the storyteller and the audience.</p>\n\n\n<h2>The three storytelling pillars of vulnerable and transparent content</h2>\n\n\n<p>In her book, <cite>Braving the Wilderness</cite>, sociologist Brené Brown explains that vulnerability connects us at an emotional level. She says that when we recognize someone is being vulnerable, we invest in their story and begin to develop an emotional bond. This interwoven connection encourages us to experience the storyteller’s joy and pain, and then creates a sense of community and common purpose among the person being vulnerable and the people who acknowledge that vulnerability.</p>\n\n\n<p>Three pillars in a company’s lifecycle embrace this bond and provide an outline for telling stories worthy of a user’s emotional investment. The pillars are:</p>\n\n\n<ul><li>the origins of a company, product, idea, or situation;</li><li>intimate narratives about customers’ life experiences;</li><li>and insights about product success and failure.</li></ul>\n\n\n<h3>Origin stories</h3>\n\n\n<p>An origin story spins a transparent tale about how a company, product, service, or idea is created. It is often told by a founder, CEO, or industry innovator. This pillar is usually used as an authentic way to provide crisis management or as a method to change how users feel about a topic, product, or your brand.</p>\n\n\n<h3>Customers’ life experiences</h3>\n\n\n<p>While vulnerable origin stories do an excellent job of making users trust your brand, telling a customer’s personal life story is arguably the most effective way to use vulnerability to entwine a brand with someone’s personal identity.</p>\n\n\n<p>Unlike an origin story, the customer experiences pillar is focused on being transparent about who your customers are, what they’ve experienced, and how those journeys align with values that matter to your brand. Through this lens, you’ll empower your customers to tell emotional, meaningful stories that make users feel vulnerable in a positive way. In this situation, your brand is often a storytelling platform where users share their story with the brand and fellow customers.</p>\n\n\n<h3>Product and service insights</h3>\n\n\n<p>Origin stories make your brand trustworthy in a crisis, and customers’ personal stories help users feel an intimate connection with your brand’s persona and mission. The last pillar, product and service insights, combines the psychological principles that make origin and customer stories successful. The outcome is a vulnerable narrative that rallies users’ excitement about, and emotional investment in, what a company sells or the goals it hopes to achieve.</p>\n\n\n<h2>Vulnerability, transparency, and the customer journey</h2>\n\n\n<p>The three storytelling pillars are crucial to embracing transparency and vulnerability in your content strategy because they let you target users at specific points in their journey. By embedding the pillars in each stage of the customer’s journey, you teach users about who you are, what matters to you, and why they should care.</p>\n\n\n<p>For our purposes, let’s define the user journey as:</p>\n\n\n<ul><li>awareness;</li><li>interest;</li><li>consideration;</li><li>conversion;</li><li>and retention.</li></ul>\n\n\n<h3>Awareness</h3>\n\n\n<p>People give each other <a href=\"https://www.forbes.com/sites/serenitygibbons/2018/06/19/you-have-7-seconds-to-make-a-first-impression-heres-how-to-succeed/#5e47a32856c2\">seven seconds</a> to make a good first impression. We’re not so generous with brands and websites. After discovering your content, users determine if it’s trustworthy within <a href=\"https://www.psychologicalscience.org/observer/how-many-seconds-to-a-first-impression\">one-tenth of a second</a>.</p>\n\n\n<p>Page design and aesthetics are often <a href=\"https://conversionxl.com/blog/first-impressions-matter-the-importance-of-great-visual-design/\">the determining factors in these split-second choices</a>, but the information users discover after that decision shapes their long-term opinions about your brand. This snap judgement is why transparency and vulnerability are crucial within awareness content.</p>\n\n\n<p>When you only get one chance to make a positive first impression with your audience, what content is going to be more memorable?</p>\n\n\n<p>Typical marketing “fluff” about how your brand was built on a shared vision and commitment to unyielding customer satisfaction and quality products? Or an upfront, authentic, and honest story about the trials and tribulations you went through to get where you are now?</p>\n\n\n<p>Buffer, a social media management company that helped pioneer <a href=\"https://en.wikipedia.org/wiki/Radical_transparency\">the <i>radical transparency</i> movement</a>, chose the latter option. The outcome created awareness content that leaves a positive lasting impression of the brand.</p>\n\n\n<p>In 2016, Joel Gascoigne, cofounder and CEO of Buffer, used an origin story to discuss the mistakes he and his company made that resulted in laying off 10 employees.</p>\n\n\n<p>In the blog post “<a href=\"https://open.buffer.com/layoffs-and-moving-forward/\">Tough News: We’ve Made 10 Layoffs. How We Got Here, the Financial Details and How We’re Moving Forward</a>,” Gascoigne wrote about Buffer’s over-aggressive growth choices, lack of accountability, misplaced trust in its financial model, explicit risk appetite, and overenthusiastic hiring. He also discussed what he learned from the experience, the changes Buffer made based on these lessons, the consequences of those changes, and next steps for the brand.</p>\n\n\n<p>Gascoigne writes about each subject with radical honesty and authenticity. Throughout the article, he’s personable and relatable; his tone and voice make it obvious he’s more concerned about the lives he’s irrevocably affected than the public image of his company floundering. Because Gascoigne is so transparent and vulnerable in the blog post, it’s easy to become invested in the narrative he’s telling. The result is an article that feels more like a deep, meaningful conversation over coffee instead of a carefully curated, PR-approved response.</p>\n\n\n<p>Yes, Buffer used this origin story to confront a PR crisis, but they did so in a way that encouraged users to trust the brand. Buffer chose to show up and be seen when they had no control over the outcome. And because Gascoigne used vulnerability and transparency to share the company’s collective pain, the company reaped positive press coverage and support on social media—further improving brand awareness, user engagement, and customer loyalty.</p>\n\n\n<p>However, awareness content isn’t always brand focused. Sometimes, smart awareness content uses storytelling to teach users and shape their worldviews. The <a href=\"https://www.3m.com/3M/en_US/state-of-science-index-survey/about-2019-survey/\">2019 <cite>State of Science Index</cite></a> is an excellent example.</p>\n\n\n<p>The annual <cite>State of Science Index</cite> evaluates how the global public perceives science. The 2019 report shows that 87 percent of people acknowledge that science is necessary to solve the world’s problems, but 33 percent are skeptical of science and believe that scientists cause as many problems as they solve. Furthermore, 57 percent of respondents are skeptical of science because of scientists’ conflicting opinions about topics they don’t understand.</p>\n\n\n<p>3M, the multinational science conglomerate that publishes the report, says the solution for this anti-science mindset is to promote intimate storytelling among scientists and layfolk.</p>\n\n\n<p>3M creates an origin story with its awareness content by focusing on the ins and outs of scientific research. The company is open and straightforward with its data and intentions, eliminating any second guesses users might have about the content they’re digesting.</p>\n\n\n<p>The company kicked off this strategy on three fronts, and each storytelling medium interweaves the benefits of vulnerability and transparency by encouraging researchers to tell stories that lead with how their findings benefit humanity. Every story 3M tells focuses on breaking through barriers the average person faces when they encounter science and encouraging scientists to be vulnerable and authentic with how they share their research.</p>\n\n\n<p>First, 3M began a podcast series known as <a href=\"https://www.3m.com/3M/en_US/state-of-science-index-survey/science-champion-podcasts/\"><cite>Science Champions</cite></a>. In the podcast, 3M Chief Science Advocate Jayshree Seth interviews scientists and educators about the global perception of science and how science and scientists affect our lives. The show is currently in its second season and discusses a range of topics in science, technology, engineering, and math.</p>\n\n\n<p>Second, the company worked with science educators, journalist Katie Couric, actor Alan Alda, and former NASA astronaut Scott Kelly to develop the free <a href=\"https://multimedia.3m.com/mws/media/1667242O/sosi-3-13-toolkit-pdf.pdf\"><cite>Scientists as Storytellers Guide</cite></a>. The ebook helps STEM researchers improve how and why they communicate their work with other people—with a special emphasis on being empathetic with non-scientists. The guide breaks down how to develop communications skills, overcome common storytelling challenges, and learn to make science more accessible, understandable, and engaging for others.</p>\n\n\n<p>Last, 3M created a film series called <a href=\"https://www.3m.com/3M/en_US/state-of-science-index-survey/insights/beyond-the-beaker/\"><cite>Beyond the Beaker</cite></a> that explores the day-to-day lives of 3M scientists. In the short videos, scientists give the viewer a glimpse into their hobbies and home life. The series showcases how scientists have diverse backgrounds, hobbies, goals, and dreams.</p>\n\n\n<p>Unlike Buffer, which benefits directly from its awareness content, 3M’s three content mediums are designed to create a long-term strategy that changes how people understand and perceive science, by spreading awareness through third parties. It’s too early to conclude that the strategy will be successful, but it’s off to a good start. <cite>Science Champions</cite> often tops “best of” podcast lists for science lovers, and the <cite>Scientists as Storytellers Guide</cite> is a popular resource among public universities.</p>\n\n\n<h3>Interest</h3>\n\n\n<p>How do you court new users when word-of-mouth and organic search dominate how people discover new brands? Target their interests.</p>\n\n\n<p>Now, you can be like the hundreds of other brands that create a “10 best things” list and hope people stumble onto your content organically and like what they see. Or, you can use content to engage with people who are passionate about your industry and have genuine, open discussions about the topics that matter to you both.</p>\n\n\n<p>The latter option is a perfect fit for the product and service insights pillar, and the customers’ life experiences pillar.</p>\n\n\n<p>To succeed in these pillars you must balance discussing the users’ passions and how your brand plays into that topic against appearing disingenuous or becoming too self-promotional.</p>\n\n\n<p>Nonprofits have an easier time walking this taut line because people are less judgemental when engaging with NGOs, but it’s rare for a for-profit company to achieve this balance. SpaceX and Thinx are among the few brands that are able to walk this tightrope.</p>\n\n\n<p>Thinx, a women’s clothing brand that sells period-proof underwear, uses its blog to generate awareness, interest, and consideration content via the customers’ life experiences pillar. The blog, aptly named <a href=\"https://www.shethinx.com/pages/thinx-periodical/\"><cite>Periodical</cite></a>, relies on transparency and vulnerability as a cornerstone to engage users about reproductive and mental health.</p>\n\n\n<p>Toni Brannagan, Thinx’s content editor, says the brand embraces transparency and vulnerability by sharing diverse ideas and personal experiences from customers and experts alike, not shying away from sensitive subjects and never misleading users about Thinx or the subjects <cite>Periodical</cite> discusses.</p>\n\n\n<p>As a company focused on women’s healthcare, the product Thinx sells is political by nature and entangles the brand with themes of shame, cultural differences, and personal empowerment. Thinx’s strategy is to tackle these subjects head-on by having vulnerable conversations in its branding, social media ads, and <cite>Periodical</cite> content.</p>\n\n\n<p>“Vulnerability and transparency play a role because you can’t share authentic diverse ideas and experiences about those things—shame, cultural differences, and empowerment—without it,” Brannagan says.</p>\n\n\n<p>A significant portion of Thinx’s website traffic is organic, which means <cite>Periodical</cite>’s interest-driven content may be a user’s first touchpoint with the brand.</p>\n\n\n<p>“We’ve seen that our most successful organic content is educational, well-researched articles, and also product-focused blogs that answer the questions about our underwear, in a way that’s a little more casual than what’s on our product pages,” Brannagan says. “In contrast, our personal essays and ‘more opinionated’ content performs better on social media and email.”</p>\n\n\n<p>Thanks in part to the blog’s authenticity and open discussions about hard-hitting topics, readers who find the brand through organic search drive the most direct conversions.</p>\n\n\n<p>Conversations with users interested in the industry or topic your company is involved in don’t always have to come from the company itself. Sometimes a single person can drive authentic, open conversations and create endearing user loyalty and engagement.</p>\n\n\n<p>For a company that relies on venture capital investments, NASA funding, and public opinion for its financial future, crossing the line between being too self-promotional and isolating users could spell doom. But SpaceX has never shied away from difficult or vulnerable conversations. Instead, the company’s founder, Elon Musk, embraces engaging with users interests in public forums like Twitter and press conferences.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Twitter thread showing an exchange between Elon Musk and a user\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig1.png?w=589\" class=\"wp-image-7172044\"></figure>\n\n\n<p>Musk’s tweets about SpaceX are unwaveringly authentic and transparent. He often tweets about his thoughts, concerns, and the challenges his companies face. Plus, Musk frequently engages with his Twitter followers and provides candid answers to questions many CEOs avoid discussing. This authenticity has earned him a <a href=\"https://thewire.in/tech/the-new-institution-called-elon-musk\">cult-like following</a>.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Elon Musk gives an honest, if not flattering, response on Twitter to a user\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig2.png?w=790\" class=\"wp-image-7172045\"></figure>\n\n\n<p>Musk and SpaceX create conversations that target people’s interests and use vulnerability to equally embrace failure and success. Both the company and its founder give the public and investors an unflinching story of space exploration.</p>\n\n\n<p>And despite laying off 10 percent of its workforce in January of 2019, SpaceX is flourishing. In May 2019, its <a href=\"https://www.cnbc.com/2019/05/31/spacex-valuation-33point3-billion-after-starlink-satellites-fundraising.html\">valuation had risen to $33.3 billion</a> and reported <a href=\"https://www.cnbc.com/2019/05/20/spacex-revenue-2-billion-from-rockets-last-year-jefferies-estimate.html\">annual revenue exceeded $2 billion</a>. It also earned global media coverage from launching Musk’s Tesla Roadster into space, recently completed a test flight of its Crew Dragon space vehicle, and cemented multiple new payload contracts.</p>\n\n\n<p>By engaging with users on social media and through standard storytelling mediums, Thinx and SpaceX bolster customer loyalty and brand engagement.</p>\n\n\n<h3>Consideration</h3>\n\n\n<p>Modern consumers argue that ignorance is not bliss. When users are considering converting with a brand, <a href=\"https://sproutsocial.com/insights/guides/2018-bgr-transparency/\">86 percent of consumers say transparency is a deciding factor</a>. Transparency remains crucial even after they convert, with 85 percent of users saying they’ll support a transparent brand during a PR crisis.</p>\n\n\n<p>Your brand must be open, clear, and honest with users; there is no longer another viable option.</p>\n\n\n<p>So how do you remain transparent while trying to sell someone a product? One solution employed by REI and Everlane is to be openly accountable to your brand and your users via the origin stories and product insights pillars.</p>\n\n\n<p>REI, a national outdoor equipment retailer, created a <a href=\"https://www.rei.com/stewardship/outdoor-industry-collaboration\">stewardship program</a> that behaves as a multifaceted origin story. The program’s content highlights the company’s history and manufacturing policies, and it lets users dive into the nitty-gritty details about its factories, partnerships, product production methods, manufacturing ethics, and carbon footprint.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Screenshot of the Collaborating for Good website\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig3.png?w=960\" class=\"wp-image-7172046\"></figure>\n\n\n<p>REI also employs a classic <a href=\"https://www.portent.com/blog/seo/content-hubs-seo-strategy.htm\">content hub strategy</a> to let customers find the program and explore its relevant information. From a single landing page, users can easily find the program through the website’s global navigation and then navigate to every tangential topic the program encompasses.</p>\n\n\n<p>REI also publishes an annual stewardship report, where users can learn intimate details about how the company makes and spends its money.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Screenshot of REI's stewardship report\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig4.png?w=960\" class=\"wp-image-7172047\"></figure>\n\n\n<p>Everlane, a clothing company, is equally transparent about its supply chain. The company promotes an insider’s look into its global factories via product insights stories. These glimpses tell the personal narratives of factory employees and owners, and provide insights into the products manufactured and the materials used. Everlane also <a href=\"https://www.everlane.com/supply-chain\">published details</a> of how they comply with the California Transparency in Supply Chains Act to guarantee ethical working conditions throughout its supply chain, including refusing to partner with human traffickers.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Screenshot of Everlane's page about the factory in Lima\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig5.png?w=960\" class=\"wp-image-7172048\"></figure>\n\n\n<p>The crucial quality that Everlane and REI share is they publicize their transparency and encourage users to explore the shared information. On each website, users can easily find information about the company’s transparency endeavors via the global navigation, social media campaigns, and product pages.</p>\n\n\n<p>The consumer response to transparent brands like REI and Everlane is overwhelmingly positive. <a href=\"https://www.environmentalleader.com/2018/12/study-shows-consumers-are-willing-to-pay-a-premium-for-supply-chain-transparency/\">Customers are willing to pay price premiums for the additional transparency</a>, which gives them comfort by knowing they’re purchasing ethical products.</p>\n\n\n<p>REI’s ownership model has further propelled the success of its transparency by using it to create unwavering customer engagement and loyalty. As a co-op where customers can “own” part of the company for a one-time $20 membership fee, REI is beholden to its members, many of which pay close attention to its supply chain and the brands REI partners with.</p>\n\n\n<p>After a deadly school shooting in Parkland, Florida, REI members urged the company to refuse to carry CamelBak products because the brand’s parent company manufactures assault-style weapons. Members argued the partnership violated REI’s supply chain ethics. <a href=\"https://www.chicagotribune.com/business/ct-biz-rei-vista-outdoor-20180302-story.html\">REI listened and halted orders with CamelBak</a>. Members rejoiced and REI earned a significant amount of positive press coverage.</p>\n\n\n<h3>Conversion</h3>\n\n\n<p>Imagine you’ve started incorporating transparency throughout your company, and promote the results to users. Your brand also begins engaging users by telling vulnerable, meaningful stories via the three pillars. You’re seeing great engagement metrics and customer feedback from these efforts, but not much else. So, how do you get your newly invested users to convert?</p>\n\n\n<p>Provide users with a full-circle experience.</p>\n\n\n<p>If you combine the three storytelling pillars with blatant transparency and actively promote your efforts, users often transition from the consideration stage into the conversion state. Best of all, when users convert with a company that already earned their trust on an emotional level, <a href=\"https://link.springer.com/article/10.1057/palgrave.bm.2550080\">they’re more likely to remain loyal to the brand</a> and emotionally invested in its future.</p>\n\n\n<p>The crucial step in combining the three pillars is consistency. Your brand’s stories must always be authentic and your content must always be transparent. The outdoor clothing brand Patagonia is among the most popular and successful companies to maintain this consistency and excel with this strategy.</p>\n\n\n<p>Patagonia is arguably the most vocal and aggressive clothing retailer when it comes to environmental stewardship and ethical manufacturing.</p>\n\n\n<p>In some cases, the company tells users <em>not to buy its clothing</em> because rampant consumerism harms the environment too much, which they care about more than profits. This level of radical transparency and vulnerability skyrocketed the company’s popularity among environmentally-conscious consumers.</p>\n\n\n<p>In 2011, Patagonia took out a <a href=\"https://www.patagonia.com/blog/2011/11/dont-buy-this-jacket-black-friday-and-the-new-york-times/\">full-page Black Friday ad in the New York Times</a> with the headline “Don’t Buy This Jacket.” In the ad, Patagonia talks about the environmental toll manufacturing clothes requires.</p>\n\n\n<p>“Consider the R2 Jacket shown, one of our best sellers. To make it required 135 liters of water, enough to meet the daily needs (three glasses a day) of 45 people. Its journey from its origin as 60 percent recycled polyester to our Reno warehouse generated nearly 20 pounds of carbon dioxide, 24 times the weight of the finished product. This jacket left behind, on its way to Reno, two-thirds [of] its weight in waste.”</p>\n\n\n<p>The ad encourages users to not buy any new Patagonia clothing if their old, ratty clothes can be repaired. To help, Patagonia launched a supplementary subdomain to its e-commerce website to support its Common Thread Initiative, which eventually got rebranded as the <a href=\"https://wornwear.patagonia.com/\">Worn Wear program</a>.</p>\n\n\n<p>Patatgonia’s Worn Wear subdomain gets users to engage with the company about causes each party cares about. Through Worn Wear, Patagonia will repair your old gear for free. If you’d rather have new gear, you can instead sell the worn out clothing to Patagonia, and they’ll repair it and then resell the product at a discount. This interaction encourages loyalty and repeat brand-user engagement.</p>\n\n\n<p>In addition, the navigation on Patagonia’s main website practically begs users to learn about the brand’s non-profit initiatives and its commitment to ethical manufacturing.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Screenshot of Patagonia's page on environmental responsibility\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig6.png?w=960\" class=\"wp-image-7172049\"></figure>\n\n\n<p>Today, <a href=\"https://crm.org/articles/patagonias-customer-base-and-the-rise-of-an-environmental-ethos\">Patagonia is among the most respected, profitable, and trusted consumer brands</a> in the United States.</p>\n\n\n<h3>Retention</h3>\n\n\n<p>Content strategy expands through nearly every aspect of the marketing stack, including ad campaigns, which take a more controlled approach to vulnerability and transparency. To target users in the retention stage and keep them invested in your brand, your goal is to create content using the customers’ life experiences pillar to amplify the emotional bond and brand loyalty that vulnerability creates.</p>\n\n\n<p>Always took this approach and ended up with one of its most successful social media campaigns.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"An Always ad portraying a determined girl holding a baseball\" src=\"https://alistapart.com/wp-content/uploads/2019/08/vulnerability-marketing-fig7.png?w=557\" class=\"wp-image-7172050\"></figure>\n\n\n<p>In June 2014, Always launched its #LikeAGirl campaign to empower adolescent and teenage girls by transforming the phrase “like a girl” from a slur into a meaningful and positive statement.</p>\n\n\n<p>The campaign is centered on a video in which Always tasked children, teenagers, and adults to behave “like a girl” by running, punching, and throwing while mimicking their perception of how a girl performs the activity. Young girls performed the tasks wholeheartedly and with gusto, while boys and adults performed overly feminine and vain characterizations. The director then challenged the person on their portrayal, breaking down what doing things “like a girl” truly means. The video ends with a powerful, heart-swelling statement:</p>\n\n\n<figure><blockquote><p>“If somebody else says that running like a girl, or kicking like a girl, or shooting like a girl is something you shouldn’t be doing, that’s their problem. Because if you’re still scoring, and you’re still getting to the ball in time, and you’re still being first...you’re doing it right. It doesn’t matter what they say.”</p></blockquote></figure>\n\n\n<p>This customer story campaign put the vulnerability girls feel during puberty front and center so the topic would resonate with users and give the brand a powerful, relevant, and purposeful role in this connection, <a href=\"https://instituteforpr.org/wp-content/uploads/Always-LikeAGirl-Turning-an-Insult-into-a-Confidence-Movement.pdf\">according to an Institute for Public Relations campaign analysis</a>.</p>\n\n\n<p>Consequently, the #LikeAGirl campaign was a rousing success and blew past the KPIs Always established. Initially, Always determined an “impactful launch” for the video meant 2 million video views and 250 million media impressions, the analysis states.</p>\n\n\n<p>Five years later, the campaign video has more than 66.9 million views and 42,700 comments on YouTube, with more than 85 percent of users reacting positively. Here are a few additional highlights the analysis document points out:</p>\n\n\n<ul><li>Eighty-one percent of women ages 16–24 support Always in creating a movement to reclaim “like a girl” as a positive and inspiring statement.</li><li>More than 1 million people shared the video.</li><li>Thirteen percent of users created user-generated content about the campaign.</li><li>The #LikeAGirl program achieved 4.5 billion global impressions.</li><li>The campaign received 290 million social impressions, with 133,000 social mentions, and it caused a 195.3 percent increase in the brand’s Twitter followers.</li></ul>\n\n\n<p>Among the reasons the #LikeAGirl content was so successful is that it aligned with Brené Brown’s concept that experiencing vulnerability creates a connection centered on powerful, shared emotions. Always then amplified the campaign’s effectiveness by using those emotions to encourage specific user behavior on social media.</p>\n\n\n<h2>How do you know if you’re making vulnerable content?</h2>\n\n\n<p>Designing a vulnerability-focused content strategy campaign begins by determining what kind of story you want to tell, why you want to tell it, why that story matters, and how that story helps you or your users achieve a goal.</p>\n\n\n<p>When you’re brainstorming topics, the most important factor is that you need to care about the stories you’re telling. These tales need to be meaningful because if you’re weaving a narrative that isn’t important to you, it shows. And ultimately, why do you expect your users to care about a subject if you don’t?</p>\n\n\n<p>Let’s say you’re developing a content campaign for a nonprofit, and you want to use your brand’s emotional identity to connect with users. You have a handful of possible narratives but you’re not sure which one will best unlock the benefits of vulnerability. In a Medium post about <a href=\"https://medium.com/@caylavidmar/your-story-matters-3-powerful-brands-that-do-vulnerability-right-a3360694ffa7\">telling vulnerable stories</a>, Cayla Vidmar presents a list of seven self-reflective questions that can reveal what narrative to choose and why.</p>\n\n\n<p>If you can answer each of Vidmar’s questions, you’re on your way to creating a great story that can connect with users on a level unrivaled by other methods. Here’s what you should ask yourself:</p>\n\n\n<ul><li>What meaning is there in my story?</li><li>Can my story help others?</li><li>How can it help others?</li><li>Am I willing to struggle and be vulnerable in that struggle (even with strangers)?</li><li>How has my story shaped my worldview (what has it made me believe)?</li><li>What good have I learned from my story?</li><li>If other people discovered this good from their story, would it change their lives?</li></ul>\n\n\n<p>While you’re creating narratives within the three pillars, refer back to Vidmar’s list to maintain the proper balance between vulnerability and transparency.</p>\n\n\n<h2>What’s next?</h2>\n\n\n<p>You now know that vulnerability and transparency are an endless fountain of strength, not a weakness. Vulnerable content won’t make you or your brand look weak. Your customers won’t flee at the sight of imperfection. Being human and treating your users like humans isn’t a liability.</p>\n\n\n<p>It’s time for your brand to embrace its untapped potential. Choose to be vulnerable, have the courage to tell meaningful stories about what matters most to your company and your customers, and overcome the fear of controlling how users will react to your content.</p>\n\n\n<p><strong>Origin story</strong></p>\n\n\n<p>Every origin story has six chapters:</p>\n\n\n<ul><li>the discovery of a problem or opportunity;</li><li>what caused this problem or opportunity;</li><li>the consequences of this discovery;</li><li>the solution to these consequences;</li><li>lessons learned during the process;</li><li>and next steps.</li></ul>\n\n\n<p><strong>Customers’ life experiences</strong></p>\n\n\n<p>Every customer journey narrative has six chapters:</p>\n\n\n<ul><li>plot background to frame the customer’s experiences;</li><li>the customer’s journey;</li><li>how the brand plays into that journey (if applicable);</li><li>how the customer’s experiences changed them;</li><li>what the customer learned from this journey;</li><li>and how other people can use this information to improve their lives.</li></ul>\n\n\n<p><strong>Product and service insights</strong></p>\n\n\n<p>Narratives about product and service insights have seven chapters:</p>\n\n\n<ul><li>an overview of the product/service;</li><li>how that product/service affects users;</li><li>why the product/service is important to the brand’s mission or to users;</li><li>what about this product/service failed or succeeded;</li><li>why did that success or failure happen;</li><li>what lessons did this scenario create;</li><li>and how are the brand and its users moving forward.</li></ul>\n\n\n<p>You have the tools and knowledge necessary to be transparent, create vulnerable content, and succeed. And we need to tell vulnerable stories because sharing our experiences and embracing our common connections matters. So go ahead, put yourself out into the open, and see how your customers respond.</p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/MMGA19A7E-c\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/MMGA19A7E-c/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/the-untapped-power-of-vulnerability-transparency-in-content-strategy/","type":"text/html"}],"crawled":1569829877486,"published":1569829877486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d8125222e:1c4d:90d684ff","keywords":["Career"],"originId":"https://alistapart.com/article/the-career-management-document/","fingerprint":"687902d8","title":"An Essential Tool for Capturing Your Career Accomplishments","author":"by Jessica Ivins","summary":{"content":"\n<p>Imagine you’re ready to apply for your next job. Like most busy professionals, you probably haven’t updated your résumé or your portfolio since you looked for your current job. </p>\n\n\n<p>Now you need to update both, and you can’t remember what work you’ve done over the past few years. (In fact, you can barely remember what you’ve done over the past few months!)</p>\n\n\n<p>So you scramble to update your résumé with new content. Then you spend all weekend scraping together a new portfolio using screenshots of whatever work evidence you can find on your laptop. You submit the résumé and portfolio with your application, hoping you didn’t forget to include any major career milestones you achieved over the last few years. </p>\n\n\n<p>This is the process most of us use to approach our job search. We wait until we’re ready to find a job, panic at our lack of résumé and portfolio, and pull together a “good enough” version of each for the job application. (Trust me, I’ve done this many times myself.)</p>\n\n\n<p>This is a stressful and ineffective way to approach a job search. There’s a much better approach you can take—and you can start working on it now, even if you’re not on the job market.</p>\n\n\n<h2><strong>The Career Management Document</strong></h2>\n\n\n<p>A Career Management Document (CMD) is a comprehensive collection of your résumé and portfolio content. It’s a document you update regularly, over time, with all the work you’ve done. </p>\n\n\n<p>When you’re ready to apply for your next job, you’ll have all the résumé and portfolio pieces available in your CMD. All you need to do is assemble those pieces into résumé and portfolio documents, then send the documents off with your job application.</p>\n\n\n<p>I update my CMD about once a week. I start by reviewing evidence of my recent work. I review Slack messages, Basecamp posts, emails, and any other current work-related content. I write my accomplishments in the format of résumé bullets, using the <a href=\"https://www.manager-tools.com/2005/10/your-resume-stinks\">framework of responsibilities and accomplishments from this Manager Tools podcast</a>. Then I add those bullets to the CMD. </p>\n\n\n<p>Here are some examples from my CMD:</p>\n\n\n<ul><li>Coached a student on writing a stronger portfolio story to showcase their advanced UX skills, resulting in the student getting a job interview.</li><li>Facilitated an end-of-study analysis in under 90 minutes to help the team synthesize user research data from 12 participants.</li><li>Led a remote retrospective with teams in two offices, developed actionable takeaways, and ended on time despite a delayed start.</li></ul>\n\n\n<p>My CMD has several hundred résumé bullets, and it continues to grow. I organize content by year and by project. Within each project are responsibilities and accomplishments.</p>\n\n\n<p>I add any content to the CMD that might go into my résumé someday. I include everything I can think of, even if it seems insignificant or trivial at the time. </p>\n\n\n<p>For example, I sometimes help with social media marketing at Center Centre, the UX design school where I’m a faculty member. I include it in my CMD. I don’t plan to pursue social media marketing as a career, but it may be relevant to a future job. Who knows—I may apply to work for an organization that makes social media marketing software someday. In that case, my social media experience could be relevant.</p>\n\n\n<h2><strong>Include portfolio artifacts with your CMD</strong></h2>\n\n\n<p>In addition to capturing bullets for my résumé, I capture content for my portfolio. Each week, I gather screenshots of my work, photos of me working with the team, and any other artifacts I can find. I store them in an organized system I can reference later. </p>\n\n\n<p>I also take brief notes about the work I did and store them with the artifacts. That way, if I look back at these materials a year from now, I’ll have notes about what I did during the project, reminding me of the details.</p>\n\n\n<p>For example, after I facilitated a user research analysis session late last year, I captured evidence of it for my portfolio. I included photos of the whiteboard where I recorded public notes during the session. I also captured brief notes about who attended the session, the date, and when it took place during the project. </p>\n\n\n<p>You can use whatever tools you’d like to gather evidence of your work. I use Google Docs for the résumé portion of my CMD. I use Dropbox to store my portfolio artifacts. I create Dropbox folders with dates and project names that correspond to the contents of my CMD.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"\" src=\"https://alistapart.com/wp-content/uploads/2019/08/cmd-screenshot-1.jpg\" class=\"wp-image-7171743\"><figcaption><br>Résumé content from my CMD. I wrote about coaching a student on crafting a presentation for her job interview. The highlighted areas are where I left comments reminding me of the details of the work. Note that some of the résumé bullets seem redundant, which is OK. When I create my next résumé, I’ll choose the most appropriate bullets.</figcaption></figure>\n\n\n<figure class=\"wp-block-image\"><img alt=\"\" src=\"https://alistapart.com/wp-content/uploads/2019/08/cmd-screenshot-2.jpg\" class=\"wp-image-7171744\"><figcaption><br>I took notes on a whiteboard while coaching the student. I stored a photo of the whiteboard in Dropbox in a folder named with the date of the work and a description of what I did.</figcaption></figure>\n\n\n<p>The key is to collect the evidence regularly and store it in an accessible, organized way that works for you. To know if you’re storing work evidence effectively, ask yourself, “Will I understand this CMD content a year from now based on how I’m capturing and storing it today?” If the answer is “yes,” you’re in good shape.</p>\n\n\n<h2><strong>Update your CMD regularly</strong></h2>\n\n\n<p>For the CMD to work when you need it, it needs to be comprehensive and up-to-date. As I mentioned before, I update my CMD once a week. I schedule thirty minutes on my calendar each week so I remember to do it. </p>\n\n\n<p>Sometimes I have a busy week, and I can’t spend thirty minutes on my CMD. So I spend whatever amount of time I have. Some weeks, I only spend ten minutes. Ten minutes per week is better than zero minutes per week. </p>\n\n\n<p>Occasionally, I don’t get a chance to update it because my week is so hectic. That’s OK because I’ll probably get to it the following week. </p>\n\n\n<p>I recommend updating your CMD once a week and not once a month or once a quarter. If you wait even a month, you’ll have trouble remembering what you did three and a half weeks ago. Even worse, if you schedule a CMD update once a month and then miss it, you won’t get to it until the next month. That means you have to think back and remember two months of work, which is hard to do. </p>\n\n\n<p>Updating your CMD every week, while the work is fresh in your mind, gets the best results.</p>\n\n\n<h2><strong>The CMD benefits you in additional ways</strong></h2>\n\n\n<p>The CMD can help you prepare for your job search beyond your résumé and your portfolio. </p>\n\n\n<p>You can use it to prepare for a job interview. Since you’re capturing work evidence from each stage of the process in your CMD, you can use that evidence to remember what you did throughout a project. Then, you can craft a story about your role on that project. </p>\n\n\n<p>Hiring managers love to hear stories about your work during job interviews. For instance, if you’re a designer, they want to know the journey you took during your design process, from the start of a project to the end. A detailed CMD will help you remember this process so you can share it in an interview. </p>\n\n\n<p>I’ve even used my CMD to write blog posts. I’ve been blogging regularly for the past two years, and I often refer to my CMD to remember work experience I had that’s relevant to what I’m writing. When I wrote the article “<a href=\"https://uxdesign.cc/how-to-tell-compelling-stories-during-a-ux-job-interview-ef5350ff83e7\">How to Tell Compelling Stories During a UX Job Interview</a>,” I used my CMD to remember interview preparation exercises I did with students. </p>\n\n\n<p>The CMD can also help you track work accomplishments for your quarterly or annual performance reviews. Additionally, you can use it to write job ads when hiring for related roles on your team.</p>\n\n\n<p>Lastly, I find it rewarding to peruse my CMD now and then, especially when I look back at work I did over a year ago. The CMD serves as a record of all my professional accomplishments. This record helps me appreciate my professional growth because I see how far my skills have come over time.</p>\n\n\n<h2><strong>Learn more about the CMD from Manager Tools</strong></h2>\n\n\n<p>At Center Centre, we originally learned about the Career Management Document through the <a href=\"https://www.manager-tools.com/\">Manager Tools podcast series</a>.</p>\n\n\n<p>Manager Tools’ podcasts explain how to use a CMD for your résumé. We expanded their approach to include portfolio work as well. I recommend listening to their podcasts about creating and maintaining your CMD:</p>\n\n\n<ul><li><a href=\"https://www.manager-tools.com/2010/06/systematic-career-documentation-part-1-hall-fame-guidance\">Systematic Career Documentation (Part 1)</a></li><li><a href=\"https://www.manager-tools.com/2010/06/systematic-career-documentation-part-2-hall-fame-guidance\">Systematic Career Documentation (Part 2)</a></li></ul>\n\n\n<h2><strong>Prepare for your next job search now</strong></h2>\n\n\n<p>We tell our students at Center Centre that preparing for your next job search is a process that starts early. It’s like saving for retirement—the sooner you start saving money, the more likely you are to be prepared when the time comes. </p>\n\n\n<p>Similarly, collecting résumé and portfolio content ahead of time will prepare you to find your next job whenever you’re ready to do so. It also prepares you for a sudden job termination like an unexpected layoff. If you lose your job without warning, you’ll likely be under a lot of stress to find a new position. Having a CMD ready will relieve the additional stress of building a résumé and portfolio from scratch. </p>\n\n\n<p>If you don’t have a CMD yet, now is a great time to start one. Schedule 30 minutes this week to begin crafting your repository of work accomplishments. You’ll be glad you did when you seek your next job.</p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/R4Jkp4VLCKM\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/R4Jkp4VLCKM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/the-career-management-document/","type":"text/html"}],"crawled":1569829757486,"published":1569829757486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d81234d6e:1c4c:90d684ff","keywords":["Accessibility"],"originId":"https://alistapart.com/article/getting-to-the-heart-of-digital-accessibility/","fingerprint":"5a338ace","title":"Getting to the Heart of Digital Accessibility","author":"by Carie Fisher","summary":{"content":"\n<p>Quick! Think of the word “developer” or “coder” — what’s the first thing that comes to mind? Maybe a whiteish male in his twenties living in a busy metropolis, wearing a nerdy t-shirt and hoodie? Someone a bit like Mark Zuckerberg? Or maybe a younger Bill Gates or Sergey Brin? Any of the dudes from the HBO series <em>Silicon Valley</em>, perhaps? Certainly no one like me.</p>\n\n\n<p>By tech standards, I’m old. I’m also female <em>and</em> a mother. I live in a midwestern town you’ve never heard of and will never visit — a town where the cows vastly outnumber the people. My hair color is (almost) natural and is no longer part of the ROYGBIV collection, so I have no perceived conference street cred. I own about a thousand geeky T-shirts, but never actually wear them in public, opting for more “girly” attire (or so was pointed out by a male colleague). On the surface, I look more suited to taking notes at a PTA meeting than writing code. I’m a bit of an outsider. A tech misfit.</p>\n\n\n<p>So when my 11-year-old daughter finished her recent coding camp and excitedly declared, “Now I’m a real developer, Mom, just like you!” there was the usual parent pride, but also a small piece of me that cringed. Because, as much as I support the STEM fields, and want the next generation of girls to be coding wizard-unicorn-ninjas, I really don’t want my own daughter to be a developer. The rationale behind this bold (and maybe controversial) statement comes from a place of protection. The tech world we live in today is far from perfect. I’ve endured my share of misogyny, self-doubt, and sexual harassment. Why wouldn’t I want to protect her from all of that?</p>\n\n\n<h2>The (diversity) elephant in the (computer) room</h2>\n\n\n<p>You’ve heard this story before: there is not enough diversity in tech. This puzzling trend seems to continue year after year, even though numerous studies show that by including more people from underrepresented communities, a company can increase its <a href=\"https://www.fastcompany.com/40515712/want-a-more-innovative-company-simple-hire-a-more-diverse-workforce\">innovation</a>, <a href=\"https://devskiller.com/diversity-in-tech/\">employee retention</a>, and <a href=\"https://www.forbes.com/sites/annapowers/2018/06/27/a-study-finds-that-diverse-companies-produce-19-more-revenue/\">bottom line</a>. Even with the recent push and supposed support for diversity and inclusivity from many Fortune 500 companies, women and female-identifying people still only hold <a href=\"https://smallbiztrends.com/2018/03/women-in-technology-statistics.html\">20% of all top tech jobs</a>.</p>\n\n\n<p>The data from FY 2018 shows that the number of women in technical roles at three of the top tech giants was <a href=\"https://www.adobe.com/diversity/data.html\">24% for Adobe</a>, <a href=\"https://diversity.google/annual-report/\">26% for Google</a>, and <a href=\"https://newsroom.fb.com/news/2018/07/diversity-report/\">22% for Facebook</a>. While these numbers show that there is still not enough representation for women, these numbers do reflect a slight increase from the previous year (FY 2017: Adobe 22%, Google 25%, Facebook 15%). But even with this upward trend of hiring women in tech roles, the marginal growth rate has not caught up with the real world. The tech workforce is seriously out of touch with reality if, in 2019, a demographic (women) that represents more than half the global population is still considered a minority.</p>\n\n\n<p>Sometimes this lack of diversity at the top level is blamed on a “pipeline” issue. The logic being: “If there are not enough girls who learn to code, then there will not be enough women who can code.” However, programs aimed at <a href=\"https://girlswhocode.com/\">teaching girls how to code</a> have skyrocketed in the past few years. Girls now make up about <a href=\"https://csedu.gallup.com/home.aspx\">half of the enrollment</a> in high-school coding classes and are scoring almost identically to their male classmates on standardized math and science tests, yet, young women make up only <a href=\"https://ngcproject.org/statistics\">18% of all Computer Science degrees</a>. I have to wonder if this steep drop in interest has more to do with lack of representation in the tech sphere, than with girls and young women simply not being “smart enough” or “not interested” in working with code? At the very least, the lack of representation certainly doesn’t help.</p>\n\n\n<p>Of course, the diversity picture becomes even more abysmal when you consider other underrepresented groups such as people of color, people from the LGBTQ community, and people with disabilities. And while I really don’t like glossing over these deeper diversity issues in tech, because they are abundant and are much more grotesque failings than the male/female ratio, I also don’t feel qualified to speak about these issues. I encourage you to look to and value the voices of others who can speak with higher authority on these deeper diversity issues, such as <a href=\"https://twitter.com/ireaderinokun\">Ire Aderinokun</a>, <a href=\"https://twitter.com/TaelurAlexis\">Taelur Alexis</a>, <a href=\"https://twitter.com/Imani_Barbarin\">Imani Barbarin</a>, <a href=\"https://twitter.com/techgirl1908\">Angie Jones</a>, <a href=\"https://twitter.com/sugaroverflow\">Fatima Khalid</a>, <a href=\"https://twitter.com/TatianaTMac\">Tatiana Mac</a>, <a href=\"https://twitter.com/sonniesedge\">Charlie Owen</a>, <a href=\"https://twitter.com/cherryrae\">Cherry Rae</a>, and so many others. And for those readers who are new to the topic of diversity in tech, watch Tatiana Mac’s recent conference talk <a href=\"https://youtu.be/nQq_gZiZ-jg\">How Privilege Defines Performance</a> — it’s well worth the 35 minutes of your life.</p>\n\n\n<h2>The four stages in the digital accessibility journey</h2>\n\n\n<p>However you look at it, the numbers don’t lie. There are some pretty significant diversity issues in tech. So how do we fix this issue before the next wave of young developers join the tech workforce? Simple: teach developers to write accessible code.</p>\n\n\n<p>This may seem like a joke to some and stretch to others, but hear me out. When we talk about accessible code, what we are really talking about at its core is inclusiveness. The actual process of writing accessible code involves rules and standards, tests and tools; but inclusive development is more abstract than that. It’s a shift in thinking. And when we rethink our approach to development, we go beyond just the base level of simple code functionality. We instead think, <em>how</em> is this code consumed? How can we make it even more intelligible and easier for people to use? Inclusive development means making something valuable, not just accessible, to as many people as we can.</p>\n\n\n<p>That line of thinking is a bit abstract, so let’s go through an example. Let’s say you are tasked with updating the color contrast between the text on a webpage or app and the background. What happens at each stage in the accessibility journey?</p>\n\n\n<p><strong>Stage 1: Awareness </strong>— You are brand new to digital accessibility and are still trying to understand what it is and how you can implement changes in your daily workflow. You may be aware that there is a set of <a href=\"https://www.w3.org/WAI/WCAG21/quickref/\">digital accessibility guidelines</a> that other developers follow, but you are a bit hazy on what it all means in a practical sense.</p>\n\n\n<p><strong>Stage 2: Knowledge </strong>— You know a bit more about digital accessibility and feel comfortable using a few testing tools, so you run an automated accessibility test on your website and it flags a possible issue with the color contrast. Based on your awareness of the guidelines, you know the <a href=\"https://contrastchecker.com/\">color contrast ratio</a> between the text and the background needs to be a certain number and that you need a tool to test this.</p>\n\n\n<p><strong>Stage 3: Practice </strong>— Feeling more confident in your knowledge of digital accessibility rules and best practices, you use a <a href=\"https://developer.paciellogroup.com/resources/contrastanalyser/\">tool to measure the color contrast ratio</a> between the text and the background. Then based on the output of the tool, you modify the hex code to meet the color contrast ratio guidelines and retest to confirm you have met the accessibility requirements for this issue.</p>\n\n\n<p><strong>Stage 4: Understanding </strong>— You understand that the accessibility guidelines and tools are created with people in mind, and that code is secondary to all of that. One is the means, and the other is the end. In the color contrast example, you understand that people with low-vision or colorblindness need these color contrast changes in order to actually <em>see</em> the words on your web page.</p>\n\n\n<p>This is a bit of an oversimplification of the process. But I hope you get the gist — that there are different stages of digital accessibility knowledge and understanding. True beginners may not be to even stage one, but I am finding that group rarer and rarer these days. The word about digital accessibility seems to be out! Which is great; but that’s only the first hurdle. What I’m seeing now is that a lot of people stop at <em>Stage 2: Knowledge</em> or <em>Stage 3: Practice </em>— where you are aware of the digital accessibility guidelines, have some testing tools in your back pocket, and know how to fix some of the issues reported, but haven’t quite connected the dots to the humans they impact.</p>\n\n\n<p>From the standpoint of getting daily stuff done, stages two and three are okay stopping points. But what happens when the things you need to do are too complex for a quick fix, or you have no buy-in from your peers or management? I feel that once we get to <em>Stage 4: Understanding</em>, and really get <em>why</em> these kinds of changes are needed, people will be more motivated to make those changes regardless of the challenges involved. When you arrive at stage four, you have gone beyond knowing the basic rules, testing, and coding. You recognize that digital accessibility is not just a “nice to have” but a “must have” and it becomes about quality of life for <em>real</em> people. This is digital inclusion. This is something you can’t unsee, you can’t unlearn, and you can’t ignore.</p>\n\n\n<h2>Making digital accessibility a priority — not a requirement</h2>\n\n\n<p>In my role as an accessibility trainer, I like to kick-off each session with the question: “What are you hoping to learn today about digital accessibility?” I ask this question to establish a rapport with the audience and to understand where everyone is in their accessibility journey, but I am also evaluating the level of company and individual buy-in too. There is nothing worse than showing up to teach a group that does not care to be taught. If I hear the words “I am only here because I have to be” — I know it will be an uphill battle to get them anywhere close to <em>Stage 4: Understanding</em>, so I mentally regroup and aim for another stage.</p>\n\n\n<p>In my experience, when companies and their leaders say “Digital accessibility is a requirement,” nine times out of ten there is a motivating factor behind this sweeping declaration (for example, impending litigation, or at least the fear of it). When changes are framed as mandatory and packaged as directives from on high with little additional context, people can be resistant and will find excuses to fight or challenge the declaration, and any change can become an uphill battle. Calling something “mandatory” only speaks to <em>Stage 1: Awareness</em>.</p>\n\n\n<p>By swapping out one word from the original declaration and saying “Digital accessibility is a priority,” companies and their leaders have reframed the conversation with their employees. When changes are framed as “working towards a solution” and discussed openly and collaboratively, people feel like they are part of the process and are more open to embracing change. In the long run, embracing change becomes part of a company’s culture and leads to innovation (and, yes, inclusion) on all levels. Calling something a priority speaks to <em>Stage 4: Understanding</em>.</p>\n\n\n<p>Some of the excuses I often hear from clients for not prioritizing accessibility is that it is too difficult, too costly, and/or too time consuming — but is that really the case? In the same accessibility training, I lead an exercise where we look at a website with an accessibility testing tool and review any issues that came up. With the group’s help we plot out the “impact to user” versus the “remediation effort” on the part of the team. From group to group, while the plots are slightly different, one commonality is that close to 80% of the errors plotted fall into the quadrant of “simple to fix” for the team, but they also fall under “high impact” to the user. Based on this empirical data, I won’t buy the argument from clients who say that accessibility is too difficult and costly and time consuming anymore. It comes down to whether it’s a priority — for each individual and for the company as a whole.</p>\n\n\n<h2>What will your coding legacy be?</h2>\n\n\n<p>The <a href=\"https://en.wikipedia.org/wiki/Infinite_monkey_theorem\"><em>infinite monkey theorem</em></a> states that a monkey hitting keys at random on a typewriter for an infinite amount of time will eventually type any given text, such as the complete works of William Shakespeare. So by that same logic, a programmer hitting keys at random on a computer for an infinite amount of time will almost surely produce a website that is accessible. But where is the thought process? Where is the human element? While all the things we’ve already talked about — awareness, education, and prioritization of accessibility are important steps in making the digital world more inclusive to all — without <em>intent, </em>we are just going to keep randomly tapping away at our computers, repeating the same mistakes over and over again. The intent behind the code has to be part of the process, otherwise accessibility is just another task that has no meaning.</p>\n\n\n<p>Maybe I’m naive, but I’d like to think we’ve come to a point in our society where we want our work lives to have meaning. And that we don’t want to just hear about the positive change that is happening, but want to be part of the change. Digital accessibility is a place where this can happen! Not only does understanding and writing purpose-driven code help people with disabilities in the short-run, I believe strongly that is key to solving the overarching diversity issue in tech in the long-run. Developers who reach <em>Stage 4: Understanding</em>, and who prioritize accessible code because they understand it’s fundamentally about <em>people</em>, will also be the ones who help create and cultivate an inclusive environment where people from more diverse backgrounds are also prioritized and accepted in the tech world.</p>\n\n\n<p>Because when you strip away all the styles, all the mark-up, all the cool features from a website or app — what’s left? People. And honestly, the more I learn about digital accessibility, the more I realize it’s not about the code at all. Digital accessibility is rooted in the user; and, while I (<a href=\"https://a11yproject.com/follow/\">and countless others</a>) can certainly teach you how to write accessible code, and build you tools, patterns, and libraries to use, I realize we can’t teach you to care. That is a choice you have to make yourself. So think for a moment — what are you leaving the next generation of developers with all that inaccessible code you haven’t given much thought to? Is it the coding legacy you really want to leave? I challenge you to do better for my daughter, her peers, and for the countless others who are not fully represented in the tech community today.</p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/GeRjOSU3AQc\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/GeRjOSU3AQc/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/getting-to-the-heart-of-digital-accessibility/","type":"text/html"}],"crawled":1569829637486,"published":1569829637486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d812178ae:1c4b:90d684ff","keywords":["Application Development, Browsers, JavaScript"],"originId":"https://alistapart.com/article/responsible-javascript-part-2/","fingerprint":"9085fc74","title":"Responsible JavaScript: Part II","author":"by Jeremy Wagner","summary":{"content":"\n<p>You and the rest of the dev team lobbied enthusiastically for a total re-architecture of the company’s aging website. Your pleas were heard by management—even up to the C-suite—who gave the green light. Elated, you and the team started working with the design, copy, and IA teams. Before long, you were banging out new code.</p>\n\n\n<p>It started out innocently enough with an <code>npm install</code> here and an <code>npm install</code> there. Before you knew it, though, you were installing production dependencies like an undergrad doing keg stands without a care for the morning after.</p>\n\n\n<p>Then you launched.</p>\n\n\n<p>Unlike the aftermath of most copious boozings, the agony didn’t start the morning after. <em>Oh</em>, no. It came months later in the ghastly form of low-grade nausea and headache of product owners and middle management wondering why conversions and revenue were both down since the launch. It then hit a fever pitch when the CTO came back from a weekend at the cabin and wondered why the site loaded so slowly on their phone—if it indeed ever loaded at all.</p>\n\n\n<p>Everyone was happy. Now <em>no</em> one is happy. Welcome to your first JavaScript hangover.</p>\n\n\n<h2>It’s not your fault</h2>\n\n\n<p>When you’re grappling with a vicious hangover, “I told you so” would be a well-deserved, if fight-provoking, rebuke—assuming you could even fight in so sorry a state.</p>\n\n\n<p>When it comes to JavaScript hangovers, there’s plenty of blame to dole out. Pointing fingers is a waste of time, though. The landscape of the web today demands that we iterate faster than our competitors. This kind of pressure means we’re likely to take advantage of any means available to be as productive as possible. <em>That</em> means we’re more likely—but not necessarily doomed—to build apps with more overhead, and possibly use patterns that can hurt performance and accessibility.</p>\n\n\n<p>Web development isn't easy. It’s a long slog we rarely get right on the first try. The best part of working on the web, however, is that we don’t <em>have</em> to get it perfect at the start. We can make improvements after the fact, and that’s just what the second installment of <a href=\"https://alistapart.com/article/responsible-javascript-part-1/\">this series</a> is here for. Perfection is a long ways off. For now, let’s take the edge off of that JavaScript hangover by improving your site’s, er, <em>scriptuation</em> in the short term.</p>\n\n\n<h2>Round up the usual suspects</h2>\n\n\n<p>It might seem rote, but it’s worth going through the list of basic optimizations. It’s not uncommon for large development teams—particularly those that work across many repositories or don’t use optimized boilerplate—to overlook them.</p>\n\n\n<h3>Shake those trees</h3>\n\n\n<p>First, make sure your toolchain is configured to perform <a href=\"https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking\"><em>tree shaking</em></a>. If tree shaking is new to you, I wrote <a href=\"https://developers.google.com/web/fundamentals/performance/optimizing-javascript/tree-shaking/\">a guide on it last year</a> you can consult. The short of it is that tree shaking is a process in which unused exports in your codebase don’t get packaged up in your production bundles.</p>\n\n\n<p>Tree shaking is available out of the box with modern bundlers such as <a href=\"https://webpack.js.org/\">webpack</a>, <a href=\"https://rollupjs.org/\">Rollup</a>, or <a href=\"https://parceljs.org/\">Parcel</a>. <a href=\"https://gruntjs.com/\">Grunt</a> or <a href=\"https://gulpjs.com/\">gulp</a>—which are not <em>bundlers</em>, but rather <em>task runners</em>—won’t do this for you. A task runner doesn’t build a <a href=\"https://webpack.js.org/concepts/dependency-graph/\">dependency graph</a> like a bundler does. Rather, they perform discrete tasks on the files you feed to them with any number of plugins. Task runners <em>can</em> be extended with plugins to use bundlers to process JavaScript. If extending task runners in this way is problematic for you, you’ll likely need to manually audit and remove unused code.</p>\n\n\n<p>For tree shaking to be effective, the following must be true:</p>\n\n\n<ol><li>Your app logic and the packages you install in your project must be authored as <a href=\"https://ponyfoo.com/articles/es6-modules-in-depth\">ES6 modules</a>. Tree shaking <a href=\"https://en.wikipedia.org/wiki/CommonJS\">CommonJS</a> modules isn’t practically possible.</li><li>Your bundler must <em>not</em> transform ES6 modules into another module format at build time. If this happens in a toolchain that uses Babel, <a href=\"https://babeljs.io/docs/en/babel-preset-env\">@babel/preset-env configuration</a> must specify <a href=\"https://babeljs.io/docs/en/babel-preset-env#modules\"><code>modules: false</code></a> to prevent ES6 code from being converted to CommonJS.</li></ol>\n\n\n<p>On the off chance tree shaking isn’t occurring during your build, getting it to work may help. Of course, its effectiveness varies on a case-by-case basis. It also depends on whether the modules you import introduce <a href=\"https://en.wikipedia.org/wiki/Side_effect_(computer_science)\">side effects</a>, which may influence a bundler’s ability to shake unused exports.</p>\n\n\n<h3>Split that code</h3>\n\n\n<p>Chances are good that you’re employing some form of <a href=\"https://developers.google.com/web/fundamentals/performance/optimizing-javascript/code-splitting/\">code splitting</a>, but it’s worth re-evaluating how you’re doing it. No matter <em>how</em> you’re splitting code, there are two questions that are always worth asking yourself:</p>\n\n\n<ol><li>Are you <a href=\"https://developers.google.com/web/fundamentals/performance/optimizing-javascript/code-splitting/#removing_duplicate_code\">deduplicating common code</a> between <a href=\"https://webpack.js.org/concepts/entry-points/\">entry points</a>?</li><li>Are you lazy loading all the functionality you reasonably can with <a href=\"https://developers.google.com/web/updates/2017/11/dynamic-import\">dynamic <code>import()</code></a>?</li></ol>\n\n\n<p>These are important because reducing redundant code is essential to performance. Lazy loading functionality also improves performance by lowering the initial JavaScript footprint on a given page. On the redundancy front, using an analysis tool such as <a href=\"https://github.com/samccone/bundle-buddy\">Bundle Buddy</a> can help you find out if you have a problem.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"The Bundle Buddy utility demonstrating how much code is shared between bundles of JavaScript.\" src=\"https://alistapart.com/wp-content/uploads/2019/06/figure-6-2x.png?w=435\" class=\"wp-image-7171674\"><figcaption>Bundle Buddy can examine your webpack compilation statistics and determine how much code is shared between your bundles.</figcaption></figure>\n\n\n<p>Where lazy loading is concerned, it can be a bit difficult to know where to start looking for opportunities. When I look for opportunities in existing projects, I’ll search for user interaction points throughout the codebase, such as click and keyboard events, and similar candidates. Any code that requires a user interaction to run is a potentially good candidate for dynamic <code>import()</code>.</p>\n\n\n<p>Of course, loading scripts on demand brings the possibility that interactivity could be noticeably delayed, as the script necessary for the interaction must be downloaded first. If data usage is not a concern, consider using the <a href=\"https://www.w3.org/TR/resource-hints/#prefetch\"><code>rel=prefetch</code> resource hint</a> to load such scripts at a low priority that won’t contend for bandwidth against critical resources. <a href=\"https://caniuse.com/#feat=link-rel-prefetch\">Support for<strong> </strong><code>rel=prefetch</code></a> is good, but nothing will break if it’s unsupported, as such browsers will ignore markup they doesn’t understand.</p>\n\n\n<h3>Externalize third-party hosted code</h3>\n\n\n<p>Ideally, you should self-host as many of your site’s dependencies as possible. If for some reason you <em>must</em> load dependencies from a third party, <a href=\"https://webpack.js.org/configuration/externals/\">mark them as externals</a> in your bundler’s configuration. Failing to do so could mean your website’s visitors will download both locally hosted code <em>and</em> the same code from a third party.</p>\n\n\n<p>Let’s look at a hypothetical situation where this could hurt you: say that your site loads Lodash from a public CDN. You've also installed Lodash in your project for local development. However, if you fail to mark Lodash as external, your production code will end up loading a third party copy of it <em>in addition</em> to the bundled, locally hosted copy.</p>\n\n\n<p>This may <em>seem</em> like common knowledge if you know your way around bundlers, but I’ve seen it get overlooked. It’s worth your time to check twice.</p>\n\n\n<p>If you aren’t convinced to self-host your third-party dependencies, then consider adding <a href=\"https://css-tricks.com/prefetching-preloading-prebrowsing/#article-header-id-0\"><code>dns-prefetch</code></a>, <a href=\"https://css-tricks.com/prefetching-preloading-prebrowsing/#article-header-id-1\"><code>preconnect</code></a>, or possibly even <a href=\"https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/\"><code>preload</code></a> hints for them. Doing so can lower your site’s <a href=\"https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive\">Time to Interactive</a> and—if JavaScript is critical to rendering content—your site’s <a href=\"https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index\">Speed Index</a>.</p>\n\n\n<h2>Smaller alternatives for less overhead</h2>\n\n\n<p><a href=\"https://nodejs.org/en/knowledge/getting-started/what-is-node-core-verus-userland/\">Userland JavaScript</a> is like an obscenely massive candy store, and we as developers are awed by the sheer amount of open source offerings. Frameworks and libraries allow us to extend our applications to quickly do all sorts of stuff that would otherwise take loads of time and effort.</p>\n\n\n<p>While I personally prefer to aggressively minimize the use of client-side frameworks and libraries in my projects, their value is compelling. Yet, we <em>do</em> have a responsibility to be a bit hawkish when it comes to what we install. When we’ve already built and shipped something that depends on a slew of installed code to run, we’ve accepted a baseline cost that only the maintainers of that code can practically address. Right?</p>\n\n\n<p>Maybe, but then again, maybe not. It depends on the dependencies used. For instance, React is extremely popular, but <a href=\"https://preactjs.com/\">Preact</a> is an <a href=\"https://bundlephobia.com/result?p=preact@8.4.2\">ultra-small</a> alternative that largely shares the same API and retains compatibility with many React add-ons. <a href=\"https://moment.github.io/luxon/\">Luxon</a> and <a href=\"https://date-fns.org/\">date-fns</a> are much more compact alternatives to <a href=\"https://momentjs.com/\">moment.js</a>, which is <a href=\"https://bundlephobia.com/result?p=moment\">not exactly tiny</a>.</p>\n\n\n<p>Libraries such as <a href=\"https://lodash.com/\">Lodash</a> offer many useful methods. Yet, some of them are easily replaceable with native ES6. <a href=\"https://lodash.com/docs/4.17.11#compact\">Lodash’s <code>compact</code> method</a>, for example, is replaceable with the <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter\"><code>filter</code> array method</a>. <a href=\"https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_chunk\">Many more can be replaced</a> without much effort, and without the need for pulling in a large utility library.</p>\n\n\n<p>Whatever your preferred tools are, the idea is the same: do some research to see if there are smaller alternatives, or if native language features can do the trick. You may be surprised at how little effort it may take you to seriously reduce your app’s overhead.</p>\n\n\n<h2>Differentially serve your scripts</h2>\n\n\n<p>There’s a good chance you’re using Babel in your toolchain to transform your ES6 source into code that can run on older browsers. Does this mean we’re doomed to serve giant bundles even to browsers that don’t need them, until the older browsers disappear altogether? <a href=\"https://philipwalton.com/articles/deploying-es2015-code-in-production-today/\">Of course not</a>! Differential serving helps us get around this by generating two different builds of your ES6 source:</p>\n\n\n<ul><li>Bundle one, which contains all the transforms and polyfills required for your site to work on older browsers. You’re probably already serving this bundle right now.</li><li>Bundle two, which contains <em>little to none</em> of the transforms and polyfills because it targets modern browsers. This is the bundle you’re probably not serving—at least not <em>yet</em>.</li></ul>\n\n\n<p>Achieving this is a bit involved. <a href=\"https://calendar.perfplanet.com/2018/doing-differential-serving-in-2019/\">I’ve written a guide on one way you can do it</a>, so there’s no need for a deep dive here. The long and short of it is that you can modify your build configuration to generate an additional but smaller version of your site’s JavaScript code, and serve it only to modern browsers. The best part is that these are savings you can achieve without sacrificing any features or functionality you already offer. Depending on your application code, the savings could be quite significant.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"\" src=\"https://alistapart.com/wp-content/uploads/2019/06/diff-serving-bundles.jpg?w=960\" class=\"wp-image-7171677\"><figcaption>A webpack-bundle-analyzer analysis of a project's legacy bundle (left) versus one for a modern bundle (right). <a rel=\"noopener noreferrer\" href=\"https://alistapart.com/wp-content/uploads/2019/06/diff-serving-bundles.jpg\" target=\"_blank\">View full-sized image</a>.</figcaption></figure>\n\n\n<p>The <a href=\"https://developers.google.com/web/fundamentals/primers/modules#browser\">simplest pattern</a> for serving these bundles to their respective platforms is brief. It also works a treat in modern browsers:</p>\n\n\n<pre class=\"wp-block-code\"><code><!-- Modern browsers load this file: -->\n<script type="module" src="/js/app.mjs"></script>\n<!-- Legacy browsers load this file: -->\n<script defer nomodule src="/js/app.js"></script></code></pre>\n\n\n<p>Unfortunately, there’s a caveat with this pattern: legacy browsers like IE 11—and even relatively modern ones such as Edge versions 15 through 18—will download <em>both</em> bundles. If this is an acceptable trade-off for you, then worry no further.</p>\n\n\n<p>On the other hand, you'll need a workaround if you’re concerned about <a href=\"https://gist.github.com/jakub-g/5fc11af85a061ca29cc84892f1059fec\">the performance implications of older browsers downloading both sets of bundles</a>. Here’s one potential solution that uses script injection (instead of the <code>script</code> tags above) to avoid double downloads on affected browsers:</p>\n\n\n<pre class=\"wp-block-code\"><code>var scriptEl = document.createElement("script");\n\nif ("noModule" in scriptEl) {\n // Set up modern script\n scriptEl.src = "/js/app.mjs";\n scriptEl.type = "module";\n} else {\n // Set up legacy script\n scriptEl.src = "/js/app.js";\n scriptEl.defer = true; // type="module" defers by default, so set it here.\n}\n\n// Inject!\ndocument.body.appendChild(scriptEl);</code></pre>\n\n\n<p>This script infers that if a browser supports <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nomodule\">the <code>nomodule</code> attribute</a> in the <code>script</code> element, it understands <code>type="module"</code>. This ensures that legacy browsers only get legacy scripts and modern browsers only get modern ones. Be warned, though, that dynamically injected scripts load asynchronously by default, so set the <code><a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async\">async</a></code> attribute to <code>false</code> if dependency order is crucial.</p>\n\n\n<h2>Transpile less</h2>\n\n\n<p>I’m not here to trash Babel. It’s indispensable, but lordy, it adds a <em>lot</em> of extra stuff without your ever knowing. It pays to peek under the hood to see what it’s up to. Some minor changes in your coding habits can have a positive impact on what Babel spits out.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"\" src=\"https://alistapart.com/wp-content/uploads/2019/06/twete.png?w=306\" class=\"wp-image-7171676\"><figcaption><a href=\"https://twitter.com/_developit/status/1110229993999777793\">https://twitter.com/_developit/status/1110229993999777793</a></figcaption></figure>\n\n\n<p>To wit: <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters\">default parameters</a> are a <em>very</em> handy ES6 feature you probably already use:</p>\n\n\n<pre class=\"wp-block-code\"><code>function logger(message, level = "log") {\n console[level](message);\n}</code></pre>\n\n\n<p>The thing to pay attention to here is the <code>level</code> parameter, which has a default of “log.” This means if we want to invoke <code>console.log</code> with this wrapper function, we don’t need to specify <code>level</code>. Great, right? Except when Babel transforms this function, the output looks like this:</p>\n\n\n<pre class=\"wp-block-code\"><code>function logger(message) {\n var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "log";\n\n console[level](message);\n}</code></pre>\n\n\n<p>This is an example of how, despite our best intentions, developer conveniences can backfire. What was a handful of bytes in our source has now been transformed into <em>much</em> larger in our production code. Uglification can’t do much about it either, as arguments can’t be reduced. Oh, and if you think <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters\">rest parameters</a> might be a worthy antidote, Babel’s transforms for them are even bulkier:</p>\n\n\n<pre class=\"wp-block-code\"><code>// Source\nfunction logger(...args) {\n const [level, message] = args;\n\n console[level](message);\n}\n\n// Babel output\nfunction logger() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n const level = args[0],\n message = args[1];\n console[level](message);\n}</code></pre>\n\n\n<p>Worse yet, Babel transforms this code even for projects with a <a href=\"https://babeljs.io/docs/en/babel-preset-env\">@babel/preset-env</a> configuration <a href=\"https://babeljs.io/docs/en/babel-preset-env#targetsesmodules\">targeting modern browsers</a>, meaning the modern bundles in your differentially served JavaScript will be affected too! You <em>could</em> use <a href=\"https://babeljs.io/docs/en/babel-preset-env#loose\">loose transforms</a> to soften the blow—and that’s a fine idea, as they’re often quite a bit smaller than their more spec-compliant counterparts—<a href=\"http://2ality.com/2015/12/babel6-loose-mode.html\">but enabling loose transforms can cause issues if you remove Babel from your build pipeline later on</a>.</p>\n\n\n<p>Regardless of whether you decide to enable loose transforms, here’s one way to cut the cruft of transpiled default parameters:</p>\n\n\n<pre class=\"wp-block-code\"><code><pre class=\"wp-block-code\"><code>// Babel won't touch this\nfunction logger(message, level) {\n console[level || "log"](message);\n}</code></pre></code></pre>\n\n\n<p>Of course, default parameters aren’t the <em>only</em> feature to be wary of. For example, <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax\">spread syntax</a> gets transformed, as do <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions\">arrow functions</a> and a whole host of <a href=\"https://babeljs.io/repl/#?babili=false&browsers=%3E%200.25%25%2C%20ie%20%3E%2010%2C%20Firefox%20ESR%2C%20not%20dead&build=&builtIns=false&spec=false&loose=false&code_lz=MYGwhgzhAECyYDsCuAzMwAuSBOBTb0A3gFDTTAD2CEG2SmFBAFALaKrpZ7YA05FSBLQCeASiKky0DAAsAlhAB0bZGkw580ALzQVHddwDcksrIWLKgkdv5Xsw42QC-xF8VCQYAYTAFcADwxcBAATGHhVTg0CEjJKalp6DEZoVgoQ3BA-YVxfPkoQRj5FEt8AcwhxWKkIJAAHfCYSxXLKxykTaXklFnTMm16MkHbTbsUc3xsJ7BGu8wKUnQWZyRcySTw6sDkhVOWqzrMlZZtl9pc3eJpoNBY5EGEfAh0EXAB3aCemACIfFntvnxvgAmAAMoOBgOg3wAMoJJrAFBg4LgMGAQCA5MAod8ACoUYQUNE4gBSYC2CG-okMQA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=true&timeTravel=false&sourceType=module&lineWrap=true&presets=env&prettier=false&targets=&version=7.4.5&externalPlugins=\">other stuff</a>.</p>\n\n\n<p>If you don’t want to avoid these features altogether, you have a couple ways of reducing their impact:</p>\n\n\n<ol><li>If you’re authoring a library, consider using <a href=\"https://babeljs.io/docs/en/babel-runtime\">@babel/runtime</a> in concert with <a href=\"https://babeljs.io/docs/en/babel-plugin-transform-runtime\">@babel/plugin-transform-runtime</a> to deduplicate the helper functions Babel puts into your code.</li><li>For polyfilled features in apps, you can include them selectively with <a href=\"https://babeljs.io/docs/en/babel-polyfill\">@babel/polyfill</a> via <a href=\"https://babeljs.io/docs/en/babel-preset-env#usebuiltins\">@babel/preset-env’s useBuiltIns: "usage"</a> option.</li></ol>\n\n\n<p>This is solely my opinion, but I believe the best choice is to avoid transpilation altogether in bundles generated for modern browsers. That’s not always possible, especially if you use <a href=\"https://reactjs.org/docs/introducing-jsx.html\">JSX</a>, which must be transformed for <em>all</em> browsers, or if you’re using bleeding edge language features that aren’t widely supported. In the latter case, it might be worth asking if those features are really necessary to deliver a good user experience (they rarely are). If you arrive at the conclusion that Babel must be a part of your toolchain, then it’s worth peeking under the hood from time to time to catch suboptimal stuff Babel might be doing that you can improve on.</p>\n\n\n<h2>Improvement is not a race</h2>\n\n\n<p>As you massage your temples wondering when this horrid JavaScript hangover is going to lift, understand that it’s precisely when we rush to get something out there as fast as we possibly can that the user experience can suffer. As the web development community obsesses on iterating faster in the name of competition, it’s worth your time to <a href=\"https://en.wikipedia.org/wiki/Thinking,_Fast_and_Slow\"><em>slow down a little bit</em></a>. You’ll find that by doing so, you may not be iterating as fast as your competitors, but <em>your product</em> will be <em>faster</em> than theirs.</p>\n\n\n<p>As you take these suggestions and apply them to your codebase, know that progress doesn’t spontaneously happen overnight. Web development is a job. The truly impactful work is done when we’re thoughtful and dedicated to the craft for the long haul. Focus on steady improvements. Measure, test, repeat, and your site’s user experience will improve, and you’ll get faster bit by bit over time.</p>\n\n\n<p><em>Special thanks to </em><a href=\"https://twitter.com/_developit\"><em>Jason Miller</em></a><em> for tech editing this piece. Jason is the creator and one of the many maintainers of </em><a href=\"https://preactjs.com/\"><em>Preact</em></a><em>, a vastly smaller alternative to React with the same API. If you use Preact, </em><a href=\"https://opencollective.com/preact\"><em>please consider supporting Preact through Open Collective</em></a><em>.</em></p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/66zevQGQ6YM\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/66zevQGQ6YM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/responsible-javascript-part-2/","type":"text/html"}],"crawled":1569829517486,"published":1569829517486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811fa3ee:1c4a:90d684ff","keywords":["Business, Career"],"originId":"https://alistapart.com/article/resilient-management-excerpt/","fingerprint":"4c7dfe28","title":"Resilient Management, An Excerpt","author":"by Lara Hogan","summary":{"content":"\n<p>In <a href=\"https://en.wikipedia.org/wiki/Tuckman%27s_stages_of_group_development\">Tuckman’s Stages of Group Development</a>, the Storming stage happens as a group begins to figure out how to work together. Previously, each person had been doing their own thing as individuals, so necessarily a few things need to be ironed out: how to collaborate, how to hit goals, how to determine priorities. Of <em>course </em>there may be some friction here! </p>\n\n\n<p>But even if your team doesn’t noticeably demonstrate this kind of internal Storming as they begin to gel, there might be some outside factors at play in your work environment that create friction. During times of team scaling and organizational change—the water we in the web industry are often swimming in—managers are responsible for things like strategy-setting, aligning their team’s work to company objectives, and unblocking the team as they ship their work. </p>\n\n\n<p>In addition to these business-context responsibilities, managers need to be able to help their teammates navigate this storm by helping them grow in their roles and support the team’s overall progress. If you and your teammates don’t adapt and evolve in your roles, it’s unlikely that your team will move out of the Storming stage and into the Norming stage of team dynamics. </p>\n\n\n<p>To spur this course-correction and growth in your teammates, you’ll end up wearing four different hats: </p>\n\n\n<ul><li><strong>Mentoring: </strong>lending advice and helping to problem solve based on your own experience. </li><li><strong>Coaching: </strong>asking open questions to help your teammate reflect and introspect, rather than sharing your own opinions or quickly problem solving.</li><li><strong>Sponsoring: </strong>finding opportunities for your teammate to level up, take on new leadership roles, and get promoted.</li><li><strong>Delivering feedback: </strong>observing behavior that is or isn’t aligned to what the team needs to be doing and sharing those observations, along with praise or suggestions. </li></ul>\n\n\n<p>Let’s dive in to how to choose, and when to use, each of\nthese skills as you grow your teammates, and then talk about\nwhat it looks like when teammates support the overarching\ndirection of the team.\n</p>\n\n\n<h2>Mentoring</h2>\n\n\n<p>When I talk to managers, I find that the vast majority have their\nmentor hats on ninety percent of the time when they’re working with their\nteammates. It’s natural! </p>\n\n\n<p>In mentoring mode, we’re doling out advice, sharing our perspective,\nand helping someone else problem solve based on that information. Our personal\nexperiences are often what we can talk most confidently about! For this reason,\nmentorship mode can feel really good and effective for the mentor. Having that\nmentor hat on can help the other person overcome a roadblock or know which next\nsteps to take, while avoiding drastic errors that they wouldn’t have seen\ncoming otherwise. </p>\n\n\n<p>As a mentor, it’s your responsibility to give advice that’s current and sensitive to the changing dialog happening in our industry. Advice that might work for one person (“Be louder in meetings!” or “Ask your boss for a raise!”) may undermine someone else, because members of underrepresented groups are unconsciously assessed and treated differently. For example, <a href=\"https://hbr.org/2018/05/the-different-words-we-use-to-describe-male-and-female-leaders\">research has shown</a> that “when women are collaborative and communal, they are not perceived as competent—but when they emphasize their competence, they’re seen as cold and unlikable, in a classic ‘double bind’”. </p>\n\n\n<p>If you are not a member of a marginalized group, and you have a\nmentee who <em>is</em>, please be a responsible mentor! Try to be aware of the\nway members of underrepresented groups are perceived, and the unconscious bias\nthat might be at play in your mentee’s work environment. When you have your\nmentor hat on, do lots of gut checking to make sure that your advice is going\nto be helpful in practice for your mentee.</p>\n\n\n<p>Mentoring is ideal when the mentee is new to their role or to the\norganization; they need to learn the ropes from someone who has firsthand\nexperience. It’s also ideal when your teammate is working on a problem and has\ntried out a few different approaches, but still feels stumped; this is why\npractices like pair coding can help folks learn new things. </p>\n\n\n<p>As mentors, we want our mentees to reach beyond us, because our mentees’ success is ultimately <em>our</em> success. Mentorship relationships evolve over time, because each party is growing. Imaginative, innovative ideas often come from people who have never seen a particular challenge before, so if your mentee comes up with a creative solution on their own that you wouldn’t have thought of, be excited for them—don’t just focus on the ways that <em>you’ve </em>done it or seen it done before.</p>\n\n\n<p>Managers often default to mentoring mode because it feels like the fastest way to solve a problem, but it falls short in helping your teammate connect their <em>own</em> dots. For that, we’ll look to coaching.</p>\n\n\n<h2>Coaching</h2>\n\n\n<p>In mentoring mode, you’re focused on both the problem and the solution. You’ll share what you as the mentor would do or have done in this situation. This means you’re more focused on yourself, and less on the person who is sitting in front of you.</p>\n\n\n<p>In coaching mode—an extremely powerful but often underutilized mode—you’re doing two primary things: </p>\n\n\n<ol><li><strong>Asking open questions </strong>to help the other person explore more of the shape of the topic, rather than staying at the surface level.</li><li><strong>Reflecting</strong>, which is like holding up a mirror for the other person and describing what you see or hear, or asking them to reflect for themselves. </li></ol>\n\n\n<p> These two tools will help you become your teammate’s fiercest champion. </p>\n\n\n<h3>Open Questions</h3>\n\n\n<p>“Closed” questions can only be answered with <em>yes</em> or <em>no</em>.\nOpen questions often start with <em>who</em>, <em>what</em>, <em>when</em>, <em>where</em>,\n<em>why</em>, and <em>how</em>. But the best open questions are about the\nproblem, not the solution. Questions that start with <em>why</em> tend to make\nthe other person feel judged, and questions that start with <em>how</em> tend\nto go into problem solving mode—both of which we want to avoid while in\ncoaching mode. </p>\n\n\n<p>However, <em>what</em> questions can be authentically curious!\nWhen someone comes to you with a challenge, try asking questions like:</p>\n\n\n<ul><li>What’s most important to you about it?</li><li>What’s holding you back?</li><li>What does success look like?</li></ul>\n\n\n<p>Let’s say my teammate comes to me and says they’re ready for a\npromotion. Open questions could help this teammate explore what this promotion\nmeans and demonstrate to me what introspection they’ve already done around it.\nRather than telling them what I think is necessary for them to be promoted, I\ncould instead open up this conversation by asking them:</p>\n\n\n<ul><li>What would you be able to do in the new level\nthat you can’t do in your current one?</li><li>What skills are required in the new level? What\nare some ways that you’ve honed those skills?</li><li>Who are the people already at that level that\nyou want to emulate? What about them do you want to emulate?</li></ul>\n\n\n<p> Their answers would give me a place to start coaching. These questions might push my teammate to think more deeply about what this promotion means, rather than allowing them to stay surface level and believe that a promotion is about checking off a lot of boxes on a list. Their answers might also open <em>my</em> eyes to things that I hadn’t seen before, like a piece of work that my teammate had accomplished that made a huge impact. But most important, going into coaching mode would start a two-way conversation with this teammate, which would help make an otherwise tricky conversation feel more like a shared exploration.</p>\n\n\n<p>Open\nquestions, asked from a place of genuine curiosity, help people feel seen and\nheard. However, if the way you ask your questions comes across as judgy or like\nyou’ve already made some assumptions, then your questions aren’t truly open\n(and your teammate can smell this on you!). Practice your intonation to make\nsure your open questions are <em>actually</em> curious and open. </p>\n\n\n<p>By the way, forming lots of open questions (instead of problem solving questions, or giving advice) is tremendously hard for most people. Don’t worry if you don’t get the hang of it at first; it takes a lot of practice and intention over time to default to coaching mode rather than mentoring mode. I promise, it’s worth it.</p>\n\n\n<h3>Reflections</h3>\n\n\n<p>Just like open questions, reflections help the other person feel\nseen and heard, and to explore the topic more deeply. </p>\n\n\n<p>It’s almost comical how rarely we get the sense that the person\nwe’re talking to is actively listening to us, or focusing entirely on helping\nus connect our own dots. Help your teammates reflect by repeating back to them\nwhat you hear them say, as in:</p>\n\n\n<ul><li>“What I’m hearing you say is that you’re\nfrustrated with how this project is going. Is that right?”</li><li>“What I know to be true about you is how deeply\nyou care about your teammates’ feelings.”</li></ul>\n\n\n<p>In each of these\nexamples, you are holding up a metaphorical mirror to your teammate, and\nhelping them look into it. You can coach them to reflect, too:</p>\n\n\n<ul><li>“How does this new architecture project map to\nyour goals?”</li><li>“Let’s reflect on where you were this time last\nyear and how far you’ve come.”</li></ul>\n\n\n<p>Occasionally, you might get a reflection wrong; this gives the\nother person an opportunity to realize something new about their topic, like\nthe words they’re choosing aren’t quite right, or there’s another underlying\nissue that should be explored. So don’t be worried about giving a bad\nreflection; reflecting back what you’re hearing will still help your teammate.</p>\n\n\n<p>The act of reflecting can help the other person do a gut check to\nmake sure they’re approaching their topic holistically. Sometimes the act of\nreflection forces (encourages?) the other person to do some really hard work: <em>introspection</em>.\nIntrospection creates an opportunity for them to realize new aspects of the\nproblem, options they can choose from, or deeper meanings that hadn’t occurred\nto them before—which often ends up being a nice shortcut to the right solution.\nOr, even better, the right problem statement. </p>\n\n\n<p>When you have your coaching hat on, you don’t need to have all the answers, or even fully understand the problem that your teammate is wrestling with; you’re just there as a mirror and as a question-asker, to help prompt the other person to think deeply and come to some new, interesting conclusions. Frankly, it may not feel all that effective when you’re in coaching mode, but I promise, coaching can generate <em>way more growth</em> for that other person than just giving them advice or sharing your perspective. </p>\n\n\n<p>Choose coaching when you’re looking to help someone (especially an emerging leader) hone their strategic thinking skills, grow their leadership aptitude, and craft their own path forward. Coaching mode is all about helping your teammate develop their own brain wrinkles, rather than telling them how you would do something. The introspection and creativity it inspires create deeper and longer-lasting growth. </p>\n\n\n<h2>Sponsoring</h2>\n\n\n<p>While you wear the mentoring and coaching hats around your\nteammates, the sponsor hat is more often worn when they’re <em>not</em> around,\nlike when you’re in a 1:1 with your manager, a sprint planning meeting, or\nanother environment where someone’s work might be recognized. You might hear\nabout an upcoming project to acquire a new audience and recommend that a\nbudding user researcher take it on, or you’ll suggest to an All Hands meeting\norganizer that a junior designer should give a talk about a new pattern they’ve\nintroduced to the style guide. </p>\n\n\n<p>Sponsorship is all about feeling <em>on the hook</em> for getting\nsomeone to the next level. As someone’s sponsor, you’ll put their name in the\nring for opportunities that will get them the experience and visibility\nnecessary to grow in their role and at the organization. You will put your\npersonal reputation on the line on behalf of the person you’re sponsoring, to\nhelp get them visible and developmental assignments. It’s a powerful tool, and\nthe one most effective at helping someone get to the next level (way more so\nthan mentoring or coaching!). </p>\n\n\n<p>The Center for Talent Innovation routinely <a href=\"https://www.talentinnovation.org/_private/assets/TheSponsorDividend_KeyFindingsCombined-CTI.pdf\">measures the career benefits of sponsorship</a> (PDF). Their studies have found that when someone has a sponsor, they are way more likely to have access to career-launching work. They’re also more likely to take actions that lead to even <em>more</em> growth and opportunities, like asking their manager for a stretch assignment or a raise. </p>\n\n\n<p>When you’re in sponsorship mode, think about the different\nopportunities you have to offer up someone’s name. This might look like:</p>\n\n\n<ul><li>giving visible/public recognition (company\n“shout outs,” having them present a project demo, thanking them in a launch\nemail, giving someone’s manager feedback about their good work);</li><li>assigning stretch tasks and projects that are <em>just\nbeyond</em> their current skill set, to help them grow and have supporting\nevidence for a future promotion; or</li><li>opening the door for them to write blog posts,\ngive company or conference talks, or contribute open-source work.</li></ul>\n\n\n<p>Remember that members of underrepresented groups are typically <a href=\"https://hbr.org/2010/08/women-are-over-mentored-but-un\">over-mentored, but under-sponsored</a>. These individuals get <em>lots</em> of advice (often unsolicited), coffee outings, and offers to teach them new skills. But it’s much rarer for them to see support that looks like sponsorship. </p>\n\n\n<p>This isn’t because sponsors intentionally ignore marginalized folks, but because of <em>in-group bias</em>. Because of how our brains (and social networks) work, the people we’re closest to tend to look mostly like us—and we draw from that same pool when we nominate people for projects, for promotions, and for hires. Until I started learning about bias in the workplace, most of the people I sponsored were white, cisgender women, like myself. Since then, I’ve actively worked to sponsor people of color and nonbinary people. It takes effort and intention to combat our default behaviors—but I know you can do it!</p>\n\n\n<p>Take a look at the daily communications you participate in: your work chat logs, the conversations you have with others, the process for figuring out who should fix a bug or work on a new project, and the processes for making your teams’ work visible (like an architecture review, code review, launch calendar, etc.). You’ll be surprised how many moments there are to sponsor someone throughout an average day. Please put in the time and intention to ensure that you’re sponsoring members of underrepresented groups, too. </p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/OONUldza8XM\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/OONUldza8XM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/resilient-management-excerpt/","type":"text/html"}],"crawled":1569829397486,"published":1569829397486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811dcf2e:1c49:90d684ff","keywords":["Design"],"originId":"https://alistapart.com/article/daily-ethical-design/","fingerprint":"c00b5c74","title":"Daily Ethical Design","author":"by Lennart Overkamp","summary":{"content":"Suddenly, I realized that the people next to me might be severely impacted by my work.\nI was having a quick lunch in the airport. A group of flight attendants sat down at the table next to me and started to prepare for their flight. For a while now, our design team had been working on futuristic concepts for the operations control center of these flight attendants’ airline, pushing ourselves to come up with innovative solutions enabled by the newest technologies. As the control center deals with all activities around flying planes, our concepts touched upon everything and everyone within the airline.\nHow was I to know what the impact of my work would be on the lives of these flight attendants? And what about the lives of all the other people working at the airline?\nIdeally, we would have talked to all the types of employees in the company and tested our concepts with them. But, of course, there was no budget (or time) allocated to do so, not to mention we faced the hurdle of convincing (internal) stakeholders of the need.\nNot for the first time, I felt frustrated: practical, real-world constraints prevented me from assessing the impact and quality of my work. They prevented me from properly conducting <i>ethical design</i>.\n<h2>What is ethical design?</h2>\nRight, good question. A very comprehensive definition of ethical design can be found at <a rel=\"noopener noreferrer\" href=\"https://www.encyclopedia.com/science/encyclopedias-almanacs-transcripts-and-maps/design-ethics\" target=\"_blank\">Encyclopedia.com</a>:\n<figure>\n<blockquote>Design ethics concerns moral behavior and responsible choices in the practice of design. It guides how designers work with clients, colleagues, and the end users of products, how they conduct the design process, how they determine the features of products, and how they assess the ethical significance or moral worth of the products that result from the activity of designing.</blockquote>\n</figure>\nIn other words, ethical design is about the “goodness”—in terms of benefit to individuals, society, and the world—of how we collaborate, how we practice our work, and what we create. There’s never a black-and-white answer for whether design is good or bad, yet there are a number of areas for designers to focus on when considering ethics.\n<h3>Usability</h3>\nNowadays usability has conquered a spot as a basic requirement for each interface; unusable products are considered design failures. And rightly so; we have a moral obligation as designers to create products that are intuitive, safe, and free from possibly life-threatening errors. We were all reminded of usability’s importance by last year’s <a rel=\"noopener noreferrer\" href=\"https://www.nngroup.com/articles/error-prevention/\" target=\"_blank\">accidental nuclear strike warning</a> in Hawaii. What if, instead of a false-positive, the operator had broadcasted a false-negative?\n<h3>Accessibility</h3>\nLike usability, inclusive design has become a standard item in the requirement list of many designers and companies. (I will never forget that time someone tried to use our website with a screen reader—and got absolutely stuck at the cookie message.) Accessible design benefits all, as it attempts to cover as many needs and capabilities as possible. Yet for each design project, there are still a lot of tricky questions to answer. Who gets to benefit from our solutions? Who is (un)intentionally left out? Who falls outside the “target customer segment”?\n<h3>Privacy</h3>\nAnother day, another Facebook privacy scandal. As we’re progressing into the Data Age, the topic of privacy has become almost synonymous with design ethics. There’s a reason why more and more people use <a rel=\"noopener noreferrer\" href=\"https://duckduckgo.com/traffic\" target=\"_blank\">DuckDuckGo</a> as an alternative search engine to Google. Corporations have access to an abundance of personal information about consumers, and as designers we have the privilege—and responsibility—of using this information to shape products and services. We have to consider how much information is strictly necessary and how much people are willing to give up in exchange for services. And how can we make people aware of the potential risks <a rel=\"noopener noreferrer\" href=\"https://link.springer.com/article/10.1007/s10603-018-9399-7\" target=\"_blank\">without overloading them</a>?\n<h3>User involvement</h3>\nOverlapping largely with privacy, this focus area is about how we deal with our users and what we do with the data that we collect from them. IDEO has recently published <a rel=\"noopener noreferrer\" href=\"https://lbodre.ideo.com/\" target=\"_blank\"><cite>The Little Book of Design Research Ethics</cite></a>, which provides a comprehensive overview of the core principles and guidelines we should follow when conducting design research.\n<h3>Persuasion</h3>\nEthics related to persuasion is about to what extent we may influence the behavior and thoughts of our users. It doesn’t take much to bring acceptable, “white hat” persuasion into gray or even dark territories. Conversion optimization, for example, can easily turn into “How do we squeeze out more revenue from our customers by <a rel=\"noopener noreferrer\" href=\"https://www.fastcompany.com/90306916/were-all-being-manipulated-by-a-b-testing-all-the-time\" target=\"_blank\">turning their unconsciousness against them</a>?” Prime examples include Netflix, which convinces us to watch, watch, and watch even more, and Booking.com, which barrages our senses with urgency and social pressure.\n<h3>Focus</h3>\nThe current digital landscape is addictive, distracting, and competing for attention. Designing for focus is about responsibly handling people’s most valuable resource: time. Our challenge is to limit everything that disrupts our users’ attention, lower the addictiveness of products, and create <a rel=\"noopener noreferrer\" href=\"https://calmtech.com/\" target=\"_blank\">calmness</a>. The Center for Humane Technology has started a useful list of <a rel=\"noopener noreferrer\" href=\"http://humanetech.com/resources\" target=\"_blank\">resources</a> for this purpose.\n<h3>Sustainability</h3>\nWhat’s the impact of our work on the world’s environment, resources, and climate? Instead of continuously adding new features in the unrelenting scrum treadmill, how could we design for fewer? We’re in the position to create responsible digital solutions that enable sustainable consumer behavior and prevent overconsumption. For example, apps such as <a rel=\"noopener noreferrer\" href=\"https://www.optimiam.com/\" target=\"_blank\">Optimiam</a> and <a rel=\"noopener noreferrer\" href=\"https://www.optimiam.com/\" target=\"_blank\">Too Good To Go</a> allow people to order leftover food that would normally be thrashed. Or consider <a rel=\"noopener noreferrer\" href=\"https://www.mutum.com/\" target=\"_blank\">Mutum</a> and <a rel=\"noopener noreferrer\" href=\"https://www.peerby.com/one\" target=\"_blank\">Peerby</a>, whose peer-to-peer platforms promote the sharing and reuse of owned products.\n<h3>Society</h3>\nThe <a rel=\"noopener noreferrer\" href=\"https://ledger.humanetech.com/\" target=\"_blank\">Ledger of Harms</a> of the Center for Human Technology is a work-in-progress collection of the negative impacts that digital technology has on society, including topics such as relationships, mental health, and democracy. Designers who are mindful of society consider the impact of their work on the global economy, communities, politics, and health.\n<figure>\n[caption id="attachment_7171650" align="alignnone" width="1200"]<img alt=\"Focus areas of ethical design: user involvement, persuasion, focus, sustainability, society, usability, accessibility, privacy\" width=\"1200\" src=\"https://alistapart.com/wp-content/uploads/2019/05/daily-ethical-design-fig1-1.jpg\" class=\"wp-image-7171650\" height=\"848\"> The focus areas of design ethics. That’s a lot to consider![/caption]\n<h2>Ethics as an inconvenience</h2>\nIdeally, in every design project, we should assess the potential impact in all of the above-mentioned areas and take steps to prevent harm. Yet there are many legitimate, understandable reasons why we often neglect to do so. It’s easy to <i>have</i> moral principles, yet in the real world, with the constraints that our daily life imposes upon us, it’s seldom easy to <em>act</em> according to those principles.\nWe might simply say it’s inconvenient at the moment. That there’s a lack of time or budget to consider all the ethical implications of our work. That there are many more pressing concerns that have priority right now. We might genuinely believe it’s just a small issue, something to consider later, perhaps. Mostly, we are simply <em>unaware</em> of the possible consequences of our work.\nAnd then there’s the sheer complexity of it all: it’s simply too much to simultaneously focus on. When short on time, or in the heat of approaching deadlines and impatient stakeholders, how do you incorporate all of design ethics’ focus areas?\nWhere do you even start?\n<h2>Ethics as a structural practice</h2>\nFor these reasons, I believe we need to elevate design ethics to a more practical level. We need to find ways to make ethics not an afterthought, not something to be considered separately, but rather something that’s so ingrained in our process that not doing it means <em>not</em> doing design at all.\nThe only way to overcome the “inconvenience” of acting ethically is to practice <em>daily</em> ethical design: ethics structurally integrated in our daily work, processes, and tools as designers. No longer will we have to rely on the exceptions among us; those extremely principled who are brave enough to stand up against the system no matter what kind of pressure is put upon them. Because the system will be on our side.\nBy applying ethics daily and structurally in our design process, we’ll be able to identify and neutralize in a very early stage the potential for mistakes and misuse. We’ll increase the quality of our design and our practices simply because we’ll think things through more thoroughly, in a more conscious and structured manner.\nBut perhaps most important is that we’ll establish a new standard for design. A standard that we can sell to our clients as the way design should be done, with ethical design processes and deliverables already included. A standard that can be taught to design students so that the newest generation of designers doesn’t know any better than to apply ethics, always.\n<h2>How to practice daily ethical design?</h2>\nAt this point we’ve arrived at the question of <em>how</em> we can structurally integrate ethics into our design process. How do we make sure that our daily design decisions will result in a product that’s usable and accessible; protects people’s privacy, agency, and focus; and benefits both society and nature?\nI want to share with you some best practices that I’ve identified so far, and how I’ve tried to apply them during a recent project at Mirabeau. The goal of the project was to build a web application that provides a shaver manufacturer’s factory workers insight into the real-time availability of production materials.\n<h3>Connect to your organization’s mission and values</h3>\nBy connecting our designs to the mission and values of the companies we work for, we can structurally use our design skills in a <a rel=\"noopener noreferrer\" href=\"http://strategicdesignbook.com/\" target=\"_blank\">strategic manner</a>, for moral purposes. We can challenge the company to truly live up to its promises and support it in carrying out its mission. This does, however, require you to be aware of the company’s values, and to compare these to your personal values.\nAs I had worked with our example client before, I knew it was a company that takes care of its employees and has a strong focus on creating a better world. During the kick-off phase, we used a strategy pyramid to structure the client’s mission and values, and to agree upon success factors for the project. We translated the company’s customer-facing brand guidelines to employee-focused design principles that maintained the essence of the organization.\n<h3>Keep track of your assumptions</h3>\nThroughout our entire design process, we make assumptions for each decision that we take. By structurally keeping track of these assumptions, you’ll never forget about the limitations of your design and where the potential risks lie in terms of (harmful) impact on users, the project, the company, and society.\nIn our example project, we listed our assumptions about user goals, content, and functionalities for each page of the application. If we were not fully sure about the value for end users, or the accuracy of a user goal, we marked it as a <i>value assumption</i>. When we were unsure if data could be made available, we marked this as a <i>data (feasibility) assumption</i>. If we were not sure whether a feature would add to the manufacturer’s business, we marked it as a <i>scope assumption</i>. Every week, we tested our assumptions with end users and business stakeholders through user tests and sprint demos. Each design iteration led to new questions and assumptions to be tested the next week.\n<h3>Aim to be proven wrong</h3>\nWhile our assumptions are the <em>known</em> unknowns, there are always <em>unknown</em> unknowns that we aren’t aware of but could be a huge risk for the quality and impact of our work. The only way we can identify these is by applying the scientific principle of <a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Falsifiability\" target=\"_blank\"><i>falsifiability</i></a>: seeking actively to be proven <em>wrong</em>. Only outsiders can point out to us what we miss as an individual or as a team.\nIn our weekly user tests, we included factory workers and stakeholders with different disciplines, from different departments, and working in different contexts, to identify the edge cases that could break our concept. On one occasion, this made us reconsider the entirety of our concept. Still, we could have done better: although scalability to other factories was an important success factor, we were unable to gather input from those other factories during the project. We felt our only option was to mention this as a risk (“limit to scalability”).\n<h3>Use the power of checklists</h3>\nLet’s face it: <a rel=\"noopener noreferrer\" href=\"https://medium.com/ux-power-tools/50-things-you-probably-forgot-to-design-7a288b0ef914\" target=\"_blank\">we forget things</a>. (Without scrolling up the page, can you name all the focus areas of design ethics?) This is where checklists help us out: they provide knowledge in the world, so that we don’t have to process it in our easily overwhelmed memory. Simple yet powerful, a checklist is an essential tool to practice daily ethical design.\nIn our example project, we used checklists to maintain an overview of questions and assumptions to user test, checking whether we included our design principles properly, and assessing whether we complied to the client’s values, design principles, and the agreed-upon success factors. In hindsight, we could also have taken a moment during the concept phase to go through the list of focus areas for design ethics, as well as have taken a more structural approach to check accessibility guidelines.\n<h2>The main challenge for daily ethical design</h2>\nMost ethics focus areas are quite tangible, where design decisions have immediate, often visible effects. While certainly challenging in their own right, they’re relatively easy to integrate in our daily practice, especially for experienced designers.\n<i>Society</i> and the <i>environment</i>, however, are more intangible topics; the effects of our work in these areas are distant and uncertain. I’m sure that when Airbnb was first conceived, the founders did not consider the magnitude of its <a rel=\"noopener noreferrer\" href=\"https://www.bbc.com/news/business-45083954\" target=\"_blank\">disruptive impact</a> on the housing market. The same goes for Instagram, as its role in <a rel=\"noopener noreferrer\" href=\"https://www.gq.com/story/fast-fashion-streetwear-brands-of-instagram\" target=\"_blank\">creating demand for fast fashion</a> must have been hard to foresee.\nHard, but not impossible. So how do we overcome this challenge and make the impact that we have on society and the environment more immediate, more <em>daily</em>?\n<h3>Conduct Dark Reality sessions</h3>\nThe ancient Greek philosopher Socrates used a series of questions to gradually uncover the invalidity of people’s beliefs. In a very similar way, we can uncover the assumptions and potential disastrous consequences of our concepts in a ‘Dark Reality’ session, a form of <a rel=\"noopener noreferrer\" href=\"https://uxplanet.org/can-speculative-design-make-ux-better-design-trend-4-4-ce8d13148e5d\" target=\"_blank\">speculative design</a> that focuses on stress-testing a concept with challenging questions.\nWe have to ask ourselves—or even better, somebody outside our team has to ask <em>us</em>— questions such as, “What is the lifespan of your product? What if the user base will be in the millions? What are the long-term effects on economy, society, and the environment? Who benefits from your design? Who loses? Who is excluded? And perhaps most importantly, how could your design be misused? (For more of these questions, Alan Cooper provided a great list in his <a rel=\"noopener noreferrer\" href=\"https://interaction18.ixda.org/program/keynote--alan-cooper/\" target=\"_blank\">keynote</a> at Interaction 18.)\nThe back-and-forth Q&A of the Dark Reality session will help us consider and identify our concept’s weaknesses and potential consequences. As it is a team effort, it will spark discussion and uncover differences in team members’ ethical values. Moreover, the session will result in a list of questions and assumptions that can be tested with potential users and subject matter experts. In the project for the airline control center, it resulted in more consideration for the human role in automatization and how digital interfaces can continue to support human capabilities (instead of replacing them), and reflection on the role of airports in future society.\nThe dark reality session is best conducted during the convergent parts of the <a rel=\"noopener noreferrer\" href=\"https://www.designcouncil.org.uk/news-opinion/design-process-what-double-diamond\" target=\"_blank\">double diamond</a>, as these are the design phases in which we narrow down to realistic ideas. It’s vital to have a questioner from outside the team with strong interviewing skills and who doesn’t easily accept an answer as sufficient. There are helpful tools available to help structure the session, such as the <a rel=\"noopener noreferrer\" href=\"http://tarotcardsoftech.artefactgroup.com/\" target=\"_blank\">Tarot Cards of Tech</a> and these <a rel=\"noopener noreferrer\" href=\"https://www.ethicsfordesigners.com/tools\" target=\"_blank\">ethical tools</a>.\n<h2>Take a step back to go forward</h2>\nAs designers, we’re optimists by nature. We see the world as a set of problems that we can solve systematically and creatively if only we try hard enough. We intend well. However, merely having the <em>intention</em> to do good is not going to be enough. Our mindset comes with the pitfall of (dis)missing potential disastrous consequences, especially under pressure of daily constraints. That’s why we need to regularly, systematically take a step back and consider the future impact of our work. My hope is that the practical, structural mindset to ethics introduced in this article will help us agree on a higher standard for design.</figure><img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/72UB8D_Bi2U\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/72UB8D_Bi2U/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/daily-ethical-design/","type":"text/html"}],"crawled":1569829277486,"published":1569829277486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811bfa6e:1c48:90d684ff","keywords":["Community, Content, Design, Industry & Business, Information Architecture, User Experience"],"originId":"https://alistapart.com/article/trans-inclusive-design/","fingerprint":"27eba67f","title":"Trans-inclusive Design","author":"by Erin White","summary":{"content":"\n<p>Late one night a few years ago, a panicked professor emailed me: “My transgender student’s legal name is showing on our online discussion board. How can I keep him from being outed to his classmates?” Short story: we couldn’t. The professor created an offline workaround with the student. Years later this problem persists not just in campus systems, but in many systems we use every day.</p>\n\n\n<p>To anyone who’d call that an unusual situation, it’s not. We are all already designing for trans users—<a href=\"https://ajph.aphapublications.org/doi/10.2105/AJPH.2016.303578\">1 in 250 people</a> in the US identifies as transgender or gender non-binary (based on current estimates), and the <a href=\"https://pediatrics.aappublications.org/content/141/3/e20171683\">number is rising</a>.</p>\n\n\n<p>We are web professionals; we can do better than an offline workaround. The choices we make impact the online and offline experiences of real people who are trans, non-binary, or gender-variant—choices that can affirm or exclude, uplift or annoy, help or harm. </p>\n\n\n<p>The rest of this article assumes you agree with the concept that trans people are human beings who deserve dignity, respect, and care. If you are seeking a <a href=\"https://transequality.org/about-transgender\">primer on trans-related vocabulary and concepts</a>, please read up and come back later.</p>\n\n\n<p>I’m going to cover issues touching on content, images, forms, databases, IA, privacy, and AI—just enough to get you thinking about the decisions you make every day and some specific ideas to get you started.</p>\n\n\n<blockquote class=\"wp-block-quote\"><p><em>“Tried making a Bitmoji again, but I always get disillusioned immediately by their binary gender model from literally step 1 and end up not using it. I don’t feel represented.”</em></p><cite><em>Editorial note: All personal statements quoted in this article have been graciously shared with the express consent of the original authors.</em></cite></blockquote>\n\n\n<h2>How we can get things right</h2>\n\n\n<p>Gender is expansively misconstrued as some interchangeable term for anatomical features. Unlike the constellation of <a href=\"https://slate.com/technology/2018/11/sex-binary-gender-neither-exist.html\">human biological forms</a> (our sex), <a href=\"https://thesocietypages.org/trot/2018/11/26/the-social-construction-of-gender-and-sex/\">gender is culturally constructed</a> and varies depending on where you are in the world. It has its own diversity. </p>\n\n\n<p>Asking for gender when it is not needed; limiting the gender options users can select; assuming things about users based on gender; or simply excluding folks from our designs are all ways we reify the man-woman gender binary in design decisions.</p>\n\n\n<h2>Names are fundamentally important</h2>\n\n\n<p>If we do nothing else, <a href=\"https://emmah.net/names.html\">we must get names right</a>. Names are the difference between past and present, invalidation and affirmation, and sometimes safety and danger. </p>\n\n\n<p>Yet, many of the systems we use and create don’t offer name flexibility. </p>\n\n\n<p>Many programmers and designers have a few <a href=\"https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/\">misconceptions about names</a>, such as assuming people have one moniker that they go by all the time, despite how common it is for names to change over a lifetime. People might update them after a change in marital status, family situation, or gender, or perhaps someone is known by a nickname, westernized name, or variation on a first name.</p>\n\n\n<p>In most locales, <a href=\"https://medium.com/gender-2-0/but-why-don-t-you-change-your-name-edb29297a73f\">legally changing names</a> is extremely difficult, extremely expensive, requires medical documentation, or is completely out of the question. </p>\n\n\n<p>Changes to name <em>and</em><strong> </strong>gender marker are even more complicated; they tend to be two separate, long-drawn-out processes. To make matters worse, <a href=\"https://www.lgbtmap.org/equality-maps\">laws vary from state to state</a> within the U.S. and most only recognize two genders—man and woman—rather than allowing non-binary options.Not all trans people change their names, but for those who do, it’s a serious and significant decision that shouldn’t be sabotaged. We can design systems that protect the lives and privacy of our users, respect the fluid nature of personal identity, and act as an <a href=\"http://www.accessiblesociety.org/topics/technology/eleccurbcut.htm\">electronic curb cut</a> that helps everyone in the process.</p>\n\n\n<h3>Deadnaming</h3>\n\n\n<p>One need only search Twitter for “deadname app” to get an idea of how apps can leave users in the lurch. Some of the most alarming examples involve apps and sites that facilitate real-life interactions (which already involve a measure of risk for everyone).</p>\n\n\n<blockquote class=\"wp-block-quote\"><p>“Lyft made it completely impossible for me to change my name on its app even when it was legally changed. I reached out to their support multiple times and attempted to delete the account and start over with no result. I was completely dependent on this service for groceries, appointments, and work, and was emotionally exhausted every single time I needed a ride. I ended up redownloading Uber - even though there was a strike against the service - which I felt awful doing. But Uber allowed me to change my name without any hoops to jump through, so for the sake of my mental health, I had to.”</p></blockquote>\n\n\n<p>Trans people are more likely to experience financial hardship, so using payment apps to ask for donations is often necessary. Some of these <a href=\"https://www.change.org/p/paypal-get-paypal-to-add-a-preferred-name-section\">services may reveal private information</a> as a matter of course, leaving them exposed and potentially at risk.</p>\n\n\n<p>There are also ramifications when linked services rely on our data sources for name information, instigating an unpredictable cascade effect with little or no recourse to prevent the sharing of sensitive details. </p>\n\n\n<p>These are examples of deadnaming. <a href=\"https://www.healthline.com/health/transgender/deadnaming\">Deadnaming</a> is what happens when someone’s previous or birth name is used, rather than the name the person uses now. Deadnaming is invalidating at the least, even as a faux pas, but can be psychologically devastating at the other extreme, even putting lives at risk.The experiences of trans, non-binary, or gender-variant folk can vary widely, and they live in <a href=\"https://www.ustranssurvey.org/reports\">disparate conditions</a> throughout the world. Many are thriving and <a href=\"https://www.tdor.co/\">creating new and joyful ways</a> to resist and undo gender norms, despite the <a href=\"https://www.advocate.com/commentary/2014/02/11/op-ed-why-are-narratives-around-transgender-people-always-so-negative\">common negative narrative</a> around trans lives. Others can face hardship; trans people are more likely to be unstably housed, underemployed, underpaid, and targets of violence in and out of their homes, workplaces, and intimate relationships. The ramifications are amplified for people of color and those with disabilities, as well as those in precarious living/working situations and environments where exposure can put them in harm’s way.</p>\n\n\n<h3>Design for name changes</h3>\n\n\n<p><strong><em>Here</em></strong>’<strong><em>s what we can do:</em></strong></p>\n\n\n<p><strong>Design for renaming.</strong> Emma Humphries’ <a href=\"http://adventuresinrenaming.net/2015/09/05/Design-for-Renaming/\">talk on renaming</a> covers this nicely. <a href=\"https://www.fastcompany.com/90165071/how-tech-giants-design-for-transgender-users-or-dont\">Airbnb</a> has developed policies and procedures for users who’ve transitioned, allowing users to keep their review histories intact with amended names and/or pronouns.</p>\n\n\n<p><strong>Get rid of </strong><a href=\"https://en.wikipedia.org/wiki/Facebook_real-name_policy_controversy\"><strong>“real name” requirements</strong></a><strong>.</strong> Allow people to use names they go by rather than their legal first names.</p>\n\n\n<p><strong>Clarify </strong><a href=\"https://twitter.com/duckinator/status/545934151610675200\"><strong>when you actually need</strong></a><strong> a legal name</strong>, and only use that in conjunction with a display name field.</p>\n\n\n<p><strong>Have a name change process</strong> that allows users to change their names without legal documentation. (It’s likely that you have procedures for marriage-related name changes already.)</p>\n\n\n<p><strong>Ensure users can still change their display names</strong> when connecting with other data sources to populate users’ names.</p>\n\n\n<p><strong>Don’t place onerous restrictions on changes.</strong> Once someone creates a username, web address, or profile URL, <a href=\"https://support.spotify.com/is/account_payment_help/account_help/change-my-username/\">allow them to change it</a>.</p>\n\n\n<p><strong>Draft a code of conduct</strong> if you’re part of an online community, and make sure to include policies around deadnaming. <a href=\"https://www.theverge.com/2018/11/27/18113344/twitter-trans-user-hateful-content-misgendering-deadnaming-ban\">Twitter banned deadnaming</a> last year.</p>\n\n\n<p><strong>Allow people to be forgotten. </strong>When people delete their accounts for whatever reason, help them make sure that their data is not lingering in your systems or in other places online.</p>\n\n\n<h2><strong>Update the systems users don’t see, too</strong></h2>\n\n\n<p>Identity management systems can be a mess, and name changes can reveal the failures among those systems, including hidden systems that users don’t see. </p>\n\n\n<p>One Twitter user’s health insurance company <a href=\"https://twitter.com/Foone/status/1118026782811152384\">kept their ID number between jobs but changed their gender</a>. Another user updated their display name but got an <a href=\"https://twitter.com/JaceyChase/status/1105177162884280321\">email confirmation addressed to their legal name</a>. </p>\n\n\n<p>Hidden information can also undermine job opportunities:</p>\n\n\n<blockquote class=\"wp-block-quote\"><p>“At a university as a student, I transitioned and changed my name and gender to be a woman. TWELVE YEARS later after being hired to work in the Libraries, the Libraries HR coordinator emailed me that I was listed as male still in the database. He changed it on my asking, but I have to wonder how long… was it a factor in my being turned down for jobs I applied to… who had seen that..?”</p></blockquote>\n\n\n<p>Emma Humphries <a href=\"http://adventuresinrenaming.net/2015/09/05/Design-for-Renaming/\">details the hidden systems</a> that can carry out-of-date information about users. Her tips for database design include:</p>\n\n\n<ul><li>Don’t use emails as unique IDs.</li><li>Use an invariant user ID internally, and link the user’s current email and display name to it.</li></ul>\n\n\n<h2>Images</h2>\n\n\n<p>Visuals should allow room for representation and imagination rather than a narrow subset of the usual suspects: figures who appear to be straight, cisgender, able-bodied, and white/Caucasian. </p>\n\n\n<p>What we can do is feature a variety of gender presentations, as well as not assume someone’s gender identity if they buy certain items.</p>\n\n\n<p>Some companies, like <a href=\"https://www.wildfang.com/\">Wildfang</a> and <a href=\"https://www.shethinx.com\">Thinx</a>, offer a broad array of images representing different races, body sizes, and gender expressions on their websites and in their ads. </p>\n\n\n<p>Many are also choosing not to hire models, <a href=\"https://www.bullockandjones.com/\">allowing room for imagination</a> and versatility:</p>\n\n\n<blockquote class=\"wp-block-quote\"><p>“I got a catalog for a ‘classic menswear company’ that features zero photos of any person of any gender. Now if only I could afford an $800 blazer...”</p></blockquote>\n\n\n<p><strong><em>Here's what we can do:</em></strong></p>\n\n\n<p><strong>Actively recruit diverse groups of models for photos.</strong> And pay them!</p>\n\n\n<p><strong>If you can’t shoot your own photos</strong>, Broadly has recently launched a <a href=\"https://broadly.vice.com/en_us/article/qvyq8p/transgender-non-binary-stock-photos-gender-spectrum-collection\">trans-inclusive stock photo collection</a> free for wide use. <a href=\"https://getavataaars.com/\">Avataaars</a> allows users to create an avatar without selecting a gender.</p>\n\n\n<h2>Information architecture</h2>\n\n\n<p>How we organize information is a political act and a non-neutral decision (<a href=\"https://www.journals.uchicago.edu/doi/abs/10.1086/669547\">librarians have said this</a> for a while). This applies to gender-based classifications.</p>\n\n\n<p>Many companies that sell consumer goods incorporate gender into their product design and marketing, no matter what. The product itself might be inherently gender-neutral (such as clothing, <a href=\"http://lettoysbetoys.org.uk/why-it-matters/\">toys</a>, <a href=\"https://twitter.com/erinrwhite/status/1124058358091329537\">bikes</a>, or even <a href=\"https://www.reddit.com/r/pointlesslygendered/comments/bi9imq/bros%C3%A9/\">wine</a>), but these design and marketing decisions can directly impact the information architecture of websites.</p>\n\n\n<p><strong><em>Here's what we can do:</em></strong></p>\n\n\n<p><strong>Evaluate why any menus, categories, or tags are based on gender</strong>, and how it can be done differently:</p>\n\n\n<blockquote class=\"wp-block-quote\"><p>“Nike has a ‘gender neutral’ clothing category, yet it’s listed under ‘men’ and ‘women’ in the website architecture. \uD83E\uDD14”<em> </em></p></blockquote>\n\n\n<h2>Forms</h2>\n\n\n<p>Forms, surveys, and other types of data gathering are surefire ways to include or exclude people. If you ask for information you don’t need or limit the options that people can select, you risk losing them as users.</p>\n\n\n<p><strong><em>Here's what we can do:</em></strong></p>\n\n\n<p><strong>Critically evaluate </strong><a href=\"http://juliepagano.com/blog/2015/06/12/on-forms-and-personal-information/\"><strong>why you are asking for personal information</strong></a>, including gender. Will that information be used to help someone, or sell things to your advertisers?</p>\n\n\n<blockquote class=\"wp-block-quote\"><p>"Why does the @CocaCola site make me select a gender just to make a purchase? Guess my family isn't getting personalized Coke bottles for Christmas."</p></blockquote>\n\n\n<p><strong>If you are asking users for their gender</strong>, you’d better have a good reason and options that include everyone. A gender field should have <a href=\"https://uxdesign.cc/designing-forms-for-gender-diversity-and-inclusion-d8194cf1f51\">more than two options</a>, or should ask for pronouns instead. When including more than binary options, actually record the selections in your databases <a href=\"https://journals.sagepub.com/doi/full/10.1177/1461444815621527\">instead of reclassifying</a> answers as male/female/null, otherwise you risk losing trust when disingenuous design decisions become public.</p>\n\n\n<p><strong>Honorifics</strong> are infrequently used these days, but it takes little work to add <a href=\"https://en.wikipedia.org/wiki/Gender-neutral_title\">gender-inclusive titles</a> to a list. For English-language sites, “Mx.” can go alongside “Mr.” and “Ms.” without fuss. United Airlines <a href=\"https://www.msn.com/en-ph/autos/other/united-airlines-becomes-first-us-carrier-to-launch-non-binary-gender-booking-option/ar-BBV6nhz?li=AAxCjZq\">debuted this option</a> earlier this year.</p>\n\n\n<h2>Content</h2>\n\n\n<p><strong><em>Here's what we can do:</em></strong></p>\n\n\n<p><strong>Avoid inappropriately gendered language.</strong> Your style guide should include <a href=\"https://en.wikipedia.org/wiki/Singular_they\">singular “they”</a> instead of “he/she” or “s/he,” and exclude <a href=\"https://writingcenter.unc.edu/tips-and-tools/gender-inclusive-language/\">frequently used words and phrases</a> that exclude trans folks. Resources such as this <a href=\"https://radicalcopyeditor.com/2017/08/31/transgender-style-guide/\">transgender style guide</a> are a quick way to check your language and benchmark your own content guidelines.</p>\n\n\n<p><strong>Check assumptions about gender and biology.</strong> Not everyone who can have a period, can get pregnant, or can breastfeed identifies as women or mothers—just as not everyone who identifies as women or mothers can have periods, can get pregnant, or can breastfeed. Thinx, a company that sells period underwear, has an inclusive tagline: “For people with periods.”</p>\n\n\n<p><strong>Avoid reinforcing the binary.</strong> Groups of people aren’t “ladies and gentlemen” or “boys and girls.” They are folks, people, colleagues, “y’all,” or even “<a href=\"https://twitter.com/texaninnyc/status/954334612905394177\">all y’all</a>.”</p>\n\n\n<p><strong>Pronouns aren’t “preferred”</strong>—they’re just pronouns. Calling pronouns preferred suggests that they’re optional and are replacing a “true” pronoun.</p>\n\n\n<p><strong>Avoid reinforcing stereotypes</strong> about trans people. Not all trans people are interested in medically transitioning, or in “<a href=\"https://en.wikipedia.org/wiki/Passing_(gender)\">passing</a>.” They also aren’t fragile or in need of a savior. Gender is separate from sexual orientation. You can’t “tell” someone is trans.</p>\n\n\n<h2><strong>Privacy, surveillance, and nefarious AI</strong></h2>\n\n\n<p>We’ve heard the story of algorithms <a href=\"https://www.forbes.com/sites/kashmirhill/2012/02/16/how-target-figured-out-a-teen-girl-was-pregnant-before-her-father-did/\">identifying a pregnant teen</a> before her parents knew. What if an algorithm predicts or reveals information about your gender identity?</p>\n\n\n<p><strong>Inferences.</strong> Users’ genders are assumed based on their purchase/browsing history.</p>\n\n\n<p><strong>Recommendations.</strong> A user bought something before they transitioned and it shows up in “recommended because you bought X.”</p>\n\n\n<p><strong>Predictions.</strong> Users’ genders are not only inferred but used to predict something else based on characteristics of that gender. Even if you don’t tell big websites what your gender is, they assume one for you based on your interests. That kind of reductive essentialism can harm people of all genders. One of this article’s peer readers summed this up:</p>\n\n\n<blockquote class=\"wp-block-quote\"><p>“Gender markers are a poor proxy for tastes. I like dresses, cute flats, and Raspberry Pis.”</p></blockquote>\n\n\n<p><strong>Flashbacks.</strong> “On this day” algorithms remind users of the past, sometimes for better (“I’ve come so far”) or for worse (“<a href=\"https://meyerweb.com/eric/thoughts/2014/12/24/inadvertent-algorithmic-cruelty/\">don’t remind me</a>”). </p>\n\n\n<h3>AI-based discrimination</h3>\n\n\n<p>AI and surveillance software can also reinforce norms about what men’s and women’s bodies should look like, resulting in <a href=\"https://www.nytimes.com/2019/04/17/opinion/tsa-transgender.html?searchResultPosition=1\">harrowing airline travel experiences</a> and creating <a href=\"https://modelviewculture.com/pieces/the-hidden-dangers-of-ai-for-queer-and-trans-people\">AI-based discrimination</a> for trans people.</p>\n\n\n<p>So, too, can <a href=\"https://www.theverge.com/2017/8/22/16180080/transgender-youtubers-ai-facial-recognition-dataset\">trans folks’ public data be used for projects that they don’t consent to</a>. Just because we can use AI for something—like <a href=\"https://twitter.com/rosa/status/1119736583521853441?s=12\">determining gender based on a face scan</a>—doesn’t mean we <em>should</em>.</p>\n\n\n<p><strong><em>Here's what we can do:</em></strong></p>\n\n\n<p><strong>Read up and proactively mitigate bias. </strong><a href=\"https://www.dazeddigital.com/science-tech/article/43211/1/trans-algorithm-machine-learning-bias-discrimination-chelsea-manning-edit\">AI and algorithms<strong><em> </em></strong>can reflect developers’ biases</a> and perpetuate stereotypes about how people’s bodies should look. Use AI to <a href=\"https://qz.com/work/1577597/this-ai-voice-is-gender-neutral-unlike-siri-and-alexa/\">challenge the gender binary</a><strong><em> </em></strong>rather than reinforce it. Design for <a href=\"https://medium.com/greater-than-experience-design/privacy-by-design-7b1165d045e0\">privacy first</a><strong><em>.</em></strong><strong> </strong>Hire more types of people who represent different lived experiences.</p>\n\n\n<h2><strong>Toward a gender-inclusive web</strong></h2>\n\n\n<p>The ideas I’ve offered here are only starting points. How you choose to create space for trans folks is going to be up to you. I don’t have all the solutions here, and there is no singular trans experience. Also, language, definitions, and concepts change <em>rapidly</em>.</p>\n\n\n<p>We shouldn’t use any of these facts as excuses to keep us from trying.</p>\n\n\n<p>When we start to think about design impact on trans folks, the ideas we bring into question can benefit<strong> </strong>everyone. Our designs should go beyond including—they should affirm and validate. Ideally, they will also reflect organizational cultures that support diversity and inclusion.</p>\n\n\n<p><strong><em>Here's what we can do:</em></strong></p>\n\n\n<p>Keep learning. Learn <a href=\"https://transequality.org/issues/resources/supporting-the-transgender-people-in-your-life-a-guide-to-being-a-good-ally\">how to be a good ally</a>. Pay trans user research participants to help validate your design assumptions. Hire trans people on your team and don't hang them out to dry or make them do all the hard work around inclusion and equity. Make it everyone’s job to build a more just web and world for everybody.</p>\n\n\n<p><em>Editorial note: All personal statements quoted in this article have been graciously shared with the express consent of the original authors.</em></p>\n\n\n<p><em>This article is stronger and wiser thanks to Mica McPheeters at </em>A List Apart<em> and the following peer readers. Thank you.</em></p>\n\n\n<p>Jake Atchison<br>Katherine Deibel, Ph.D.<br>Justina F. Hall<br>Austyn Higgs<br>Emma Humphries<br>Tara Robertson<br>Levi R. Walter</p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/h3VJ6VzV3vU\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/h3VJ6VzV3vU/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/trans-inclusive-design/","type":"text/html"}],"crawled":1569829157486,"published":1569829157486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811a25ae:1c47:90d684ff","keywords":["Information Architecture"],"originId":"https://alistapart.com/article/everyday-information-architecture-excerpt/","fingerprint":"e72e3567","title":"Everyday Information Architecture: Auditing for Structure","author":"by Lisa Maria Martin","summary":{"content":"\n<p>Just as we need to\nunderstand our content before we can recategorize it, we need to understand the\nsystem before we try to rebuild it. </p>\n\n\n<p>Enter the structural audit: a review of the site focused solely on its menus, links, flows, and hierarchies. I know you thought we were done with audits back in Chapter 2, but hear me out! Structural audits have an important and singular purpose: to help us build a new sitemap.</p>\n\n\n<p>This isn’t about recreating the <em>intended</em> sitemap—no, this is about experiencing the site the way users experience it. This audit is meant to track and record the structure of the site as it <em>really</em> works.</p>\n\n\n<h2>Setting up the template</h2>\n\n\n<p>First, we’re gonna need another spreadsheet. (Look, it is not <em>my</em> fault that spreadsheets are the perfect system for recording audit data. I don’t make the rules.)</p>\n\n\n<p>Because this involves building a spreadsheet from scratch, I keep a “template” at the top of my audit files—rows that I can copy and paste into each new audit (Fig 4.1). It’s a color-coded outline key that helps me track my page hierarchy <em>and</em> my place in the auditing process. When auditing thousands of pages, it’s easy to get dizzyingly lost, particularly when coming back into the sheet after a break; the key helps me stay oriented, no matter how deep the rabbit hole.</p>\n\n\n<div class=\"wp-block-image\"><figure><img alt=\"\" src=\"https://alistapart.com/wp-content/uploads/2019/04/4.1-outline-key.png\" class=\"wp-image-6000665\"><figcaption><strong>Fig 4.1:</strong> I use a color-coded outline key to record page hierarchy as I move through the audit. Wait, how many circles did Dante write about?</figcaption></figure></div>\n\n\n<h3>Color-coding</h3>\n\n\n<p>Color is the easiest, quickest way to convey page depth at a\nglance. The repetition of black text, white cells, and gray lines can have a\nnumbing effect—too many rows of sameness, and your eyes glaze over. My coloring\nmay result in a spreadsheet that looks like a twee box of macarons, but at\nleast I know, instantly, where I am.</p>\n\n\n<p>The exact colors don’t really matter, but I find that the\nfamiliar mental model of a rainbow helps with recognition—the cooler the row color,\nthe deeper into the site I know I must be. </p>\n\n\n<p>The nested rainbow of pages is great when you’re auditing neatly nested pages—but most websites color outside the lines (pun <em>extremely</em> intended) with their structure. I leave my orderly rainbow behind to capture duplicate pages, circular links, external navigation, and other inconsistencies like:</p>\n\n\n<ul><li><strong>On-page navigation.</strong> A bright text color denotes pages that are accessible via links within page content—not through the navigation. These pages are critical to site structure but are easily overlooked. Not every page <em>needs</em> to be displayed in the navigation menus, of course—news articles are a perfect example—but sometimes this indicates publishing errors. </li><li><strong>External links.</strong> These are navigation links that go to pages outside the domain. They might be social media pages, or even sites held by the same company—but if the domain isn’t the one I’m auditing, I don’t need to follow it. I <em>do </em>need to note its existence in my spreadsheet, so I color the text as the red flag that it is. (As a <em>general </em>rule, I steer clients away from placing external links in navigation, in order to maintain a consistent experience. If there’s a need to send users offsite, I’ll suggest using a contextual, on-page link.)</li><li><strong>Files.</strong> This mostly refers to PDFs, but can include Word files, slide decks, or anything else that requires downloading. As with external links, I want to capture anything that might disrupt the in-site browsing experience. (My audits usually filter out PDFs, but for organizations that overuse them, I’ll audit them separately to show how much “website” content is locked inside.) </li><li><strong>Unknown hierarchy.</strong> Every once in a while, there’s a page that doesn’t seem to belong anywhere—maybe it’s missing from the menu, while its URL suggests it belongs in one section and its navigation scheme suggests another. These pages need to be discussed with their owners to determine whether the content needs to be considered in the new site.</li><li><strong>Crosslinks.</strong> These are navigation links for pages that canonically live in a different section of the site—in other words, they’re duplicates. This often happens in footer navigation, which may repeat the main navigation or surface links to deeper-but-important pages (like a Contact page or a privacy policy). I don’t want to record the same information about the page twice, but I do need to know where the crosslink is, so I can track different paths to the content. I color these cells gray so they don’t draw my attention.</li></ul>\n\n\n<p>Note that coloring every row (and indenting, as you’ll see in a moment) can be a tedious process—unless you rely on Excel’s formatting brush. That tool applies all the right styles in just two quick clicks. </p>\n\n\n<h3>Outlines and page IDs</h3>\n\n\n<p>Color-coding is half of my template; the other half is the outline, which is how I keep track of the structure itself. (No big deal, just <em>the entire point of the spreadsheet</em>.)</p>\n\n\n<p>Every page in the site gets assigned an ID. <em>You</em> are assigning this number; it doesn’t correspond to anything but your own perception of the navigation. This number does three things for you: </p>\n\n\n<ol><li>It associates pages with their place in the site hierarchy. Decimals indicate levels, so the page ID can be decoded as the page’s place in the system.</li><li>It gives each page a unique identifier, so you can easily refer to a particular page—saying “2.4.1” is much clearer than “you know that one page in the fourth product category?”</li><li>You can keep using the ID in other contexts, like your sitemap. Then, later, when your team decides to wireframe pages 1.1.1 and 7.0, you’ll all be working from the same understanding.</li></ol>\n\n\n<p>Let me be completely honest: things might get goofy sometimes\nwith the decimal outline. There will come a day when you’ll find yourself\ncasually typing out “1.2.1.2.1.1.1,” and at that moment, a fellow auditor somewhere\nin the universe will ring a tiny gong for you.</p>\n\n\n<p>In addition to the IDs, I indent each level, which reinforces\nboth the numbers and the colors. Each level down—each digit in the ID, each\nchange in color—gets one indentation.</p>\n\n\n<p>I identify top-level pages with a single number: 1.0, 2.0, 3.0, etc. The next page level in the first section would be 1.1, 1.2, 1.3, and so on. I mark the homepage as 0.0, which is mildly controversial—the homepage is technically a level above—but, look: I’ve got a lot of numbers to write, and I don’t need those numbers to tell me they’re under the homepage, so this is my system. Feel free to use the numbering system that work best for you.</p>\n\n\n<h3>Criteria and columns</h3>\n\n\n<p>So we’ve got some secret codes for tracking hierarchy and depth, but what about other structural criteria? What are our spreadsheet <em>columns </em>(Fig 4.2)? In addition to a column for Page ID, here’s what I cover:</p>\n\n\n<ul><li><strong>URL.</strong>\nI don’t consistently fill out this column, because I already collected this\ndata back in my automated audit. I include it every twenty entries or so (and\non crosslinks or pages with unknown hierarchy) as another way of tracking\nprogress, and as a direct link into the site itself. </li><li><strong>Menu\nlabel/link.</strong> I include this column only if I notice a lot of\nmismatches between links, labels, and page names. Perfect agreement isn’t\nrequired; but frequent, significant differences between the language that <em>leads</em>\nto a page and the language <em>on the page itself</em> may indicate\ninconsistencies in editorial approach or backend structures. </li><li><strong>Name/headline.\n</strong>Think of this as “what does the page owner call it?” It may be\nthe H1, or an H2; it may match the link that brought you here, or the page\ntitle in the browser, or it may not. </li><li><strong>Page\ntitle.</strong> This is for the name of the page in the metadata. Again,\nI don’t use this in every audit—particularly if the site uses the same long,\nbranded metadata title for every single page—but frequent mismatches can be\nuseful to track.</li><li><strong>Section.</strong>\nWhile the template can indicate your level, it can’t tell you which area of the\nsite you’re in—unless you write it down. (This may differ from the section data\nyou applied to your automated audit, taken from the URL structure; here, you’re\nnoting the section where the page appears.)</li><li><strong>Notes.</strong>\nFinally, I keep a column to note specific challenges, and to track patterns I’m\nseeing across multiple pages—things like “Different template, missing subnav”\nor “Only visible from previous page.” My only caution here is that if you’re planning\nto share this audit with another person, make sure your notes are—<em>ahem</em>—professional.\nUnless you enjoy anxiously combing through hundreds of entries to revise comments\nlike “Wow haha nope” (not that I would know anything about that).</li></ul>\n\n\n<figure class=\"wp-block-image\"><img alt=\"\" src=\"https://alistapart.com/wp-content/uploads/2019/04/4.2-audit-columns.png?w=960\" class=\"wp-image-6000666\"><figcaption><strong>Fig 4.2:</strong> A semi-complete structural audit. This view shows a lot of second- and third-level pages, as well as pages accessed through on-page navigation.</figcaption></figure>\n\n\n<p>Depending on your project needs, there may be other columns, too.\nIf, in addition to using this spreadsheet for your new sitemap, you want to use\nit in migration planning or template mapping, you may want columns for new\nURLs, or template types. </p>\n\n\n<p>You can get your own copy of my template as <a href=\"http://bkaprt.com/eia/04-01/\">a downloadable Excel file</a>. Feel free to tweak it to suit your style and needs; I know I always do. As long as your spreadsheet helps you understand the hierarchy and structure of your website, you’re good to go.</p>\n\n\n<h2>Gathering data</h2>\n\n\n<p> Setting up the template is one thing—actually filling it out is, admittedly, another. So how do we go from a shiny, new, naive spreadsheet to a complete, jaded, seen-some-<em>stuff</em> spreadsheet? I always liked Erin Kissane’s description of the process, from <em>The Elements of Content Strategy</em>: </p>\n\n\n<blockquote class=\"wp-block-quote\"><p>Big inventories involve a lot of black coffee, a few late nights, and a playlist of questionable but cheering music prominently featuring the soundtrack of object-collecting video game Katamari Damacy. It takes quite a while to exhaustively inventory a large site, but it’s the only way to really understand what you have to work with.</p></blockquote>\n\n\n<p>We’re not talking about the same kind of exhaustive inventory she\nwas describing (though I <em>am</em> recommending Katamari music). But even our\nless intensive approach is going to require your butt in a seat, your eyes on a\nscreen, and a certain amount of patience and focus. You’re about to walk, with\nyour fingers, through most of a website.</p>\n\n\n<p>Start on the homepage. (We know that not all users start there,\nbut we’ve got to have some kind of order to this process or we’ll never get\nthrough it.) Explore the main navigation before moving on to secondary\nnavigation structures. Move left to right, top to bottom (assuming that is your\nlanguage direction) over each page, looking for the links. You want to record\nevery page you can reasonably access on the site, noting navigational and\nstructural considerations as you go. </p>\n\n\n<p>My advice as you work:</p>\n\n\n<ul><li><strong>Use two monitors.</strong> I struggle immensely without two screens in this process, which involves constantly switching between spreadsheet and browser in rapid, tennis-match-like succession. If you don’t have access to multiple monitors, find whatever way is easiest for you to quickly flip between applications.</li><li><strong>Record what you see.</strong> I generally note all visible menu links at the same level, then exhaust one section at a time. Sometimes this means I have to adjust what I initially observed, or backtrack to pages I missed earlier. You might prefer to record all data across a level before going deeper, and that would work, too. Just be consistent to minimize missed links.</li><li><strong>Be alert to inconsistencies.</strong> On-page links, external links, and crosslinks can tell you a lot about the structure of the site, but they’re easy to overlook. Missed on-page links mean missed content; missed crosslinks mean duplicate work. (Note: the further you get into the site, the more you’ll start seeing crosslinks, given all the pages you’ve already recorded.) </li><li><strong>Stick to what’s structurally relevant.</strong> A single file that’s not part of a larger pattern of file use is not going to change your understanding of the structure. Neither is recording every single blog post, quarterly newsletter, or news story in the archive. For content that’s dynamic, repeatable, and plentiful, I use an <em>x</em> in the page ID to denote more of the same. For example, a news archive with a page ID of 2.8 might show just one entry beneath it as 2.8.x; I don’t need to record every page up to 2.8.791 to understand that there are 791 articles on the site (assuming I noted that fact in an earlier content review). </li><li><strong>Save</strong>. Save <em>frequently</em>. I cannot even begin to speak of the unfathomable heartbreak that is Microsoft Excel burning an unsaved audit to the ground. </li></ul>\n\n\n<p>Knowing which links to follow, which to record, and how best to\nuntangle structural confusion—that improves with time and experience. Performing\nstructural audits will not only teach you about your current site, but will\nhelp you develop fluency in systems thinking—a boon when it comes time to\ndocument the new site.</p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/Ls-z54zDDFU\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/Ls-z54zDDFU/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/everyday-information-architecture-excerpt/","type":"text/html"}],"crawled":1569829037486,"published":1569829037486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811850ee:1c46:90d684ff","keywords":["Community, Industry"],"originId":"https://alistapart.com/article/nothing-fails-like-success/","fingerprint":"9a31d3f6","title":"Nothing Fails Like Success","author":"by Jeffrey Zeldman","summary":{"content":"\n<p>A family buys a house they can’t afford. They can’t make their monthly mortgage payments, so they borrow money from the Mob. Now they’re in debt to the bank and the Mob, live in fear of losing their home, and must do whatever their creditors tell them to do.<br></p>\n\n\n<p>Welcome to the internet, 2019.<br></p>\n\n\n<p>Buying something you can’t afford, and borrowing from organizations that don’t have your (or your customers’) best interest at heart, is the business plan of most internet startups. It’s why our digital services and social networks in 2019 are a <a href=\"https://en.wikipedia.org/wiki/Criticism_of_Facebook\">garbage fire</a> of <a href=\"https://www.dailydot.com/layer8/fake-news-sites-list-facebook/\">lies</a>, <a href=\"https://www.ajc.com/lifestyles/religion/teens-and-the-distorted-reality-social-media/XnWAduy5s1I3rUL1yH1ZZO/\">distortions</a>, <a href=\"https://www.nytimes.com/2017/05/31/technology/twitter-facebook-google-europe-hate-speech.html\">hate speech</a>, <a href=\"https://www.theguardian.com/media-network/media-network-blog/2014/may/13/internet-confirmation-bias\">tribalism</a>, <a href=\"https://www.verywellhealth.com/social-medias-role-in-privacy-breaches-2317518\">privacy violations</a>, <a href=\"https://en.wikipedia.org/wiki/Elizabeth_Holmes\">snake oil</a>, <a href=\"https://www.businessinsider.com/doctors-warn-google-twitter-facebook-anti-vaxxers-2019-3\">dangerous idiocy</a>, <a href=\"https://www.nytimes.com/2017/09/29/opinion/mark-zuckerberg-facebook.html\">deflected responsibility</a>, and whole new categories of unpunished ethical breaches and crimes. <br></p>\n\n\n<p>From optimistically conceived origins and message statements about making the world a better place, too many websites and startups have become <a href=\"https://alistapart.com/article/canary-in-a-coal-mine-how-tech-provides-platforms-for-hate\">the leading edge of bias and trauma</a>, especially for marginalized and at-risk groups.</p>\n\n\n<h2>Why (almost) everything sucks</h2>\n\n\n<p>Twitter, for instance, needs a lot of views for advertising to pay at the massive scale its investors demand. A lot of views means you can’t be too picky about what people share. If it’s misogynists or racists inspiring others who share their heinous beliefs to bring back the 1930s, hey, it’s measurable. If a powerful elected official’s out-of-control tweeting reduces churn and increases views, not only can you pay your investors, you can even take home a bonus. Maybe it can pay for that next meditation retreat.<br></p>\n\n\n<p>You can cloak this basic economic trade-off in fifty layers of bullshit—say you believe in freedom of speech, or that the antidote to bad speech is more speech—but the fact is, hate speech is profitable. It’s killing our society and our planet, but it’s profitable. And the remaining makers of Twitter—the ones whose consciences didn’t send them packing years ago—no longer have a choice. The guy from the Mob is on his way over, and the <a href=\"https://www.urbandictionary.com/define.php?term=vig\">vig</a> is due.<br></p>\n\n\n<p>Not to single out Twitter, but this is clearly the root cause of its seeming indifference to the destruction hate speech is doing to society…and will ultimately do to the platform. (But by then Jack will be able to afford to <a href=\"https://www.vanityfair.com/news/2018/12/jack-dorsey-vipassana-debacle-why-people-hate-twitter\">meditate</a> full-time.)<br></p>\n\n\n<p>Other companies do other evil things to pay their vig. When you owe the Mob, you have no choice. Like sell our data. Or lie about medical research.<br></p>\n\n\n<p>There <em>are</em> internet companies (like <a href=\"https://basecamp.com/\">Basecamp</a>, or like <a href=\"https://automattic.com/\">Automattic</a>, makers of <a href=\"https://wordpress.com/\">WordPress.com</a>, where I work) that charge money for their products and services, and use that money to grow their business. I wish more internet companies could follow that model, but it’s hard to retrofit a legitimate business model to a product that started its life as free. <br></p>\n\n\n<p>And there are even some high-end news publications, such as <em>The New York Times</em>, <em>The</em> <em>Washington Post</em>, and <em>The Guardian</em>, that survive on a combination of advertising and flexible paywalls. But these options are not available to most digital publications and businesses.</p>\n\n\n<h2>Return with me to those <a href=\"http://johnstyn.com/\">Halcyon</a> days…</h2>\n\n\n<p>Websites and internet startups used to be you and your friends making cool stuff for your other friends, and maybe building new friendships and even small communities in the process. (Even in 2019, that’s still how <em>some</em> websites and startups begin—as labors of love, fashioned by idealists in their spare time.) <br></p>\n\n\n<p>Because they are labors of love; because we’ve spent 25 years training people to believe that <a href=\"https://en.wikipedia.org/wiki/Information_wants_to_be_free\">websites, and news, and apps, and services should be free</a>; because, when we begin a project, we can scarcely believe anyone will ever notice or care about it—for these reasons and more, the things we make digitally, especially on the web, are offered free of charge. We labor on, excited by positive feedback, and delighted to discover that, if we keep at it, our little community will grow.<br></p>\n\n\n<p>Most such labors of love disappear after a year or two, as the creators drift out of touch with each other, get “real” jobs, fall in love, start families, or simply lose interest due to lack of attention from the public or the frustrations of spending weekends and holidays grinding away at an underappreciated site or app while their non-internet friends spend those same hours either having fun or earning money.</p>\n\n\n<h2>Along came money</h2>\n\n\n<p>But <em>some</em> of these startup projects catch on. And when they do, a certain class of investor smells ROI. And the naive cofounders, who never expected their product or service to really get anywhere, can suddenly envision themselves rich and Zuckerberg-famous. Or maybe they like the idea of quitting their day job, believing in themselves, and <em>really going for it</em>. After all, that is an empowering and righteous vision. <br></p>\n\n\n<p>Maybe they believe that by taking the initial investment, they can do more good—that their product, if developed further, can actually help people. This is often the motivation behind agreeing to an initial investment deal, especially in categories like healthcare.<br></p>\n\n\n<p>Or maybe the founders are problem solvers. Existing products or services in a given category have a big weakness. The problem solvers are sure that their idea is better. With enough capital, and a slightly bigger team, they can show the world how to do it right. Most inventions that have moved humankind forward followed exactly this path. It should lead to a better world (and it sometimes does). It shouldn’t produce privacy breaches and fake medicine and election-influencing bots and all the other plagues of our emerging digital civilization. So why does it?</p>\n\n\n<h2>Content wants to be paid</h2>\n\n\n<p>Primarily it is because these businesses have no business model. They were made and given away free. Now investors come along who can pay the founders, buy them an office, give them the money to staff up, and even help with PR and advertising to help them grow faster.<br></p>\n\n\n<p>Now there are salaries and insurance and taxes and office space and travel and lecture tours and sales booths at SXSW, but there is still no charge for the product.<br></p>\n\n\n<p>And the investor seeks a big return.<br></p>\n\n\n<p>And when the initial investment is no longer enough to get the free-product company to scale to the big leagues, that’s when the really big investors come in with the really big bucks. And the company is suddenly famous overnight, and “everybody” is using the product, and it’s still free, and the investors are still expecting a giant payday.<br></p>\n\n\n<p>Like I said—a house you can’t afford, so you go into debt to the bank and the Mob.</p>\n\n\n<h2>The money trap</h2>\n\n\n<p>Here it would be easy to blame capitalism, or at least untrammeled, under-regulated capitalism, which has often been a source of human suffering—not that capitalism, properly regulated, can’t also be a force for innovation which <em>ameliorates</em> suffering. That’s the dilemma for our society, and where you come down on free markets versus governmental regulation of businesses should be an intellectual decision, but these days it is a label, and we hate our neighbors for coming down a few degrees to the left or right of us. But I digress and oversimplify, and this isn’t a complaint about late stage capitalism per se, although it may smell like one.<br></p>\n\n\n<p>No, the reason small companies created by idealists too frequently turn into consumer-defrauding forces for evil has to do with the amount of profit each new phase of investor expects to receive, and how quickly they expect to receive it, and the fact that the products and services are still free. And<a href=\"https://www.quora.com/Who-originally-suggested-that-if-youre-not-paying-for-the-product-you-are-the-product\"> you know what they say about free products</a>.<br></p>\n\n\n<h2>Nothing fails like success</h2>\n\n\n<p>A friend who’s a serial entrepreneur has started maybe a dozen internet businesses over the span of his career. They’ve all met a need in the marketplace. As a consequence, they’ve all found customers, and they’ve all made a profit. Yet his investors are rarely happy. <br></p>\n\n\n<p>“Most of my startups have the decency to fail in the first year,” one investor told him. My friend’s business was taking in several million dollars a year and was slowly growing in staff and customers. It was profitable. Just not obscenely so. <br></p>\n\n\n<p>And internet investors don’t want a modest return on their investment. They want an obscene profit right away, or a brutal loss, which they can write off their taxes. Making them a hundred million for the ten million they lent you is good. Losing their ten million is also good—they pay a lower tax bill that way, or they use the loss to fold a company, or they make a profit on the furniture while writing off the business as a loss…whatever rich people can legally do under our tax system, which is quite a lot. <br></p>\n\n\n<p>What these folks don’t want is to lend you ten million dollars and get twelve million back.<br></p>\n\n\n<p>You and I might go, “Wow! I just made two million dollars just for being privileged enough to have money to lend somebody else.” And that’s why you and I will never have ten million dollars to lend anybody. Because we would be grateful for it. And we would see a free two million dollars as a life-changing gift from God. But investors don’t think this way.</p>\n\n\n<h2>We didn’t start the fire, but we roasted our weenies in it</h2>\n\n\n<p>As much as we pretend to be a religious nation, our society worships these investors and their profits, worships companies that turn these profits, worships above all the myth of overnight success, which we use to motivate the hundreds of thousands of workers who will work nights and weekends for the owners in hopes of cashing in when the stock goes big. <br></p>\n\n\n<p>Most times, even if the stock does go big, the owner has found a way to devalue it by the time it does. Owners have brilliant advisers they pay to figure out how to do those things. You and I don’t.</p>\n\n\n<h2>A Christmas memory</h2>\n\n\n<p>I remember visiting San Francisco years ago and scoring an invitation to Twitter’s Christmas party through a friend who worked there at the time. Twitter was, at the time, an app that worked via SMS and also via a website. Period.<br></p>\n\n\n<p>Some third-party companies, starting with my friends at <a href=\"https://iconfactory.com/\">Iconfactory</a>, had built iPhone apps for people who wanted to navigate Twitter via their newfangled iPhones instead of the web. Twitter itself hadn’t publicly addressed mobile and might not even have been thinking about it.<br></p>\n\n\n<p>Although Twitter was transitioning from a fun cult thing—used by bloggers who attended <a href=\"https://www.sxsw.com/festivals/interactive/\">SXSW Interactive</a> in 2007—to an emerging cultural phenomenon, it was still quite basic in its interface and limited in its abilities. Which was not a bad thing. There is art in constraint, value in doing one thing well. As an outsider, if I’d thought about it, I would have guessed that Twitter’s entire team consisted of no more than 10 or 12 wild-eyed, sleep-deprived true believers. <br></p>\n\n\n<p>Imagine my surprise, then, when I showed up at the Christmas party and discovered I’d be sharing dinner with hundreds of designers, developers, salespeople, and executives instead of the handful I’d naively anticipated meeting. (By now, of course, Twitter employs many thousands. It’s still not clear to an outsider why so many workers are needed.)<br></p>\n\n\n<p>But one thing is clear: somebody has to pay for it all.</p>\n\n\n<h2>Freemium isn’t free</h2>\n\n\n<p>Employees, let alone thousands of them, on inflated Silicon Valley engineer salaries, aren’t free. Health insurance and parking and meals and HR and travel and expense accounts and meetups and software and hardware and office space and amenities aren’t free. Paying for all that while striving to repay investors tenfold means making a buck any way you can. <br></p>\n\n\n<p>Since the product was born free and a paywall isn’t feasible, Twitter must rely on that old standby: advertising. Advertising may not generate enough revenue to keep your hometown newspaper (or most podcasts and content sites) in business, but at Twitter’s scale, it pays. <br></p>\n\n\n<p>It pays because Twitter has so many active users. And what keeps those users coming back? Too often, it’s the dopamine of relentless tribalism—folks whose political beliefs match and reinforce mine in a constant unwinnable war of words with folks whose beliefs differ.<br></p>\n\n\n<p>Of course, half the antagonists in a given brawl may be bots, paid for in secret by an organization that wants to make it appear that most citizens are against Net Neutrality, or that most Americans oppose even the most basic gun laws, or that our elected officials work for lizard people. The whole system is broken and dangerous, but it’s also addictive, and we can’t look away. From our naive belief that content wants to be free, and our inability to create businesses that pay for themselves, we are turning our era’s greatest inventions into engines of doom and despair.</p>\n\n\n<h2>Your turn</h2>\n\n\n<p>So here we are. Now what do we do about it? <br></p>\n\n\n<p>It’s too late for current internet businesses (victims of their own success) that are mortgaged to the hilt in investor gelt. But could the next generation of internet startups learn from older, stable companies like Basecamp, and design products that pay for themselves via customer income—products that profit slowly and sustainably, allowing them to scale up in a similarly slow, sustainable fashion?<br></p>\n\n\n<p>The self-payment model may not work for apps and sites that are designed as modest amusements or communities, but maybe those kinds of startups don’t need to make a buck—maybe they can simply be labors of love, like the websites we loved in the 1990s and early 2000s.<br></p>\n\n\n<p>Along those same lines, can the <a href=\"https://indieweb.org/\">IndieWeb</a>, and products of IndieWeb thinking like <a href=\"https://micro.blog/\">Micro.blog</a>, save us? Might they at least provide an alternative to the toxic aspects of our current social web, and restore the ownership of our data and content? And before you answer, <a href=\"https://indieweb.org/Getting_Started\">RTFM</a>.<br></p>\n\n\n<p>On an individual and small collective basis, the IndieWeb already works. But does an IndieWeb approach scale to the general public? If it doesn’t scale <em>yet</em>, can we, who envision and design and build, create a new generation of tools that will help give birth to a flourishing, independent web? One that is as accessible to ordinary internet users as Twitter and Facebook and Instagram? <a href=\"https://en.wikipedia.org/wiki/Tantek_%C3%87elik\">Tantek Çelik</a> thinks so, and he’s been right about the web for nearly 30 years. (For more about what Tantek thinks, <a href=\"http://5by5.tv/bigwebshow/186\">listen to our conversation</a> in Episode № 186 of The Big Web Show.)<br>Are these approaches mere whistling against a hurricane? Are most web and internet users content with how things are? What do <em>you</em> think? Share your thoughts on your personal website (dust yours off!) or (irony ahoy!) on your indie or mainstream social networks of choice using hashtag <strong>#LetsFixThis</strong>. I can’t wait to see what you have to say.</p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/11S-0ER8qtM\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/11S-0ER8qtM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/nothing-fails-like-success/","type":"text/html"}],"crawled":1569828917486,"published":1569828917486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d81167c2e:1c45:90d684ff","keywords":["Accessibility, User Experience"],"originId":"https://alistapart.com/article/accessibility-for-vestibular/","fingerprint":"4e4d4d7e","title":"Accessibility for Vestibular Disorders: How My Temporary Disability Changed My Perspective","author":"by Facundo Corradini","summary":{"content":"\n<p>Accessibility can be tricky. There are plenty of conditions to take into consideration, and many technical limitations and weird exceptions that make it quite hard to master for most designers and developers.</p>\n\n\n<p>I never considered myself an accessibility <em>expert</em>, but I took great pride in making my projects Web Content Accessibility Guidelines (WCAG) compliant…ish. They would pass most automated tests, show perfectly in the accessibility tree, and work quite well with keyboard navigation. I would even try (and fail) to use a screen reader every now and then.</p>\n\n\n<p>But life would give me a lesson I would probably never learn otherwise: last October, my <em>abled</em> life took a drastic change—I started to feel extremely dizzy, with a constant sensation of falling or spinning to the right. I was suffering from a bad case of vertigo caused by labyrinthitis that made it impossible to get <em>anything</em> done.</p>\n\n\n<p>Vertigo can have a wide range of causes, the most common being a viral infection or tiny calcium crystal free floating in the inner ear, which is pretty much our body’s accelerometer. Any disruption in there sends the brain confusing signals about the body’s position, which causes really heavy nausea, dizziness, and headaches. If you’ve ever felt seasick, it’s quite a similar vibe. If not, think about that feeling when you just get off a rollercoaster…it’s like that, only <em>all day long</em>.</p>\n\n\n<p>For most people, <a href=\"https://vestibular.org/understanding-vestibular-disorders/types-vestibular-disorders/benign-paroxysmal-positional-vertigo\">vertigo is something they’ll suffer just once in a lifetime</a>, and it normally goes away in a week or two. Incidence is really high, with some estimates claiming that up to 40% of the population suffers vertigo at least once in their lifetime. Some people live all their lives with it (or with similar symptoms caused by a range of diseases and syndromes grouped under the umbrella term of <em>vestibular disorders</em>), with 4% of US adults reporting chronic problems with balance, and an additional 1.1% reporting chronic dizziness, <a href=\"https://www.asha.org/PRPSpecificTopic.aspx?folderid=8589942134§ion=Incidence_and_Prevalence\">according to the American Speech-Language-Hearing Association</a>.</p>\n\n\n<p>In my case, it was a little over a month. Here’s what I learned while going through it.</p>\n\n\n<h2>Slants can trigger vestibular symptoms</h2>\n\n\n<p>It all started as I was out for my daily jog. I felt slightly dizzy, then suddenly my vision got totally distorted. Everything appeared further away, like looking at a fun house’s distortion mirror. I stumbled back home and rested; at that moment I believed I might have over-exercised, and that hydration, food, and rest were all I needed. Time would prove me wrong.</p>\n\n\n<p>What I later learned was that experiencing vertigo is a constant war between one of your inner ears telling the brain “everything is fine, we’re level and still” and the other ear shouting “oh my God, we’re falling, we’re falling!!!” Visual stimuli can act as an intermediary, supporting one ear’s message or the other’s. Vertigo can also work in the opposite way, with the dizziness interfering with your vision.</p>\n\n\n<p>I quickly found that when symptoms peaked, staring at a distant object would ease the falling sensation <em>somewhat</em>.</p>\n\n\n<p>In the same fashion, some visual stimuli would worsen it.</p>\n\n\n<p>Vertical slants were a big offender in that sense. For instance, looking at a subtle vertical slant (the kind that you’d have to look at twice to make sure it’s not perfectly vertical) on a webpage would instantly trigger symptoms for me. Whether it was a page-long slant used to create some interest beside text or a tiny decoration to mark active tabs, looking at anything with slight slants would instantly send me into the rollercoaster.</p>\n\n\n<p>Horizontal slants (whatever the degree) and harder vertical slants wouldn’t cause these issues.</p>\n\n\n<p>My best guess is that slight vertical slants can look like forced perspective and therefore reinforce the falling-from-height sensation, so I would recommend avoiding vertical slants if you can, or make them super obvious. A slight slant looks like perspective, a harder one looks like a triangle.</p>\n\n\n<h2>Target size matters (even on mouse-assisted devices)</h2>\n\n\n<p>After a magnetic resonance imaging (MRI) scan, some tests to discard neurological conditions, and other treatments that proved ineffective, I was prescribed Cinnarizine.</p>\n\n\n<p>Cinnarizine is a calcium channel blocker—to put it simply, it prevents the malfunctioning inner ear “accelerometer” from sending incorrect info to the brain. <br>And it worked wonders. After ten days of being barely able to get out of bed, I was finally getting something closer to my normal life. I would still feel dizzy all the time, with some peaks throughout the day, but for the most part, it was much easier.</p>\n\n\n<p>At this point, I was finally able to use the computer (but still unable to produce any code at all). To make the best of it, I set on a mission to self-experiment on accessibility for vestibular disorders. In testing, I found that one of the first things that struck me was that I would always miss targets (links and buttons).</p>\n\n\n<p>I’m from the generation that grew up with desktop computers, so using a mouse is second nature. The pointer is pretty much an extension of my mind, as it is for many who use it regularly. But while Cinnarizine helped with the dizziness, it has a common side effect of negatively impacting coordination and fine motor skills (it is recommended not to drive or operate machinery while under treatment). It was not a surprise when I realized it would be much harder to get the pointer to do what I intended.</p>\n\n\n<p>The common behavior would be: moving the pointer past the link I intended to click, clicking before reaching it at all, or having to try multiple times to click on smaller targets.</p>\n\n\n<p><a href=\"https://www.w3.org/WAI/WCAG21/Understanding/target-size.html\">Success Criterion 2.5.5 Target Size (Level AAA)</a> of the World Wide Web Consortium (W3C)’s WCAG recommends bigger target sizes so users can activate them easily. The obvious reason for this is that it’s harder to pinpoint targets on smaller screens with coarser inputs (i.e., touchscreens of mobile devices). A fairly common practice for developers is to set bigger target sizes for smaller viewport widths (assuming that control challenges are only touch-related), while neglecting the issue on big screens expected to be used with mouse input. I know I’m guilty of that myself.</p>\n\n\n<p>Instead of targeting this behavior for just smaller screen sizes, there are plenty of reasons to create larger target sizes on <em>all</em> devices: it will benefit users with limited vision (when text is scaled up accordingly and colors are of sufficient contrast), users with mobility impairments such as hand tremors, and of course, users with difficulty with fine motor skills.</p>\n\n\n<h2>Font size and spacing</h2>\n\n\n<p>Even while “enjoying” the ease of symptoms provided by the treatment, reading <em>anything</em> still proved to be a challenge for the following three weeks.</p>\n\n\n<p>I was completely unable to use mobile devices while suffering vertigo due to the smaller font sizes and spacing, so I was forced to use my desktop computer for everything.</p>\n\n\n<p>I can say I was experiencing something similar to users with mild forms of dyslexia or attention disorders: whenever I got to a website that didn’t follow good font styling, I would find myself reading the same line over and over again.</p>\n\n\n<p>This proves once again that accessibility is intersectional: when we improve things for a particular purpose it usually benefits users with other challenges as well. I used to believe recommendations on font styles were mostly intended for the nearsighted and those who have dyslexia. Turns out they are also critical for those with vertigo, and even for those with some cognitive differences. At the end of the day, everybody benefits from better readability.</p>\n\n\n<p>Some actions you can take to improve readability are:</p>\n\n\n<ul><li>Keep line height to at least 1.5 times the font size (i.e., <code>line-height: 1.5</code>).</li><li>Set the spacing between paragraphs to at least 2.0 times the font size. We can do this by adjusting the margins using relative units such as <code>em</code>.</li><li>Letter spacing should be at least 0.12 times the font size. We can adjust this by using the <code>letter-spacing</code> CSS property, perhaps setting it in a relative unit.</li><li>Make sure to have good contrast between text and its background.</li><li>Keep <code>font-weight</code> at a reasonable level for the given <code>font-family</code>. Some fonts have thin strokes that make them harder to read. When using thinner fonts, try to improve contrast and font size accordingly, even more than what WCAG would suggest.</li><li>Choose fonts that are easy to read. There has been a large and still inconclusive debate on which font styles are better for users, but one thing I can say for sure is that popular fonts (as in fonts that the user might be already familiar with) are generally the least challenging for users with reading issues.</li></ul>\n\n\n<p><a href=\"https://www.w3.org/WAI/WCAG21/Understanding/text-spacing.html\">WCAG recommendations on text are fairly clear</a> and fortunately are the most commonly implemented of recommendations, but even they can still fall short sometimes. So, better to follow <a href=\"http://adrianroselli.com/2015/03/typefaces-for-dyslexia.html#Tips\">specific guides on accessible text</a> and your best judgement. <a href=\"https://www.smashingmagazine.com/2018/09/importance-manual-accessibility-testing/\">Passing automated tests does not guarantee actual accessibility.</a></p>\n\n\n<p>Another issue on which my experience with vertigo proved to be similar to that of people with dyslexia and attention disorders was how hard it was for me to keep my attention in just one place. In that sense…</p>\n\n\n<h2>Animations are bad (and parallax is pure evil)</h2>\n\n\n<p>Val Head has already covered <a href=\"https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity\">visually-triggered vestibular disorders</a> in an outstanding article, so I would recommend giving it a good read if you haven’t already.</p>\n\n\n<p>To summarize, animations can trigger nausea, dizziness, and headaches in some users, so we should use them purposely and responsibly.</p>\n\n\n<p>While most animations did not trigger my symptoms, parallax scrolling did. I’d never been a fan of parallax to begin with, as I found it confusing. And when you’re experiencing vertigo, the issues introduced by parallax scrolling compound.</p>\n\n\n<p>Really, there are no words to describe just how bad a simple parallax effect, scrolljacking, or even <code>background-attachment: fixed</code> would make me feel. I would rather jump on one of those 20-G centrifuges astronauts use than look at a website with parallax scrolling.</p>\n\n\n<p>Every time I encountered it, I would put the bucket beside me to good use and be forced to lie in bed for <em>hours</em> as I felt the room spinning around me, and no meds could get me out of it. It was <em>THAT</em> bad.</p>\n\n\n<p>Though normal animations did not trigger a reaction as severe, they still posed a big problem. The extreme, conscious, focused effort it took to read would make it such that anything moving on the screen would instantly break my focus, and force me to start the paragraph all over. And I mean <em>anything</em>.</p>\n\n\n<p>I would constantly find myself reading a website only to have the typical collapsing navigation bar on scroll distract me just enough that I’d totally lose count of where I was at. Autoplaying carousels were <em>so</em> annoying I would delete them using dev tools as soon as they showed up. Background videos would make me get out of the website desperately.</p>\n\n\n<p>Over time I started using mouse selection as a pointer; a visual indication of what I’d already read so I could get back to it whenever something distracted me. Then I tried custom stylesheets to disable transforms and animations whenever possible, but that also meant many websites having critical elements not appear at all, as they were implemented to start off-screen or otherwise invisible, and show up on scroll.</p>\n\n\n<p>Of course, deleting stuff via dev tools or using custom stylesheets is not something we can expect 99.99% of our users to even know about.</p>\n\n\n<p>So if anything, consider reducing animations to a minimum. Provide users with controls to turn off non-essential animations (<a href=\"https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions.html\">WCAG 2.2.3 Animation from Interactions</a>) and to pause, stop, or hide them (<a href=\"https://www.w3.org/WAI/WCAG21/Understanding/pause-stop-hide.html\">WCAG 2.2.2 Pause, Stop, Hide</a>). Implement animations and transitions in such a way that if the user disables them, critical elements still display.</p>\n\n\n<p>And be extra careful with parallax: my recommendation is to, at the very least, try limiting its use to the header (“hero”) only, and be mindful of getting a smooth, realistic parallax experience. My vertigo self would have said, “<em>just don’t freaking use parallax. Never. EVER.</em>” But I guess that might be a hard idea to sell to stakeholders and designers.</p>\n\n\n<p>Also consider learning how to use the <code>prefers-reduced-motion</code> feature query. This is a newer addition to the specs (it’s part of the <a href=\"https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-reduced-motion\">Media Queries Level 5 module</a> , which is at an early Editor’s Draft stage) that allows authors to apply selective styling depending on whether the user has requested the system to minimize the use of animations. <a href=\"https://caniuse.com/#search=prefers-reduced-motion\">OS and browser support for it is still quite limited</a>, but the day will come when we will set any moving thing inside a query for when the user has <code>no-preference</code>, blocking animations from those who choose <code>reduce</code>.</p>\n\n\n<p>After about a week of wrestling websites to provide a static experience, I remembered something that would prove to be my biggest ally while the vertigo lasted:</p>\n\n\n<h2>Reader mode</h2>\n\n\n<p>Some browsers include a “reader mode” that strips the content from any styling choices, isolates it from any distraction, and provides a perfect WCAG compliant layout for the text to maximize readability.</p>\n\n\n<p>It is extremely helpful to provide a clear and consistent reading experience throughout multiple websites, especially for users with any kind of reading impairment.</p>\n\n\n<p>I have to confess: before experiencing my vestibular disorder, I had never used Reader Mode (the formal name varies in browsers) or even checked if my projects were compatible with it. I didn’t even think it was such a useful feature, as a quick search for “reader mode” actually returned quite a few threads by users asking how to disable it or how to take the button for it out of Firefox’s address bar. (It seems some people are unwittingly activating it…perhaps the icon is not clear enough.)</p>\n\n\n<p>Displaying the button to access Reader Mode is toggled by browser heuristics, which are based on the use (or not) of semantic tags in a page’s HTML. Unfortunately this meant not all websites provided such a “luxury.”</p>\n\n\n<p>I really wish I wouldn’t have to say this in 2019…but please, <em>please</em> use semantic tags. Correct <a href=\"https://alistapart.com/article/conversational-semantics\">conversational semantics</a> allow your website to be displayed in Reader Mode, and provide a better experience for users of screen readers. Again, accessibility is intersectional.</p>\n\n\n<p>Reader Mode proved to be extremely useful while my vertigo lasted. But there was something even better:</p>\n\n\n<h2>Dark color schemes</h2>\n\n\n<p>By the fourth week, I started feeling mostly fine. I opened Visual Studio Code to <em>try</em> to get back to work. In doing so, it served me well to find one more revelation: a light-text-on-dark-background scheme was SO much easier for me to read. (Though I still was not able to return to work at this time.)</p>\n\n\n<p>I was quite surprised, as I had always preferred light mode with dark-text-on-light-background for reading, and dark mode, with light-text-on-dark for coding. I didn’t know at the time that I was suffering from <em>photophobia</em> (which is a sensitivity to light), which was one of the reasons I found it hard to read on my desktop and to use my mobile device at all.</p>\n\n\n<p>As far as I know, photophobia is not a common symptom of <em>vestibular disorders</em>, but there are many conditions that will trigger it, so it’s worth looking into for our projects’ accessibility.</p>\n\n\n<p>CSS is also planning a media query to switch color schemes. Known as <code>prefers-color-scheme</code>, it allows applying styles based on the user’s stated preference for dark or light theming. It’s also part of the Media Queries Level 5 spec, and at the time of writing this article <a href=\"https://caniuse.com/#search=prefers-color-scheme\">it’s only available in Safari Technology Preview</a>, with Mozilla planning to ship it in the upcoming Firefox 67. Luckily there’s a <a href=\"https://github.com/csstools/css-prefers-color-scheme/blob/HEAD/README-POSTCSS.md\">PostCSS plugin</a> that allows us to use it in most modern browsers by turning <code>prefers-color-scheme</code>queries into <code>color-index</code> queries, which have much better support.</p>\n\n\n<p>If PostCSS is not your cup of tea, or for whatever reason you cannot use that approach to automate switching color schemes to a user’s preference, try at least to provide a theming option in your app’s configuration. Theming has become extremely simple since the release of CSS Custom Properties, so implementing this sort of switch is relatively easy and will greatly benefit anyone experiencing photophobia.</p>\n\n\n<h2>Moving on</h2>\n\n\n<p>After a month and some days, the vertigo disappeared completely, and I was able to return to work without needing any meds or further treatment. It should stay that way, as for most people it’s a once-in-a-lifetime occurrence.</p>\n\n\n<p>I went back to my <em>abled</em> life, but the experience changed my mindset for good.</p>\n\n\n<p>As I said before, I always cared for making my projects compatible for people using keyboard navigation and screen readers. But I learned the hard way that there are plenty of “invisible conditions” that are just as important to take into consideration: vestibular disorders, <a href=\"https://alistapart.com/article/designing-for-cognitive-differences\">cognitive differences</a>, dyslexia, and color blindness, just to name a few. I was totally neglecting those most of the time, barely addressing the issues in order to pass automated tests, which means I was unintentionally annoying some users by making websites inaccessible to them.</p>\n\n\n<p>After my experience with vertigo, I’ve turned to an <a href=\"https://www.24a11y.com/2017/accessibility-first/\">accessibility-first approach to design and development</a>. Now I ask myself, “am I leaving anyone behind with this decision?,” before dropping a single line of code. Accessibility should never be an afterthought.</p>\n\n\n<p>Making sure my projects work from the start for those with difficulties also improves the experience for everyone else. Think about how improving text styles for users with dyslexia, vertigo, or visual problems improves readability for all users, or how being able to control animations or choose a color scheme can be critical for users with attention disorders and photophobia, respectively, while also a nice feature for everybody.</p>\n\n\n<p>It also turned my workflow into a much smoother development experience, as addressing accessibility issues from the beginning can mean a slower start, but it’s also much easier and faster than trying to fix broken accessibility afterwards.</p>\n\n\n<p>I hope that by sharing my personal experience with vertigo, I’ve illustrated how we can all design and develop a better web for everybody. Remember, <a href=\"https://uxmag.com/articles/we-re-just-temporarily-abled\">we’re all just temporarily abled</a>.</p>\n\n\n<p><br></p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/P6NK__5U2eQ\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/P6NK__5U2eQ/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/accessibility-for-vestibular/","type":"text/html"}],"crawled":1569828797486,"published":1569828797486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d8114a76e:1c44:90d684ff","keywords":["Application Development, JavaScript"],"originId":"https://alistapart.com/article/responsible-javascript-part-1/","fingerprint":"6e3f673d","title":"Responsible JavaScript: Part I","author":"by Jeremy Wagner","summary":{"content":"\n<p>By the numbers, <a href=\"https://httparchive.org/reports/state-of-javascript#bytesJs\">JavaScript is a performance liability</a>. If the trend persists, the median page will be shipping at least 400 KB of it before too long, and that’s merely what’s <em>transferred</em>. Like other text-based resources, JavaScript is almost always served compressed—but that might be the only thing we’re getting consistently right in its delivery.</p>\n\n\n<p>Unfortunately, while reducing resource transfer time is a big part of that whole performance thing, compression has no effect on how long browsers take to process a script once it arrives in its entirety. If a server sends 400 KB of compressed JavaScript, the actual amount browsers have to process after decompression is north of a megabyte. How well devices cope with these heavy workloads depends, well, on the <em>device</em>. <a href=\"https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4\">Much has been written</a> about how adept various devices are at processing lots of JavaScript, but the truth is, the amount of time it takes to process even a trivial amount of it varies greatly between devices.</p>\n\n\n<p>Take, for example, this <a href=\"https://devmode.jeremy.codes/\">throwaway project of mine</a>, which serves around 23 KB of uncompressed JavaScript. On a mid-2017 MacBook Pro, Chrome chews through this comparably tiny payload in about 25 ms. On a <a href=\"https://www.gsmarena.com/nokia_2-8513.php\">Nokia 2 Android phone</a>, however, that figure balloons to around 190 ms. That’s not an insignificant amount of time, but in either case, the page gets interactive reasonably fast.</p>\n\n\n<p>Now for the big question: how do you think that little Nokia 2 does on an average page? It chokes. Even on a fast connection, browsing the web on it is an exercise in patience as JavaScript-laden web pages brick it for considerable stretches of time.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"A performance timeline for a JavaScript-heavy website. Most of the timeline is JavaScript.\" src=\"https://alistapart.com/wp-content/uploads/2019/04/fig-01-2x.png?w=960\" class=\"wp-image-6000652\"><figcaption><em>Figure 1. A performance timeline overview of a Nokia 2 Android phone browsing on a page where excessive JavaScript monopolizes the main thread.</em></figcaption></figure>\n\n\n<p>While devices and the networks they navigate the web on are largely improving, we’re eating those gains as trends suggest. We need to use JavaScript <em>responsibly</em>. That begins with understanding <em>what</em> we’re building as well as <em>how</em> we’re building it.</p>\n\n\n<h2>The mindset of “sites” versus “apps”</h2>\n\n\n<p>Nomenclature can be strange in that we sometimes loosely identify things with terms that are inaccurate, yet their meanings are implicitly understood by everyone. Sometimes we overload the term “bee” to also mean “wasp”, even though the differences between bees and wasps are substantial. Those differences can motivate you to deal with each one differently. For instance, we’ll want to destroy a wasp nest, but because bees are highly beneficial and vulnerable insects, we may opt to relocate them.</p>\n\n\n<p>We can be just as fast and loose in interchanging the terms “website” and “web app”. The differences between them are less clear than those between yellowjackets and honeybees, but conflating them can bring about painful outcomes. The pain comes in the affordances we allow ourselves when something is merely a “web<em>site</em>” versus a fully-featured “web app.” If you’re making an informational website for a business, you’re less likely to lean on a powerful framework to manage changes in the DOM or implement client-side routing—at least, I <em>hope</em>. Using tools so ill-suited for the task would not only be a detriment to the people who use that site but arguably less productive.</p>\n\n\n<p>When we build a web <em>app</em>, though, <em>look out</em>. We’re installing packages which usher in hundreds—if not <em>thousands</em>—of dependencies, <a href=\"https://snyk.io/blog/malicious-code-found-in-npm-package-event-stream/\">some of which</a> we’re not sure are even safe. We’re also writing complicated configurations for module bundlers. In this frenzied, yet ubiquitous, sort of dev environment, it takes knowledge and vigilance to ensure what gets built is fast and accessible. If you doubt this, run <a href=\"https://docs.npmjs.com/cli/ls.html#prod--production\"><code>npm ls --prod</code></a> in your project’s root directory and <a href=\"https://gist.github.com/malchata/dae0a011033846e2cb44d315b0496f0d\">see if you recognize everything in that list</a>. Even if you do, that doesn’t account for third party scripts—of which I’m sure your site has at least a few.</p>\n\n\n<p>What we tend to forget is that the environment websites and web apps occupy is one and the same. Both are subject to the <em>same environmental pressures</em> that the large gradient of networks and devices impose. Those constraints don’t suddenly vanish when we decide to call what we build “apps”, nor do our users’ phones gain magical new powers when we do so.</p>\n\n\n<p>It’s our responsibility to evaluate who uses what we make, and accept that the conditions under which they access the internet can be different than what we’ve assumed. We need to know the purpose we’re trying to serve, and only <em>then</em> can we build something that admirably serves that purpose—<a href=\"https://css-tricks.com/simple-boring/\">even if it isn’t exciting to build</a>.</p>\n\n\n<p>That means reassessing our reliance on JavaScript and how the use of it—particularly to the exclusion of HTML and CSS—can tempt us to adopt unsustainable patterns which harm performance and accessibility.</p>\n\n\n<h2>Don’t let frameworks force you into unsustainable patterns</h2>\n\n\n<p>I’ve been witness to some strange discoveries in codebases when working with teams that depend on frameworks to help them be highly productive. One characteristic common among many of them is that poor accessibility and performance patterns often result. Take the React component below, for example:</p>\n\n\n<pre class=\"wp-block-code\"><code>import React, { Component } from "react";\nimport { validateEmail } from "helpers/validation";\n\nclass SignupForm extends Component {\n constructor (props) {\n super(props);\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.updateEmail = this.updateEmail.bind(this);\n this.state.email = "";\n }\n\n updateEmail (event) {\n this.setState({\n email: event.target.value\n });\n }\n\n handleSubmit () {\n // If the email checks out, submit\n if (validateEmail(this.state.email)) {\n // ...\n }\n }\n\n render () {\n return (\n <div>\n <span class="email-label">Enter your email:</span>\n <input type="text" id="email" onChange={this.updateEmail} />\n <button onClick={this.handleSubmit}>Sign Up</button>\n </div>\n );\n }\n}</code></pre>\n\n\n<p>There are some notable accessibility issues here:</p>\n\n\n<ol><li>A form that doesn’t use a <code><form></code> element is <em>not</em> a form. Indeed, you could paper over this by specifying <a href=\"https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Form_Role\"><code>role="form"</code></a> in the parent <code><div></code>, but if you’re building a form—and this <em>sure looks like one</em>—use a <code><form></code> element with the proper <code>action</code> and <code>method</code> attributes. The <code>action</code> attribute is crucial, as it ensures the form will still do <em>something</em> in the absence of JavaScript—provided the component is server-rendered, of course.</li><li>A <code><span></code> is not a substitute for a <code><label></code> element, which provides accessibility benefits <code><span></code>s don’t.</li><li>If we intend to do something on the client side prior to submitting a form, then we should move the action bound to the <code><button></code> element's <code>onClick</code> handler to the <code><form></code> element’s <code>onSubmit</code> handler.</li><li>Incidentally, why use JavaScript to validate an email address when HTML5 offers form validation controls in almost every browser back to IE 10? There’s an opportunity here to rely on the browser and use an <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email\">appropriate input type</a>, as well as the <a href=\"https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation#The_required_attribute\"><code>required</code></a> attribute—but be aware that getting this to work right with screen readers <a href=\"https://developer.paciellogroup.com/blog/2019/02/required-attribute-requirements/\">takes a little know-how</a>.</li><li>While not an accessibility issue, this component doesn't rely on any state or lifecycle methods, which means it can be refactored into a stateless functional component, which uses considerably less JavaScript than a full-fledged React component.</li></ol>\n\n\n<p>Knowing these things, we can refactor this component:</p>\n\n\n<pre class=\"wp-block-code\"><code>import React from "react";\n\nconst SignupForm = props => {\n const handleSubmit = event => {\n // Needed in case we're sending data to the server XHR-style\n // (but will still work if server-rendered with JS disabled).\n event.preventDefault();\n\n // Carry on...\n };\n \n return (\n <form method="POST" action="/signup" onSubmit={handleSubmit}>\n <label for="email" class="email-label">Enter your email:</label>\n <input type="email" id="email" required />\n <button>Sign Up</button>\n </form>\n );\n};</code></pre>\n\n\n<p>Not only is this component now more accessible, but it also uses less JavaScript. In a world that’s drowning in JavaScript, deleting lines of it should feel downright therapeutic. <a href=\"https://alistapart.com/article/paint-the-picture-not-the-frame\">The browser gives us so much for free</a>, and we should try to take advantage of that as often as possible.</p>\n\n\n<p>This is not to say that inaccessible patterns occur <em>only</em> when frameworks are used, but rather that a sole preference for JavaScript <em>will</em> eventually surface gaps in our understanding of HTML and CSS. These knowledge gaps will often result in mistakes we may not even be aware of. Frameworks can be useful tools that increase our productivity, but continuing education in core web technologies is essential to creating <em>usable</em> experiences, no matter what tools we choose to use.</p>\n\n\n<h2>Rely on the web platform and you’ll go far, fast</h2>\n\n\n<p>While we’re on the subject of frameworks, it must be said that the web platform is a formidable framework of its own. As the previous section showed, we’re better off when we can rely on established markup patterns and browser features. The alternative is to reinvent them, and invite all the pain such endeavors all but guarantee us, or worse: merely <em>assume</em> that the author of every JavaScript package we install has solved the problem comprehensively and thoughtfully.</p>\n\n\n<h3>SINGLE PAGE APPLICATIONS</h3>\n\n\n<p>One of the tradeoffs developers are quick to make is to adopt the single page application (SPA) model, even if it’s not a fit for the project. Yes, you <em>do</em> gain better perceived performance with the client-side routing of an SPA, but what do you <em>lose</em>? The browser’s own navigation functionality—albeit synchronous—provides a slew of benefits. For one, history is managed according to <a href=\"https://html.spec.whatwg.org/#the-history-interface\">a complex specification</a>. Users without JavaScript—be it by <a href=\"https://kryogenix.org/code/browser/everyonehasjs.html\">their own choice or not</a>—won’t lose access altogether. For SPAs to remain available when JavaScript is not, server-side rendering suddenly becomes a thing you have to consider.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Two series of screenshots. On the left, we have a blank screen for several seconds until the app appears after 5.24s. On the right, the basic components appear at 4ms and the site is fully usable at 5.16s.\" src=\"https://alistapart.com/wp-content/uploads/2019/04/fig2.png?w=960\" class=\"wp-image-6000653\"><figcaption><em>Figure 2. A comparison of an example app loading on a slow connection. The app on the left depends entirely upon JavaScript to render a page. The app on the right renders a response on the server, but then uses </em><a href=\"https://reactjs.org/docs/react-dom.html#hydrate\">client-side hydration</a><em> to attach components to the existing server-rendered markup.</em></figcaption></figure>\n\n\n<p>Accessibility is also harmed if a client-side router fails to let people know what content on the page has changed. This can leave those reliant on assistive technology to suss out what changes have occurred on the page, which can be an arduous task.</p>\n\n\n<p>Then there’s our old nemesis: overhead. Some client-side routers are very small, but when you <em>start</em> with <a href=\"https://bundlephobia.com/result?p=react-dom@16.8.2\">React</a>, <a href=\"https://bundlephobia.com/result?p=react-router@4.3.1\">a compatible router</a>, and possibly even <a href=\"https://bundlephobia.com/result?p=redux@4.0.1\">a state management library</a>, you’re accepting that there’s a certain amount of code you can never optimize away—approximately 135 KB in this case. Carefully consider what you’re building and whether a client side router is worth the tradeoffs you’ll inevitably make. Typically, you’re better off without one.</p>\n\n\n<p>If you’re concerned about the perceived navigation performance, you <em>could</em> lean on <a href=\"https://www.w3.org/TR/resource-hints/#prefetch-link-relation-type\"><code>rel=prefetch</code></a> to speculatively fetch documents on the same origin. This has a dramatic effect on improving perceived loading performance of pages, as the document is immediately available in the cache. Because prefetches are done at a low priority, they’re also less likely to contend with critical resources for bandwidth.</p>\n\n\n<figure class=\"wp-block-image\"><img alt=\"Screenshot showing a list of assets loaded on a webpage. 'writing/' is labeled as prefetched on initial navigation. This asset is then loaded in 2ms when actually requested by the user.\" src=\"https://alistapart.com/wp-content/uploads/2019/04/fig3.png?w=960\" class=\"wp-image-7171594\"><figcaption>Figure 3. The HTML for the writing/ URL is prefetched on the initial page. When the writing/ URL is requested by the user, the HTML for it is loaded instantaneously from the browser cache.</figcaption></figure>\n\n\n<p>The primary drawback with link prefetching is that you need to be aware that it <em>can</em> be potentially wasteful. <a href=\"https://github.com/GoogleChromeLabs/quicklink\">Quicklink</a>, a tiny link prefetching script from Google, mitigates this somewhat by checking if the current client is on a slow connection—or has <a href=\"https://support.google.com/chrome/answer/2392284?co=GENIE.Platform%3DAndroid&hl=en\">data saver mode</a> enabled—and avoids prefetching links on cross-origins by default.</p>\n\n\n<p><a href=\"https://adactio.com/articles/13796\">Service workers</a> are also hugely beneficial to perceived performance for returning users, whether we use client side routing or not—<a href=\"https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#for_best_performance_bypass_the_network_for_navigations\">provided you know the ropes</a>. <a href=\"https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker\">When we precache routes with a service worker</a>, we get many of the same benefits as link prefetching, but with a much greater degree of control over requests and responses. Whether you think of your site as an “app” or not, adding a service worker to it is perhaps one of the most responsible uses of JavaScript that exists today.</p>\n\n\n<h3>JAVASCRIPT ISN’T THE SOLUTION TO YOUR LAYOUT WOES</h3>\n\n\n<p>If we’re installing a package to solve a layout problem, proceed with caution and ask “what am I trying to accomplish?” CSS is <a href=\"https://twitter.com/rachelandrew/status/1088870059240505344\"><em>designed to do this job</em></a>, and requires no abstractions to use effectively. Most layout issues JavaScript packages attempt to solve, like <a href=\"https://www.npmjs.com/package/flexibility\">box placement, alignment, and sizing</a>, <a href=\"https://www.npmjs.com/package/shave\">managing text overflow</a>, and even <a href=\"https://www.npmjs.com/package/lost\">entire layout systems</a>, are solvable with CSS <em>today</em>. Modern layout engines like Flexbox and Grid are supported well enough that we shouldn’t need to start a project with any layout framework. CSS <em>is</em> the framework. When we have <a href=\"https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/\">feature queries</a>, progressively enhancing layouts to adopt new layout engines is suddenly <a href=\"https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/\">not so hard</a>.</p>\n\n\n<pre class=\"wp-block-code\"><code>/* Your mobile-first, non-CSS grid styles goes here */\n\n/* The @supports rule below is ignored by browsers that don't\n support CSS grid, _or_ don't support @supports. */\n@supports (display: grid) {\n /* Larger screen layout */\n @media (min-width: 40em) {\n /* Your progressively enhanced grid layout styles go here */\n }\n}</code></pre>\n\n\n<p>Using JavaScript solutions for layout and presentations problems is not new. It was something we did when we lied to ourselves in 2009 that every website had to look in IE6 exactly as it did in the more capable browsers of that time. If we’re still developing websites to look the same in every browser in 2019, we should reassess our development goals. There will <em>always</em> be some browser we’ll have to support that can’t do everything those modern, evergreen browsers can. Total visual parity on all platforms is not only a pursuit made in vain, it’s the principal foe of <a href=\"https://alistapart.com/article/understandingprogressiveenhancement\">progressive enhancement</a>.</p>\n\n\n<h2>I’m not here to kill JavaScript</h2>\n\n\n<p>Make no mistake, I have no ill will toward JavaScript. It’s given me a career and—if I’m being honest with myself—a source of enjoyment for over a decade. Like any long-term relationship, I learn more about it the more time I spend with it. It’s a mature, feature-rich language that only gets more capable and elegant with every passing year.</p>\n\n\n<p>Yet, there are times when I feel like JavaScript and I are at odds. I <em>am</em> critical of JavaScript. Or maybe more accurately, I’m critical of how we’ve developed a tendency to view it as a first resort to building for the web. As I pick apart yet another bundle not unlike a tangled ball of Christmas tree lights, it’s become clear that the web is <em>drunk</em> on JavaScript. We reach for it for almost everything, even when the occasion doesn’t call for it. Sometimes I wonder how vicious the hangover will be.</p>\n\n\n<p>In a series of articles to follow, I’ll be giving more practical advice to follow to stem the encroaching tide of excessive JavaScript and how we can wrangle it so that <em>what</em> we build for the web is usable—or at least <em>more</em> so—for everyone everywhere. Some of the advice will be preventative. Some will be mitigating “hair of the dog” measures. In either case, the outcomes will hopefully be the same. I believe that we all love the web and want to do right by it, but I want us to think about how to make it more resilient and inclusive for all.</p>\n\n\n<p><br></p>\n<img width=\"1\" alt=\"\" src=\"http://feeds.feedburner.com/~r/alistapart/main/~4/QhDNVVA5eoQ\" height=\"1\">","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/QhDNVVA5eoQ/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/responsible-javascript-part-1/","type":"text/html"}],"crawled":1569828677486,"published":1569828677486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d72eabb28:1d8d:90d684ff","originId":"https://www.raywenderlich.com/5685025-android-fall-sale-giveaway-winners-and-last-day-for-discount","fingerprint":"7e246132","title":"Android Fall Sale Giveaway Winners – and Last Day for Discount! [FREE]","updated":1569591049000,"summary":{"content":"Check out the winners of our Android Fall Sale giveaway — and don’t forget today is the last day to grab the discount on our any of our Android books and limited-time bundles!","direction":"ltr"},"alternate":[{"href":"https://www.raywenderlich.com/5685025-android-fall-sale-giveaway-winners-and-last-day-for-discount","type":"text/html"}],"crawled":1569591049000,"published":1569591049000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more","htmlUrl":"http://www.raywenderlich.com/feed"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Miscellaneous"],"originId":"https://nshipster.com/ios-13","recrawled":1569511972636,"updateCount":1,"fingerprint":"a9d09fb8","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:46322:18991ffa","updated":1569222000000,"author":"Mattt","summary":{"direction":"ltr","content":"<p>To mark last week’s release of iOS 13, we’re taking a look at some obscure (largely undocumented) APIs that you can now use in your apps.</p>"},"alternate":[{"href":"https://nshipster.com/ios-13/","type":"text/html"}],"crawled":1569418049005,"title":"iOS 13","published":1569222000000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"<p>Apple announced a lot at WWDC this year.\nDuring the conference and the days that followed,\na lot of us walked around in a daze,\nattempting to recover from have our minds <em>“hashtag-mindblown’d”</em> (#\uD83E\uDD2F).\nBut now a few months later,\nafter everything announced has launched\n<em>(<a rel=\"noopener noreferrer\" href=\"https://appleinsider.com/articles/19/09/16/what-apple-is-holding-back-for-ios-131\">well, almost everything</a>)</em>\nthose very same keynote announcements now elicit far different reactions:</p>\n<div>\n<p>Dark Mode?</p>\n<p>\uD83D\uDE0E <a href=\"https://nshipster.com/dark-mode/\">Already covered it</a>.</p>\n<p>SwiftUI?</p>\n<p>\uD83D\uDE44 <a href=\"https://nshipster.com/wwdc-2019/\">Give it another year or two</a>.</p>\n<p>Augmented Reality?</p>\n<p>\uD83E\uDD71 <a rel=\"noopener noreferrer\" href=\"http://appleinsider.com/articles/17/01/09/rumor-apple-working-with-carl-zeiss-on-ar-glasses-to-debut-in-2018\">Wake us up when Apple announces AR glasses.</a></p>\n\n</div>\n<p>Although the lion’s share of attention\nhas been showered on the aforementioned features,\nnot nearly enough coverage has been given to the rest of iOS 13 —\nand that’s a shame,\nbecause this release is among the most exciting in terms of new functionality\nand satisfying in terms of improving existing functionality.</p>\n<p>So to mark last week’s release of iOS 13,\nwe’re taking a look at some obscure (largely undocumented) APIs\nthat you can now use in your apps.\nWe’ve scavenged the best bits out of the\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/ios_ipados_release_notes/ios_13_release_notes\">iOS 13 Release Notes</a>\n<a rel=\"noopener noreferrer\" href=\"http://codeworkshop.net/objc-diff/sdkdiffs/ios/13.0/\">API diffs</a>,\nand now present them to you.</p>\n<p>Here are some of our favorite things you can do starting in iOS 13:</p>\n<hr>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#generate-rich-representations-of-urls\"></a>Generate Rich Representations of URLs</h2>\n<p>New in iOS 13,\nthe\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/LinkPresentation\">LinkPresentation framework</a>\nprovides a convenient, built-in way to replicate the\nrich previews of URLs you see in Messages.\nIf your app has any kind of chat or messaging functionality,\nyou’ll definitely want to check this out.</p>\n<p>Rich previews of URLs have a rich history\ngoing at least as far back as the early ’00s,\nwith the spread of\n<a rel=\"noopener noreferrer\" href=\"http://microformats.org\">Microformats</a>\nby semantic web pioneers,\nand early precursors to Digg and Reddit\nusing <a rel=\"noopener noreferrer\" href=\"https://github.com/xsuchy/khtml2png\">khtml2png</a>\nto generate thumbnail images of web pages.\nFast forward to 2010,\nwith the rise of social media and user-generated content,\nwhen Facebook created the <a rel=\"noopener noreferrer\" href=\"https://ogp.me\">OpenGraph protocol</a>\nto allow web publishers to customize how their pages looked\nwhen posted on the Newsfeed.</p>\n<p>These days,\nmost websites reliably have OpenGraph <code><meta></code> tags on their site\nthat provide a summary of their content for\nsocial networks, search engines, and anywhere else that links are trafficked.\nFor example,\nhere’s what you would see if you did “View Source” for this very webpage:</p>\n<pre data-lang=\"HTML\"><code><span><meta</span> <span>property=</span><span>"og:site_name"</span> <span>content=</span><span>"NSHipster"</span> <span>/></span>\n <span><meta</span> <span>property=</span><span>"og:image"</span> <span>content=</span><span>"https://nshipster.com/logo.png"</span> <span>/></span>\n <span><meta</span> <span>property=</span><span>"og:type"</span> <span>content=</span><span>"article"</span> <span>/></span>\n <span><meta</span> <span>property=</span><span>"og:title"</span> <span>content=</span><span>"i<wbr>OS 13"</span> <span>/></span>\n <span><meta</span> <span>property=</span><span>"og:url"</span> <span>content=</span><span>"https://nshipster.com/ios-13/"</span> <span>/></span>\n <span><meta</span> <span>property=</span><span>"og:description"</span> <span>content=</span><span>"To mark last week's release of i<wbr>OS 13, we're taking a look at some obscure (largely undocumented) APIs that you can now use in your apps."</span> <span>/></span>\n </code></pre>\n<p>If you wanted to consume this information in your app,\nyou can now use the LinkPresentation framework’s <code>LPMetadata<wbr>Provider</code> class\nto fetch the metadata and optionally construct a representation:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Link<wbr>Presentation</span>\n <span>let</span> <span>metadata<wbr>Provider</span> <span>=</span> <span>LPMetadata<wbr>Provider</span><span>()</span>\n <span>let</span> <span>url</span> <span>=</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"https://nshipster.com/ios-13/"</span><span>)</span><span>!</span>\n <span>let</span> <span>metadata<wbr>Provider</span> <span>=</span> <span>LPMetadata<wbr>Provider</span><span>()</span>\n <span>metadata<wbr>Provider</span><span>.</span><span>start<wbr>Fetching<wbr>Metadata</span><span>(</span><span>for</span><span>:</span> <span>url</span><span>)</span> <span>{</span> <span>[</span><span>weak</span> <span>self</span><span>]</span> <span>metadata</span><span>,</span> <span>error</span> <span>in</span>\n <span>guard</span> <span>let</span> <span>metadata</span> <span>=</span> <span>metadata</span> <span>else</span> <span>{</span> <span>return</span> <span>}</span>\n <span>let</span> <span>link<wbr>View</span> <span>=</span> <span>LPLink<wbr>View</span><span>(</span><span>metadata</span><span>:</span> <span>metadata</span><span>)</span>\n <span>self</span><span>?</span><span>.</span><span>view</span><span>.</span><span>add<wbr>Subview</span><span>(</span><span>link<wbr>View</span><span>)</span>\n <span>}</span>\n </code></pre>\n<p>After setting appropriate constraints\n(and perhaps a call to <code>size<wbr>To<wbr>Fit()</code>),\nyou’ll get the following,\nwhich the user can tap to preview the linked webpage:</p>\n<picture>\n<source srcset=\"https://nshipster.com/assets/ios-13-link-view--dark-44ec415e21baf1b4ece65f0a35477ea8b1e15d0c83340f3c0f9642846d4ffbdd.png\" media=\"(prefers-color-scheme: dark)\">\n<img alt=\"iOS 13 Link View\" width=\"300\" src=\"https://nshipster.com/assets/ios-13-link-view--light-2d5b91cda2d73c97f418c2af23a2445d12b1b81b863a433a08e56ce40ea60deb.png\">\n</picture>\n<aside>\n<p>In the <code>start<wbr>Fetching<wbr>Metadata(for:)</code> completion handler,\nyou can inspect and mutate the metadata retrieved from the server.\nYou might take this opportunity to add default images / videos\nfor pages that don’t have them,\ntranslate text into one of the user’s preferred languages,\nor censor explicit text and media.</p>\n</aside>\n<p>Alternatively,\nif you already have the metadata in-app,\nor otherwise can’t or don’t want to fetch remotely,\nyou can construct an <code>LPLink<wbr>Metadata</code> directly:</p>\n<pre data-lang=\"Swift\"><code><span>let</span> <span>metadata</span> <span>=</span> <span>LPLink<wbr>Metadata</span><span>()</span>\n <span>metadata</span><span>.</span><span>url</span> <span>=</span> <span>url</span>\n <span>metadata</span><span>.</span><span>title</span> <span>=</span> <span>"i<wbr>OS 13"</span>\n <span>metadata</span><span>.</span><span>icon<wbr>Provider</span> <span>=</span> <var>...</var>\n <span>let</span> <span>link<wbr>View</span> <span>=</span> <span>LPLink<wbr>View</span><span>(</span><span>metadata</span><span>:</span> <span>metadata</span><span>)</span>\n </code></pre>\n<aside>\n<p><code>LPMetadata<wbr>Provider</code> works as advertised on iOS,\nbut macOS clients must have the <code>com.apple.security.network.client</code> entitlement\nin order to fetch metadata from remote URLs.</p>\n</aside>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#perform-on-device-speech-recognition\"></a>Perform On-Device Speech Recognition</h2>\n<p><a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/speech/sfspeechrecognizer\"><code>SFSpeech<wbr>Recognizer</code></a>\ngets a major upgrade in iOS 13 —\nmost notably for its added support for on-device speech recognition.</p>\n<p>Previously,\ntranscription required an internet connection\nand was restricted to a maximum of 1-minute duration\nwith daily limits for requests.\nBut now,\nyou can do speech recognition completely on-device and offline,\nwith no limitations.\nThe only caveats are that\noffline transcription isn’t as good as what you’d get with a server connection,\nand is only available for certain languages.</p>\n<p>To determine whether offline transcription is available for the user’s locale,\ncheck the <code>SFSpeech<wbr>Recognizer</code> property\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/speech/sfspeechrecognizer/3152604-supportsondevicerecognition\"><code>supports<wbr>On<wbr>Device<wbr>Recognition</code></a>.\nAt the time of publication, the list of supported languages are as follows:</p>\n<figure>\n<dl>\n<dt>English</dt>\n<dd>United States (<code>en-US</code>)</dd>\n<dd>Canada (<code>en-CA</code>)</dd>\n<dd>Great Britain (<code>en-GB</code>)</dd>\n<dd>India (<code>en-IN</code>)</dd>\n<dt>Spanish</dt>\n<dd>United States (<code>es-US</code>)</dd>\n<dd>Mexico (<code>es-MX</code>)</dd>\n<dd>Spain (<code>es-ES</code>)</dd>\n<dt>Italian</dt>\n<dd>Italy (<code>it-IT</code>)</dd>\n<dt>Portuguese</dt>\n<dd>Brazil (<code>pt-BR</code>)</dd>\n<dt>Russian</dt>\n<dd>Russia (<code>ru-RU</code>)</dd>\n<dt>Turkish</dt>\n<dd>Turkey (<code>tr-TR</code>)</dd>\n<dt>Chinese</dt>\n<dd>Mandarin (<code>zh-cmn</code>)</dd>\n<dd>Cantonese (<code>zh-yue</code>)</dd>\n</dl>\n</figure>\n<aside>\n<p>According to the iOS 13 release notes:\n“The <code>supports<wbr>On<wbr>Device<wbr>Recognition</code> property always returns <code>false</code>\nthe first time it’s accessed.\nAfter a few seconds,\naccessing it again returns the correct value.”</p>\n</aside>\n<p>But that’s not all for speech recognition in iOS 13!\n<code>SFSpeech<wbr>Recognizer</code> now provides information including\nspeaking rate and average pause duration,\nas well as voice analytics features like\n<dfn>jitter</dfn> (variations in pitch) and\n<dfn>shimmer</dfn> (variations in amplitude).</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Speech</span>\n <span>guard</span> <span>SFSpeech<wbr>Recognizer</span><span>.</span><span>authorization<wbr>Status</span><span>()</span> <span>==</span> <span>.</span><span>authorized</span>\n <span>let</span> <span>recognizer</span> <span>=</span> <span>SFSpeech<wbr>Recognizer</span><span>()</span>\n <span>else</span> <span>{</span>\n <span>fatal<wbr>Error</span><span>()</span>\n <span>}</span>\n <span>let</span> <span>url</span><span>:</span> <span>URL</span> <span>=</span> <var>...</var>\n <span>let</span> <span>request</span> <span>=</span> <span>SFSpeech<wbr>URLRecognition<wbr>Request</span><span>(</span><span>url</span><span>:</span> <span>url</span><span>)</span>\n <span>recognizer</span><span>.</span><span>recognition<wbr>Task</span><span>(</span><span>with</span><span>:</span> <span>request</span><span>)</span> <span>{</span> <span>(</span><span>result</span><span>,</span> <span>error</span><span>)</span> <span>in</span>\n <span>guard</span> <span>let</span> <span>result</span> <span>=</span> <span>result</span> <span>else</span> <span>{</span> <span>return</span> <span>}</span>\n <span>for</span> <span>segment</span> <span>in</span> <span>result</span><span>.</span><span>best<wbr>Transcription</span><span>.</span><span>segments</span> <span>{</span>\n <span>guard</span> <span>let</span> <span>voice<wbr>Analytics</span> <span>=</span> <span>segment</span><span>.</span><span>voice<wbr>Analytics</span> <span>else</span> <span>{</span> <span>continue</span> <span>}</span>\n <span>let</span> <span>pitch</span> <span>=</span> <span>voice<wbr>Analytics</span><span>.</span><span>pitch</span><span>.</span><span>acoustic<wbr>Feature<wbr>Value<wbr>Per<wbr>Frame</span>\n <span>let</span> <span>voicing</span> <span>=</span> <span>voice<wbr>Analytics</span><span>.</span><span>voicing</span><span>.</span><span>acoustic<wbr>Feature<wbr>Value<wbr>Per<wbr>Frame</span>\n <span>let</span> <span>jitter</span> <span>=</span> <span>voice<wbr>Analytics</span><span>.</span><span>jitter</span><span>.</span><span>acoustic<wbr>Feature<wbr>Value<wbr>Per<wbr>Frame</span>\n <span>let</span> <span>shimmer</span> <span>=</span> <span>voice<wbr>Analytics</span><span>.</span><span>shimmer</span><span>.</span><span>acoustic<wbr>Feature<wbr>Value<wbr>Per<wbr>Frame</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n<p>Information about pitch and voicing and other features\ncould be used by your app\n(perhaps <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/createml/mlsoundclassifier\">in coordination with CoreML</a>)\nto differentiate between speakers\nor determine subtext from a speaker’s inflection.</p>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#send-and-receive-web-socket-messages\"></a>Send and Receive Web Socket Messages</h2>\n<p>Speaking of the Foundation URL Loading System,\nwe now have native support for\nsomething that’s been at the top of our wish list for many years:\n<dfn>web sockets</dfn>.</p>\n<p>Thanks to the new\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/urlsessionwebsockettask\"><code>URLSession<wbr>Web<wbr>Socket<wbr>Task</code></a> class\nin iOS 13,\nyou can now incorporate real-time communications in your app\nas easily and reliably as sending HTTP requests —\nall without any third-party library or framework:</p>\n<pre data-lang=\"Swift\"><code><span>let</span> <span>url</span> <span>=</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"wss://<var>...</var>"</span><span>)</span><span>!</span>\n <span>let</span> <span>web<wbr>Socket<wbr>Task</span> <span>=</span> <span>URLSession</span><span>.</span><span>shared</span><span>.</span><span>web<wbr>Socket<wbr>Task</span><span>(</span><span>with</span><span>:</span> <span>url</span><span>)</span>\n <span>web<wbr>Socket<wbr>Task</span><span>.</span><span>resume</span><span>()</span>\n <span>// Send one message</span>\n <span>let</span> <span>message</span><span>:</span> <span>URLSession<wbr>Web<wbr>Socket<wbr>Task</span><span>.</span><span>Message</span> <span>=</span> <span>.</span><span>string</span><span>(</span><span>"Hello, world!"</span><span>)</span>\n <span>web<wbr>Socket<wbr>Task</span><span>.</span><span>send</span><span>(</span><span>message</span><span>)</span> <span>{</span> <span>error</span> <span>in</span>\n <var>...</var>\n <span>}</span>\n <span>// Receive one message</span>\n <span>web<wbr>Socket<wbr>Task</span><span>.</span><span>receive</span> <span>{</span> <span>result</span> <span>in</span>\n <span>guard</span> <span>case</span> let .success(message) = result else { return }\n <var>...</var>\n <span>}</span>\n <span>// Eventually...</span>\n <span>web<wbr>Socket<wbr>Task</span><span>.</span><span>cancel</span><span>(</span><span>with</span><span>:</span> <span>.</span><span>going<wbr>Away</span><span>,</span> <span>reason</span><span>:</span> <span>nil</span><span>)</span>\n </code></pre>\n<aside>\n<p>For lower-level control over web sockets,\nincluding both client and server support,\ncheck out the\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/network\">Network framework</a>.</p>\n</aside>\n<p>For years now,\nnetworking has been probably the fastest-moving part\nof the whole Apple technology stack.\nEach WWDC,\nthere’s so much to talk about that\nthat they routinely have to break their content across two separate sessions.\n2019 was no exception,\nand we highly recommend that you take some time to check out this year’s\n“Advances in Networking” sessions\n(<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/videos/play/wwdc2019/712/\">Part 1</a>,\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/videos/play/wwdc2019/713\">Part 2</a>).</p>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#do-more-with-maps\"></a>Do More With Maps</h2>\n<p>MapKit is another part of Apple SDKs\nthat’s consistently had a strong showing at WWDC year after year.\nAnd it’s often the small touches that make the most impact in our day-to-day.</p>\n<p>For instance,\nthe new\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/mapkit/mkmapview/cameraboundary\"><code>MKMap<wbr>View.Camera<wbr>Boundary</code></a> API\nin iOS 13\nmakes it much easier to constrain a map’s viewport to a particular region\nwithout locking it down completely.</p>\n<pre data-lang=\"Swift\"><code><span>let</span> <span>region</span> <span>=</span> <span>MKCoordinate<wbr>Region</span><span>(</span><span>center</span><span>:</span> <span>map<wbr>View</span><span>.</span><span>center</span><span>,</span>\n <span>latitudinal<wbr>Meters</span><span>:</span> <span>1000</span><span>,</span>\n <span>longitudinal<wbr>Meters</span><span>:</span> <span>1000</span><span>)</span>\n <span>map<wbr>View</span><span>.</span><span>camera<wbr>Boundary</span> <span>=</span> <span>MKMap<wbr>View</span><span>.</span><span>Camera<wbr>Boundary</span><span>(</span><span>coordinate<wbr>Region</span><span>:</span> <span>region</span><span>)</span>\n </code></pre>\n<p>And with the new\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/mapkit/mkpointofinterestfilter\"><code>MKPoint<wbr>Of<wbr>Interest<wbr>Filter</code></a> API,\nyou can now customize the appearance of map views\nto show only certain kinds of points of interest\n<em>(whereas previously it was an <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/mapkit/mkmapview/1452102-showspointsofinterest\">all-or-nothing</a> proposition)</em>.</p>\n<pre data-lang=\"Swift\"><code><span>let</span> <span>filter</span> <span>=</span> <span>MKPoint<wbr>Of<wbr>Interest<wbr>Filter</span><span>(</span><span>including</span><span>:</span> <span>[</span><span>.</span><span>cafe</span><span>])</span>\n <span>map<wbr>View</span><span>.</span><span>point<wbr>Of<wbr>Interest<wbr>Filter</span> <span>=</span> <span>filter</span> <span>// only show cafés</span>\n </code></pre>\n<p>Finally,\nwith <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/mapkit/mkgeojsondecoder\"><code>MKGeo<wbr>JSONDecoder</code></a>,\nwe now have a built-in way to pull in <a rel=\"noopener noreferrer\" href=\"https://geojson.org\">GeoJSON</a> shapes\nfrom web services and other data sources.</p>\n<pre data-lang=\"Swift\"><code><span>let</span> <span>decoder</span> <span>=</span> <span>MKGeo<wbr>JSONDecoder</span><span>()</span>\n <span>if</span> <span>let</span> <span>url</span> <span>=</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"<var>...</var>"</span><span>),</span>\n <span>let</span> <span>data</span> <span>=</span> <span>try</span><span>?</span> <span>Data</span><span>(</span><span>contents<wbr>Of<wbr>URL</span><span>:</span> <span>url</span><span>),</span>\n <span>let</span> <span>geo<wbr>JSONObjects</span> <span>=</span> <span>try</span><span>?</span> <span>decoder</span><span>.</span><span>decode</span><span>(</span><span>data</span><span>)</span> <span>{</span>\n <span>for</span> <span>case</span> let overlay as MKOverlay in geo<wbr>JSONObjects {\n <span>map<wbr>View</span><span>.</span><span>add<wbr>Overlay</span><span>(</span><span>overlay</span><span>)</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#keep-promises-in-javascript\"></a>Keep Promises in JavaScript</h2>\n<p>If you enjoyed our article about <a href=\"https://nshipster.com/javascriptcore/\">JavaScriptCore</a>,\nyou’d be thrilled to know that <code>JSValue</code> objects\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/javascriptcore/jsvalue/3335012-init\">now natively support promises</a>.</p>\n<p>For the uninitiated:\nin JavaScript, a <a rel=\"noopener noreferrer\" href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise\"><code>Promise</code></a>\nis an object that represents the eventual completion (or rejection)\nof an asynchronous operation\nand its resulting value.\nPromises are a mainstay of modern JS development —\nperhaps most notably within the <a rel=\"noopener noreferrer\" href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\"><code>fetch</code></a> API.</p>\n<p>Another addition to JavaScriptCore in iOS 13\nis support for <a rel=\"noopener noreferrer\" href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol\">symbols</a>\n(no, not <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/design/human-interface-guidelines/sf-symbols/\">those symbols</a>).\nFor more information about\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/javascriptcore/jsvalue/3042804-init\"><code>init(new<wbr>Symbol<wbr>From<wbr>Description:in:)</code></a>,\n<del>refer to the docs</del>\n<ins>just guess how to use it.<ins></ins></ins></p>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#respond-to-objective-c-associated-objects\"></a>Respond to Objective-C Associated Objects (?)</h2>\n<p>On a lark,\nwe decided to see if there was anything new in Objective-C this year\nand were surprised to find out about\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/objectivec/objc_hook_setassociatedobject?language=objc\">objc_setHook_setAssociatedObject</a>.\nAgain, we don’t have much to go on except the declaration,\nbut it looks like you can now configure a block to execute when\nan <a href=\"https://nshipster.com/associated-objects/\">associated object</a> is set.\nFor anyone still deep in the guts of the Objective-C runtime,\nthis sounds like it could be handy.</p>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#tame-activity-items\"></a>Tame Activity Items (?)</h2>\n<p>On the subject of missing docs:\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uiactivityitemsconfiguration\"><code>UIActivity<wbr>Items<wbr>Configuration</code></a>\n<em>seems</em> like a compelling option for managing\nactions in the new iOS 13 share sheets,\nbut we don’t really know where to start…</p>\n<p><img width=\"400\" alt=\"iOS 13 Share Actions\" src=\"https://nshipster.com/assets/ios-13-share-actions-8f74463802929bb95567c7c9e7f295512fe8f4463d8cd59821fa4af578e67dc7.png\"></p>\n<p>Shame that we don’t have the information we need to take advantage of this yet.</p>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#format-lists-and-relative-times\"></a>Format Lists and Relative Times</h2>\n<p>As discussed in <a href=\"https://nshipster.com/formatter/\">a previous article</a>,\niOS 13 brings two new formatters to Foundation:\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/listformatter\"><code>List<wbr>Formatter</code></a>\nand\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/relativedatetimeformatter\"><code>Relative<wbr>Date<wbr>Time<wbr>Formatter</code></a>.</p>\n<p>Not to harp on about this,\nbut both of them are <em>still</em> undocumented,\nso if you want to learn more,\nwe’d recommend checking out that article from July.\nOr, if you’re in a rush\nhere’s a quick example demonstrating how to use both of them together:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>let</span> <span>relative<wbr>Date<wbr>Time<wbr>Formatter</span> <span>=</span> <span>Relative<wbr>Date<wbr>Time<wbr>Formatter</span><span>()</span>\n <span>relative<wbr>Date<wbr>Time<wbr>Formatter</span><span>.</span><span>date<wbr>Time<wbr>Style</span> <span>=</span> <span>.</span><span>named</span>\n <span>let</span> <span>list<wbr>Formatter</span> <span>=</span> <span>List<wbr>Formatter</span><span>()</span>\n <span>list<wbr>Formatter</span><span>.</span><span>string</span><span>(</span><span>from</span><span>:</span> <span>[</span>\n <span>relative<wbr>Date<wbr>Time<wbr>Formatter</span><span>.</span><span>localized<wbr>String</span><span>(</span><span>from</span><span>:</span> <span>Date<wbr>Components</span><span>(</span><span>day</span><span>:</span> <span>-</span><span>1</span><span>)),</span>\n <span>relative<wbr>Date<wbr>Time<wbr>Formatter</span><span>.</span><span>localized<wbr>String</span><span>(</span><span>from</span><span>:</span> <span>Date<wbr>Components</span><span>(</span><span>day</span><span>:</span> <span>0</span><span>)),</span>\n <span>relative<wbr>Date<wbr>Time<wbr>Formatter</span><span>.</span><span>localized<wbr>String</span><span>(</span><span>from</span><span>:</span> <span>Date<wbr>Components</span><span>(</span><span>day</span><span>:</span> <span>1</span><span>))</span>\n <span>])</span> <span>// "yesterday, today, and tomorrow"</span>\n </code></pre>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#track-the-progress-of-enqueued-operations\"></a>Track the Progress of Enqueued Operations</h2>\n<p>Starting in iOS 13,\n<code>Operation<wbr>Queue</code> now has a\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/operationqueue/3172535-progress\"><code>progress</code></a> property.</p>\n<p>Granted,\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/progress\"><code>(NS)Progress</code></a>\nobjects aren’t the most straightforward or convenient things to work with\n(we’ve been meaning to write an article about them at some point),\nbut they have a complete and well-considered API,\nand even have some convenient slots in app frameworks.</p>\n<p>For example,\ncheck out how easy it is to wire up a <code>UIProgress<wbr>View</code>\nto display the live-updating progress of an operation queue\nby way of its <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uiprogressview/1619840-observedprogress\"><code>observed<wbr>Progress</code> property</a>:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>UIKit</span>\n <span>fileprivate</span> <span>class</span> <span>Download<wbr>Operation</span><span>:</span> <span>Operation</span> <span>{</span> <var>...</var> <span>}</span>\n <span>class</span> <span>View<wbr>Controller</span><span>:</span> <span>UIView<wbr>Controller</span> <span>{</span>\n <span>private</span> <span>let</span> <span>operation<wbr>Queue</span> <span>=</span> <span>{</span>\n <span>let</span> <span>queue</span> <span>=</span> <span>Operation<wbr>Queue</span><span>()</span>\n <span>queue</span><span>.</span><span>max<wbr>Concurrent<wbr>Operation<wbr>Count</span> <span>=</span> <span>1</span>\n <span>}()</span>\n <span>@IBOutlet</span> <span>private</span> <span>var</span> <span>progress<wbr>View</span><span>:</span> <span>UIProgress<wbr>View</span><span>!</span>\n <span>@IBAction</span> <span>private</span> <span>func</span> <span>start<wbr>Downloading</span><span>(</span><span>_</span> <span>sender</span><span>:</span> <span>Any</span><span>)</span> <span>{</span>\n <span>operation<wbr>Queue</span><span>.</span><span>cancel<wbr>All<wbr>Operations</span><span>()</span>\n <span>progress<wbr>View</span><span>.</span><span>observed<wbr>Progress</span> <span>=</span> <span>operation<wbr>Queue</span><span>.</span><span>progress</span>\n <span>for</span> <span>url</span> <span>in</span> <span>[</span><var>...</var><span>]</span> <span>{</span>\n <span>let</span> <span>operation</span> <span>=</span> <span>Download<wbr>Operation</span><span>(</span><span>url</span><span>:</span> <span>url</span><span>)</span>\n <span>operation<wbr>Queue</span><span>.</span><span>add<wbr>Operation</span><span>(</span><span>operation</span><span>)</span>\n <span>}</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n<p>It’s also worth mentioning a few other APIs coming to in 13,\nlike\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/operationqueue/3329364-schedule\"><code>schedule(after:interval:tolerance:options:_:)</code></a>,\nwhich clues <code>Operation<wbr>Queue</code> into the new\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/combine\">Combine framework</a>\nin a nice way,\nand <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/operationqueue/3172534-addbarrierblock\"><code>add<wbr>Barrier<wbr>Block(_:)</code></a>,\nwhich presumably works like\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/dispatch/1452917-dispatch_barrier_sync?language=objc\">Dispatch barrier blocks</a>\n(though without documentation, it’s anyone’s guess).</p>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#manage-background-tasks-with-ease\"></a>Manage Background Tasks with Ease</h2>\n<p>One of the things that often differentiates category-defining apps from their competitors\nis their use of background tasks\nto make sure the app is fully synced and updated for the next time\nit enters the foreground.</p>\n<p>iOS 7 was the first release to provide\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uiapplication/1623051-beginbackgroundtask\">an official API for scheduling background tasks</a>\n<em>(though developers had employed various creative approaches prior to this)</em>.\nBut in the intervening years,\nmultiple factors —\nfrom an increase in iOS app capabilities and complexity\nto growing emphasis in performance, efficiency, and privacy for apps —\nhave created a need for a more comprehensive solution.</p>\n<p>That solution came to iOS 13 by way of the new\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/backgroundtasks?language=objc\">BackgroundTasks framework</a>.</p>\n<p>As described in this year’s WWDC session\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/videos/play/wwdc2019/707\">“Advances in App Background Execution”</a>,\nthe framework distinguishes between two broad classes of background tasks:</p>\n<ul>\n<li>\n<dfn>app refresh tasks</dfn>:\nshort-lived tasks that keep an app up-to-date throughout the day</li>\n<li>\n<dfn>background processing tasks</dfn>:\nlong-lived tasks for performing deferrable maintenance tasks</li>\n</ul>\n<p>The WWDC session and the accompanying sample code project\ndo a great job of explaining how to incorporate both of these\ninto your app.\nBut if you want the quick gist of it,\nhere’s a small example of an app that schedules periodic refreshes\nfrom a web server:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>UIKit</span>\n <span>import</span> <span>Background<wbr>Tasks</span>\n <span>fileprivate</span> <span>let</span> <span>background<wbr>Task<wbr>Identifier</span> <span>=</span> <span>"com.nshipster.example.task.refresh"</span>\n <span>@UIApplication<wbr>Main</span>\n <span>class</span> <span>App<wbr>Delegate</span><span>:</span> <span>UIResponder</span><span>,</span> <span>UIApplication<wbr>Delegate</span> <span>{</span>\n <span>var</span> <span>window</span><span>:</span> <span>UIWindow</span><span>?</span>\n <span>lazy</span> <span>var</span> <span>background<wbr>URLSession</span> <span>=</span> <span>{</span>\n <span>let</span> <span>configuration</span> <span>=</span> <span>URLSession<wbr>Configuration</span><span>.</span><span>background</span><span>(</span><span>with<wbr>Identifier</span><span>:</span> <span>"com.nshipster.url-session.background"</span><span>)</span>\n <span>configuration</span><span>.</span><span>discretionary</span> <span>=</span> <span>true</span>\n <span>configuration</span><span>.</span><span>timeout<wbr>Interval<wbr>For<wbr>Request</span> <span>=</span> <span>30</span>\n <span>return</span> <span>URLSession</span><span>(</span><span>configuration</span><span>:</span> <span>configuration</span><span>,</span> <span>delegate</span><span>:</span> <var>...</var><span>,</span> <span>delegate<wbr>Queue</span><span>:</span> <var>...</var><span>)</span>\n <span>}</span>\n <span>func</span> <span>application</span><span>(</span><span>_</span> <span>application</span><span>:</span> <span>UIApplication</span><span>,</span> <span>did<wbr>Finish<wbr>Launching<wbr>With<wbr>Options</span> <span>launch<wbr>Options</span><span>:</span> <span>[</span><span>UIApplication</span><span>.</span><span>Launch<wbr>Options<wbr>Key</span><span>:</span> <span>Any</span><span>]?)</span> <span>-></span> <span>Bool</span> <span>{</span>\n <var>...</var>\n <span>BGTask<wbr>Scheduler</span><span>.</span><span>shared</span><span>.</span><span>register</span><span>(</span><span>for<wbr>Task<wbr>With<wbr>Identifier</span><span>:</span> <span>background<wbr>Task<wbr>Identifier</span><span>,</span> <span>using</span><span>:</span> <span>nil</span><span>)</span> <span>{</span> <span>task</span> <span>in</span>\n <span>self</span><span>.</span><span>handle<wbr>App<wbr>Refresh</span><span>(</span><span>task</span><span>:</span> <span>task</span> <span>as!</span> <span>BGApp<wbr>Refresh<wbr>Task</span><span>)</span>\n <span>}</span>\n <span>return</span> <span>true</span>\n <span>}</span>\n <span>func</span> <span>application<wbr>Did<wbr>Enter<wbr>Background</span><span>(</span><span>_</span> <span>application</span><span>:</span> <span>UIApplication</span><span>)</span> <span>{</span>\n <span>schedule<wbr>App<wbr>Refresh</span><span>()</span>\n <span>}</span>\n <span>func</span> <span>schedule<wbr>App<wbr>Refresh</span><span>()</span> <span>{</span>\n <span>let</span> <span>request</span> <span>=</span> <span>BGApp<wbr>Refresh<wbr>Task<wbr>Request</span><span>(</span><span>identifier</span><span>:</span> <span>background<wbr>Task<wbr>Identifier</span><span>)</span>\n <span>request</span><span>.</span><span>earliest<wbr>Begin<wbr>Date</span> <span>=</span> <span>Date</span><span>(</span><span>time<wbr>Interval<wbr>Since<wbr>Now</span><span>:</span> <span>60</span> <span>*</span> <span>10</span><span>)</span>\n <span>do</span> <span>{</span>\n <span>try</span> <span>BGTask<wbr>Scheduler</span><span>.</span><span>shared</span><span>.</span><span>submit</span><span>(</span><span>request</span><span>)</span>\n <span>}</span> <span>catch</span> <span>{</span>\n <span>print</span><span>(</span><span>"Couldn't schedule app refresh: </span><span>\\(</span><span>error</span><span>)</span><span>"</span><span>)</span>\n <span>}</span>\n <span>}</span>\n <span>func</span> <span>handle<wbr>App<wbr>Refresh</span><span>(</span><span>task</span><span>:</span> <span>BGApp<wbr>Refresh<wbr>Task</span><span>)</span> <span>{</span>\n <span>schedule<wbr>App<wbr>Refresh</span><span>()</span>\n <span>let</span> <span>url</span><span>:</span> <span>URL</span> <span>=</span> <var>...</var>\n <span>var</span> <span>data<wbr>Task</span> <span>=</span> <span>background<wbr>URLSession</span><span>.</span><span>data<wbr>Task</span><span>(</span><span>with</span><span>:</span> <span>url</span><span>)</span> <span>{</span> <span>(</span><span>data</span><span>,</span> <span>response</span><span>,</span> <span>error</span><span>)</span> <span>in</span>\n <var>...</var>\n <span>let</span> <span>success</span> <span>=</span> <span>(</span><span>200</span><span>..<</span><span>300</span><span>)</span><span>.</span><span>contains</span><span>(</span><span>response</span><span>?</span><span>.</span><span>status<wbr>Code</span><span>)</span>\n <span>task</span><span>.</span><span>set<wbr>Task<wbr>Completed</span><span>(</span><span>success</span><span>:</span> <span>success</span><span>)</span>\n <span>}</span>\n <span>task</span><span>.</span><span>expiration<wbr>Handler</span> <span>=</span> <span>{</span>\n <span>data<wbr>Task</span><span>.</span><span>cancel</span><span>()</span>\n <span>}</span>\n <span>data<wbr>Task</span><span>.</span><span>resume</span><span>()</span>\n <span>}</span>\n <var>...</var>\n <span>}</span>\n </code></pre>\n<aside>\n<p>The previous way of doing background updates —\nnamely,\n<code>UIApplication.set<wbr>Minimum<wbr>Background<wbr>Fetch<wbr>Interval(_:)</code> and\n<code>UIApplication<wbr>Delegate.application(_:perform<wbr>Fetch<wbr>With<wbr>Completion<wbr>Handler:)</code> —\nare now deprecated in iOS 13.</p>\n</aside>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#annotate-text-content-types-for-better-accessibility\"></a>Annotate Text Content Types for Better Accessibility</h2>\n<p>You know how frustrating it is to hear some people read out URLs?\n(<em lang=\"en-fonipa\">“eɪʧ ti ti pi ˈkoʊlən slæʃ slæʃ ˈdʌbəlju ˈdʌbəlju ˈdʌbəlju dɑt”…</em>)\nThat’s what it can be like when\n<a rel=\"noopener noreferrer\" href=\"https://www.apple.com/accessibility/iphone/vision/\">VoiceOver</a>\ntries to read something without knowing more about <em>what</em> it’s reading.</p>\n<p>iOS 13 promises to improve the situation considerably\nwith the new <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/objectivec/nsobject/3075456-accessibilitytextualcontext\"><code>accessibility<wbr>Textual<wbr>Context</code></a> property\nand <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uiaccessibilitytextattributecontext\"><code>UIAccessibility<wbr>Text<wbr>Attribute<wbr>Context</code></a> <code>NSAttributed<wbr>String</code> attribute key.\nWhenever possible,\nbe sure to annotate views and attributed strings with\nthe constant that best describes the kind of text being displayed:</p>\n<ul>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>Console</code></li>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>File<wbr>System</code></li>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>Messaging</code></li>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>Narrative</code></li>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>Source<wbr>Code</code></li>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>Spreadsheet</code></li>\n<li><code>UIAccessibility<wbr>Textual<wbr>Context<wbr>Word<wbr>Processing</code></li>\n</ul>\n<aside>\n<p>For more information,\nsee the WWDC 2019 session\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/videos/play/wwdc2019/248/\">“Creating an Accessible Reading Experience”</a>.</p>\n</aside>\n<h2>\n<a href=\"https://nshipster.com/ios-13/#remove-implicitly-unwrapped-optionals-from-view-controllers-initialized-from-storyboards\"></a>Remove Implicitly Unwrapped Optionals from View Controllers Initialized from Storyboards</h2>\n<p>SwiftUI may have signaled the eventual end of Storyboards,\nbut that doesn’t mean that things aren’t and won’t continue to get better\nuntil if / when that day comes.</p>\n<p>One of the most irritating anti-patterns for Swift purists\nwhen working on iOS projects with Storyboards\nhas been view controller initialization.\nDue to an impedance mismatch between\nInterface Builder’s “prepare for segues” approach and\nSwift’s object initialization rules,\nwe frequently had to choose between\nmaking all of our properties non-private, variable, and (implicitly unwrapped) optionals,\nor foregoing Storyboards entirely.</p>\n<p>Xcode 11 and iOS 13 allow these paradigms to reconcile their differences\nby way of the new <code>@IBSegue<wbr>Action</code> attribute\nand some new <code>UIStoryboard</code> class methods:</p>\n<p>First,\nthe <code>@IBSegue<wbr>Action</code> attribute\ncan be applied view controller method declarations\nto designate itself as the API responsible for\ncreating a segue’s destination view controller\n<em>(i.e. the <code>destination<wbr>View<wbr>Controller</code> property of the <code>segue</code> parameter\nin the <code>prepare(for:sender:)</code> method)</em>.</p>\n<pre data-lang=\"Swift\"><code><span>@IBSegue<wbr>Action</span>\n <span>func</span> <span>make<wbr>Profile<wbr>View<wbr>Controller</span><span>(</span><span>coder</span><span>:</span> <span>NSCoder</span><span>,</span> <span>sender</span><span>:</span> <span>Any</span><span>?,</span> <span>segue<wbr>Identifier</span><span>:</span> <span>String</span><span>?)</span> <span>-></span> <span>Profile<wbr>View<wbr>Controller</span><span>?</span> <span>{</span>\n <span>Profile<wbr>View<wbr>Controller</span><span>(</span>\n <span>coder</span><span>:</span> <span>coder</span><span>,</span>\n <span>name</span><span>:</span> <span>self</span><span>.</span><span>selected<wbr>Name</span><span>,</span>\n <span>avatar<wbr>Image<wbr>URL</span><span>:</span> <span>self</span><span>.</span><span>selected<wbr>Avatar<wbr>Image<wbr>URL</span>\n <span>)</span>\n <span>}</span>\n </code></pre>\n<p>Second,\nthe <code>UIStoryboard</code> class methods\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uistoryboard/3213988-instantiateinitialviewcontroller\"><code>instantiate<wbr>Initial<wbr>View<wbr>Controller(creator:)</code></a>\nand <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uistoryboard/3213989-instantiateviewcontroller%60\"><code>instantiate<wbr>View<wbr>Controller(identifier:creator:)</code></a>\noffer a convenient block-based customization point for\ninstantiating a Storyboard’s view controllers.</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>UIKit</span>\n <span>struct</span> <span>Person</span> <span>{</span> <var>...</var> <span>}</span>\n <span>class</span> <span>Profile<wbr>View<wbr>Controller</span><span>:</span> <span>UIView<wbr>Controller</span> <span>{</span>\n <span>let</span> <span>name</span><span>:</span> <span>String</span>\n <span>let</span> <span>avatar<wbr>Image<wbr>URL</span><span>:</span> <span>URL</span><span>?</span>\n <span>init</span><span>?(</span><span>coder</span><span>:</span> <span>NSCoder</span><span>,</span> <span>name</span><span>:</span> <span>String</span><span>,</span> <span>avatar<wbr>Image<wbr>URL</span><span>:</span> <span>URL</span><span>?)</span> <span>{</span>\n <span>self</span><span>.</span><span>name</span> <span>=</span> <span>name</span>\n <span>self</span><span>.</span><span>avatar<wbr>Image<wbr>URL</span> <span>=</span> <span>avatar<wbr>Image<wbr>URL</span>\n <span>super</span><span>.</span><span>init</span><span>(</span><span>coder</span><span>:</span> <span>coder</span><span>)</span>\n <span>}</span>\n <span>required</span> <span>init</span><span>?(</span><span>coder</span><span>:</span> <span>NSCoder</span><span>)</span> <span>{</span>\n <span>fatal<wbr>Error</span><span>(</span><span>"init(coder:) has not been implemented"</span><span>)</span>\n <span>}</span>\n <span>}</span>\n <span>let</span> <span>storyboard</span> <span>=</span> <span>UIStoryboard</span><span>(</span><span>name</span><span>:</span> <span>"Profile<wbr>View<wbr>Controller"</span><span>,</span> <span>bundle</span><span>:</span> <span>nil</span><span>)</span>\n <span>storyboard</span><span>.</span><span>instantiate<wbr>Initial<wbr>View<wbr>Controller</span><span>(</span><span>creator</span><span>:</span> <span>{</span> <span>decoder</span> <span>in</span>\n <span>Profile<wbr>View<wbr>Controller</span><span>(</span>\n <span>coder</span><span>:</span> <span>decoder</span><span>,</span>\n <span>name</span><span>:</span> <span>"Johnny Appleseed"</span><span>,</span>\n <span>avatar<wbr>Image<wbr>URL</span><span>:</span> <span>nil</span>\n <span>)</span>\n <span>})</span>\n </code></pre>\n<p>Together with the new <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/app_and_environment/scenes\">UIKit Scene</a> APIs,\niOS 13 gives us a lot to work with\nas we wait for SwiftUI to mature and stabilize.</p>\n\n<hr>\n<p>That does it for our round-up of iOS 13 features\nthat you may have missed.\nBut rest assured —\nwe’re planning to cover many more new APIs in future NSHipster articles.</p>\n<p>If there’s anything we missed that you’d like for us to cover,\nplease <a rel=\"noopener noreferrer\" href=\"https://twitter.com/nshipster\">@ us on Twitter</a>!</p>"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Cocoa"],"originId":"https://nshipster.com/apns-device-tokens","fingerprint":"a5ee91d6","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:46321:18991ffa","updated":1568617200000,"author":"Mattt","summary":{"direction":"ltr","content":"<p>Precedent plays an important role throughout software engineering. So what does a change in iOS 13 to push notification registration have to say about Apple’s API design decisions?</p>"},"alternate":[{"href":"https://nshipster.com/apns-device-tokens/","type":"text/html"}],"crawled":1569418049005,"title":"Apple Push Notification Device Tokens","published":1568617200000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"<p>In law,\nthe latin phrase\n<dfn lang=\"la\">stare decisis</dfn> (<em>“to stand by things decided”</em>)\nis often used to refer to the doctrine of precedent —\nthe idea that,\nwhen deciding a case,\na court should look to previous decisions made\nfor cases with similar facts and scenarios.\nThis principle serves as a foundation of the American legal system,\nand the English common law from which it derives.</p>\n<p>For example,\nconsider <a rel=\"noopener noreferrer\" href=\"https://www.oyez.org/cases/2018/17-204\" title=\"Apple v. Pepper, 587 U.S. ___ (2019)\"><em>Apple v. Pepper</em></a>,\nwhich was argued before the Supreme Court of the United States\nin its most recent session\nand sought to settle the following question:</p>\n<blockquote>\n<p>If Apple and its App Store constitute a monopoly,\ncan consumers sue Apple for offering apps at higher-than-competitive prices,\neven when the prices are set by third-party developers?</p>\n</blockquote>\n<p>In its decision,\nthe Court relied on precedent set in 1977\nby a case known as <a rel=\"noopener noreferrer\" href=\"https://www.oyez.org/cases/1976/76-404\" title=\"Illinois Brick Co. v. Illinois, 431 U.S. 720 (1977)\"><em>Illinois Brick</em></a>,\nwhich itself affirmed a ruling made a decade earlier\nin a case called <a rel=\"noopener noreferrer\" href=\"https://www.oyez.org/cases/1967/335\" title=\"Hanover Shoe, Inc. v. United Shoe Machinery Corporation, 392 US 481 (1968)\"><em>Hanover Shoe</em></a>.\nOn its face,\niPhones in 2010’s would seem to have little to do with bricks from the 1970’s\n<em>(aside from the <a rel=\"noopener noreferrer\" href=\"https://www.theiphonewiki.com/wiki/Brick\">obvious connotation</a>)</em>,\nbut within the context of\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/United_States_antitrust_law\">United States antitrust law</a>,\nthe connection between the two was inescapable.</p>\n<aside>\n<p>Of course, there are <em>many</em> other cases\nthat offer simpler and more comprehensive illustrations of\nthe role of precedent in American jurisprudence,\nbut we figured this one would be the least likely to cause readers to think\nthat NSHipster got acqui-hired by\n<a rel=\"noopener noreferrer\" href=\"https://www.atrium.co\">Atrium</a> or something.</p>\n</aside>\n<p>Adherence to precedence confers inertia in the decision-making process.\nIt promotes stability throughout the legal system\nand the institutions that rely on a consistent application of laws.</p>\n<p>However,\nlike inertia,\nprecedence can also be overcome with sufficiently compelling reasons;\nwe are bound by the past only insofar as to give it due consideration.</p>\n<hr>\n<p>Bearing all of that in mind,\nlet’s <a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Smash_cut\"><dfn>smash cut</dfn></a>\nto our subject for this week’s brief:\n<dfn>Apple Push Notification Device Tokens</dfn> —\nand in particular,\na single change in iOS 13 that may incidentally break push notifications\nfor thousands of apps.</p>\n<h2>\n<a href=\"https://nshipster.com/apns-device-tokens/#a-push-notifications-primer\"></a>A Push Notifications Primer</h2>\n<p>Push notifications allow apps to communicate with users\nin response to remote events when they aren’t currently in use.</p>\n<p>Unlike SMS or email,\nwhich allows a sender to communicate with a recipient directly\nusing a unique identifier (a phone number and email address, respectively),\ncommunication between the app’s remote server and the user’s local device\nare facilitated by the Apple Push Notification service\n(<abbr title=\"Apple Push Notification service\">APNs</abbr>).</p>\n<p>Here’s how that works:</p>\n<ul>\n<li>After launching an app,\nthe app calls the method\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uiapplication/1623078-registerforremotenotifications\"><code>register<wbr>For<wbr>Remote<wbr>Notifications()</code></a>,\nprompting the user to grant the app permission to send push notifications.</li>\n<li>In response to permission being granted,\nthe app delegate calls the method\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application\"><code>application(_:did<wbr>Register<wbr>For<wbr>Remote<wbr>Notifications<wbr>With<wbr>Device<wbr>Token:)</code></a>.</li>\n</ul>\n<p>The <code>device<wbr>Token</code> parameter in the app delegate method\nis an opaque <code>Data</code> value —\nkind of like a really long unique phone number or email address —\nthat the app’s push notification provider uses\nto route notifications through APNs to reach\nthis particular installation of the app.</p>\n<p>In principle,\nrepresenting this parameter as a <code>Data</code> value makes a lot of sense —\nthe value itself is meaningless.\nHowever, in practice,\nthis API design decision has been the source of untold amounts of heartache.</p>\n<h2>\n<a href=\"https://nshipster.com/apns-device-tokens/#the-enduring-challenges-of-sending-device-tokens-back-to-the-server\"></a>The Enduring Challenges of Sending Device Tokens Back to the Server</h2>\n<p>When the app delegate receives its <code>device<wbr>Token</code>,\nthat’s not the end of the story.\nIn order for its to be used to send push notifications,\nit needs to be sent from the client to the server.</p>\n<p>The question is, <em>“How”</em>?</p>\n<p>Before you jump to a particular answer,\nconsider the historical context of iOS 3 (circa 2009),\nwhen push notifications were first introduced:</p>\n<h3>\n<a href=\"https://nshipster.com/apns-device-tokens/#back-in-my-day\"></a><em>“Back in My Day…“</em>\n</h3>\n<p>You could create an <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/nsurlrequest\"><code>NSURLRequest</code></a> object,\nset its <code>http<wbr>Body</code> property to the <code>device<wbr>Token</code>,\nand send it using <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/nsurlconnection\"><code>NSURLConnection</code></a>,\nbut you’d probably also want to include some additional information —\nlike a username or email address —\nto associate it with an account in the app.\nThat meant that the <code>data</code> you set as a request’s HTTP body\ncouldn’t just be the device token.</p>\n<p>Sending an HTTP <code>POST</code> body with<code>application/x-www-form-urlencoded</code>\n(e.g. <code>username=jappleseed&device<wbr>Token=____</code>)\nis one possibility for encoding multiple fields into a single payload,\nbut then the question becomes,\n<em>“How do you encode binary data into text?”</em></p>\n<p><a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Base64\">Base64</a>\nis a great binary-to-text encoding strategy,\nbut <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/nsdata/1413546-base64encodedstringwithoptions?language=objc\"><code>NSData -base64Encoded<wbr>String<wbr>With<wbr>Options:</code></a>\nwouldn’t be available until iOS 7,\nfour years after push notifications were first introduced in iOS 3.\nWithout <a href=\"https://nshipster.com/cocoapods/\">CocoaPods</a> or a strong open-source ecosystem\nto fill in the gaps,\nyou were left to follow\n<a rel=\"noopener noreferrer\" href=\"https://www.cocoawithlove.com/2009/06/base64-encoding-options-on-mac-and.html\">blog posts</a>\ndescribing how to roll your own implementation,\nhoping that things would work as advertised.</p>\n<aside>\n<p>In retrospect,\nprobably the best answer\n(at least in terms of taking advantage of built-in functionality)\nwould be to serialize an <code>NSDictionary</code>\ncontaining the device token and other information\ninto a property list.</p>\n<p>Then again,\nsupport for <code>.plist</code> files server-side is historically thin.\nSo maybe that wouldn’t have been any better, after all…</p>\n</aside>\n<p>Given the complexity in using Base64 encoding on iOS < 7,\nmost developers instead opted to take advantage of\nwhat they saw as an easier, built-in alternative:</p>\n<h3>\n<a href=\"https://nshipster.com/apns-device-tokens/#nsdata-in-its-own-words\"></a>NSData, in its Own Words</h3>\n<p>Developers,\nin an attempt to understand what exactly\nthis <code>device<wbr>Token</code> parameter was,\nwould most likely have passed it into an <code>NSLog</code> statement:</p>\n<pre data-lang=\"language-obj-c highlighter-rouge\"><code><span>NSLog</span><span>(</span><span>@"%@"</span><span>,</span> <span>device<wbr>Token</span><span>);</span>\n <span>// Prints "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"</span>\n </code></pre>\n<p>Unfortunately,\nfor developers less versed in matters of data and encoding,\nthis output from <code>NSLog</code> may have led them astray:<br>\n<em>“Oh wow, so <code>device<wbr>Token</code> is actually a string!\n(I wonder why Apple was making this so difficult in the first place).\nBut no matter — I can take it from here.”</em></p>\n<pre data-lang=\"language-obj-c highlighter-rouge\"><code><span>// ⚠️ Warning: Don't do this</span>\n <span>NSString</span> <span>*</span><span>token</span> <span>=</span> <span>[[[[</span><span>device<wbr>Token</span> <span>description</span><span>]</span>\n <span>string<wbr>By<wbr>Replacing<wbr>Occurrences<wbr>Of<wbr>String</span><span>:</span><span>@" "</span> <span>with<wbr>String</span><span>:</span><span>@""</span><span>]</span>\n <span>string<wbr>By<wbr>Replacing<wbr>Occurrences<wbr>Of<wbr>String</span><span>:</span><span>@"<"</span> <span>with<wbr>String</span><span>:</span><span>@""</span><span>]</span>\n <span>string<wbr>By<wbr>Replacing<wbr>Occurrences<wbr>Of<wbr>String</span><span>:</span><span>@">"</span> <span>with<wbr>String</span><span>:</span><span>@""</span><span>];</span>\n </code></pre>\n<p>It’s unclear whether push notification service providers spurred this practice\nby requiring Base16 / hexadecimal representations from the beginning,\nor if they adopted it in response to how folks were\nalready accustomed to doing it,\nbut either way,\nthe practice stuck.\nAnd for nearly a decade,\nthis was how a significant percentage of apps were handling\npush notification device token registration.</p>\n<p>That was until Swift 3 and iOS 10.</p>\n<aside>\n<p>Granted,\nthere’s no single, canonical way to represent binary data as text —\nthe same token could be represented in Base64 as\n<code>"llsl<wbr>HGyxkm3jyz<wbr>Zv37Ft3ea5CGq<wbr>KPKye<wbr>X4V2e<wbr>Tduq3w="</code>\nor in <a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Ascii85\">Ascii85 encoding</a> as\n<code>"Q<PXTCp<wbr>B.?j3'?!hm%%Sk.(b4MEIu3?\\NZK2f>[D"</code>\nor in <a rel=\"noopener noreferrer\" href=\"https://github.com/Flight-School/Guide-to-Swift-Strings-Sample-Code#base-encoding\">Base\uD83E\uDDD1 encoding</a> as\n<code>"\uD83D\uDC69\uD83C\uDFFB\uD83E\uDDB1\uD83D\uDC69\uD83C\uDFFB\uD83E\uDDB1\uD83D\uDC69\uD83C\uDFFC\uD83E\uDDB3\uD83D\uDC69\uD83C\uDFFB\uD83E\uDDB1\uD83D\uDC68\uD83C\uDFFB\uD83E\uDDB1\uD83D\uDC68\uD83C\uDFFB\uD83E\uDDB0\uD83D\uDC69\uD83C\uDFFE\uD83D\uDC69\uD83C\uDFFD\uD83E\uDDB3\uD83D\uDC69\uD83C\uDFFB\uD83E\uDDB0\uD83D\uDC69\uD83C\uDFFB\uD83E\uDDB2\uD83D\uDC69\uD83C\uDFFF\uD83D\uDC69\uD83C\uDFFB\uD83D\uDC69\uD83C\uDFFE\uD83D\uDC69\uD83C\uDFFE\uD83E\uDDB0\uD83D\uDC68\uD83C\uDFFF\uD83D\uDC69\uD83C\uDFFD\uD83E\uDDB1\uD83D\uDC69\uD83C\uDFFF\uD83D\uDC69\uD83C\uDFFF\uD83E\uDDB3\uD83D\uDC68\uD83C\uDFFB\uD83D\uDC69\uD83C\uDFFD\uD83D\uDC69\uD83C\uDFFF\uD83D\uDC69\uD83D\uDC68\uD83C\uDFFF\uD83E\uDDB0\uD83D\uDC69\uD83C\uDFFF\uD83E\uDDB1\uD83D\uDC68\uD83E\uDDB1\uD83D\uDC68\uD83C\uDFFB\uD83E\uDDB0\uD83D\uDC69\uD83C\uDFFC\uD83E\uDDB1\uD83D\uDC68\uD83C\uDFFC\uD83D\uDC68\uD83C\uDFFD\uD83D\uDC68\uD83C\uDFFC\uD83D\uDC69\uD83C\uDFFE\uD83D\uDC69\uD83D\uDC68\uD83C\uDFFE\uD83E\uDDB2\uD83D\uDC69\uD83C\uDFFF\uD83E\uDDB0\uD83D\uDC68\uD83C\uDFFE\uD83E\uDDB0\uD83D\uDC69\uD83C\uDFFE\uD83E\uDDB3\uD83D\uDC69\uD83D\uDC68\uD83C\uDFFD\uD83E\uDDB3\uD83D\uDC68\uD83C\uDFFF\uD83E\uDDB3\uD83D\uDC69\uD83C\uDFFD\uD83E\uDDB0\uD83D\uDC69\uD83C\uDFFC\uD83E\uDDB1\uD83D\uDC69\uD83C\uDFFF\uD83D\uDC69\uD83C\uDFFD\uD83E\uDDB2\uD83E\uDD21"</code>.\nBut if your push notification service provider expects device tokens\nin its classic, Base16 hexadecimal string representation,\nyou should do adopt the approach described above.</p>\n</aside>\n<h3>\n<a href=\"https://nshipster.com/apns-device-tokens/#relitigating-the-past-with-swift-3\"></a>Relitigating the Past with Swift 3</h3>\n<p>By 2016,\nSwift had stabilized and matured to the point that\nmost if not many developers were choosing to write new apps in Swift,\nor at least write all new code in Swift for existing apps.</p>\n<p>For those who did,\nthe transition to Swift 3\nwas most memorable for its painful migration from Swift 2.\nAs part of <a rel=\"noopener noreferrer\" href=\"https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md\">“the grand API renaming”</a>\ncommon Foundation types, including <code>NSData</code>,\ndropped their <code>NS</code> prefix in APIs,\nusing a bridged, Swift value type in its place.\nFor the most part,\nthings worked as expected.\nBut there were a few differences in behavior —\nlargely <em>undocumented or incidental behavior</em>\nthat caused a breaking change.\nFor example,\nconsider the following change in\n<code>application(_:did<wbr>Register<wbr>For<wbr>Remote<wbr>Notifications<wbr>With<wbr>Device<wbr>Token:)</code>:</p>\n<pre data-lang=\"Swift\"><code><span>// Swift 2: device<wbr>Token is NSData</span>\n <span>device<wbr>Token</span><span>.</span><span>description</span> <span>// "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"</span>\n <span>// Swift 3: device<wbr>Token is Data</span>\n <span>device<wbr>Token</span><span>.</span><span>description</span> <span>// "32 bytes"</span>\n </code></pre>\n<p>However,\nmany developers remained undeterred by what was seen as a minor annoyance,\nand worked around the issue by recasting to <code>NSData</code> and its former behavior:</p>\n<pre data-lang=\"Swift\"><code><span>// ⚠️ Warning: Don't do this</span>\n <span>let</span> <span>token<wbr>Data</span> <span>=</span> <span>device<wbr>Token</span> <span>as</span> <span>NSData</span>\n <span>let</span> <span>token</span> <span>=</span> <span>token<wbr>Data</span><span>.</span><span>description</span>\n <span>let</span> <span>token</span> <span>=</span> <span>"</span><span>\\(</span><span>device<wbr>Token</span><span>)</span><span>"</span><span>.</span><span>replacing<wbr>Occurrences</span><span>(</span><span>of</span><span>:</span> <span>" "</span><span>,</span> <span>with</span><span>:</span> <span>""</span><span>)</span>\n <span>.</span><span>replacing<wbr>Occurrences</span><span>(</span><span>of</span><span>:</span> <span>"<"</span><span>,</span> <span>with</span><span>:</span> <span>""</span><span>)</span>\n <span>.</span><span>replacing<wbr>Occurrences</span><span>(</span><span>of</span><span>:</span> <span>">"</span><span>,</span> <span>with</span><span>:</span> <span>""</span><span>)</span>\n </code></pre>\n<p>Once again,\ndoing things the wrong way\nmanaged to keep things working for another couple years.</p>\n<p>But that’s all coming to an end with iOS 13.</p>\n<aside>\n<p>The impact of this change is significant,\nand bears repeating:</p>\n<p>If your implementation of\n<code>application(_:did<wbr>Register<wbr>For<wbr>Remote<wbr>Notifications<wbr>With<wbr>Device<wbr>Token:)</code>\ninvolves converting <code>device<wbr>Token</code> to a <code>String</code>\nand calling <code>replacing<wbr>Occurrences(of:with:)</code>,\nthis approach won’t work in apps compiled with the iOS 13 SDK.\n<strong>This is true whether your app is written in Swift or Objective-C</strong>.</p>\n</aside>\n<h3>\n<a href=\"https://nshipster.com/apns-device-tokens/#overturned-in-ios-13\"></a>Overturned in iOS 13</h3>\n<p>iOS 13 changes the format of descriptions\nfor Foundation objects,\nincluding <code>NSData</code>:</p>\n<pre data-lang=\"Swift\"><code><span>// i<wbr>OS 12</span>\n <span>(</span><span>device<wbr>Token</span> <span>as</span> <span>NSData</span><span>)</span><span>.</span><span>description</span> <span>// "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"</span>\n <span>// i<wbr>OS 13</span>\n <span>(</span><span>device<wbr>Token</span> <span>as</span> <span>NSData</span><span>)</span><span>.</span><span>description</span> <span>// "{length = 32, bytes = 0x965b251c 6cb1926d e3cb366f dfb16ddd ... 5f857679 376eab7c }"</span>\n </code></pre>\n<p>Whereas previously,\nyou could coerce <code>NSData</code> to spill its entire contents\nby converting it into a <code>String</code>,\nit now reports its length and a truncated summary of its internal bytes.</p>\n<p>So from now on,\nif you need to convert your push notification registration <code>device<wbr>Token</code>\ninto a Base16-encoded / hexadecimal string representation,\nyou should do the following:</p>\n<pre data-lang=\"Swift\"><code><span>let</span> <span>device<wbr>Token<wbr>String</span> <span>=</span> <span>device<wbr>Token</span><span>.</span><span>map</span> <span>{</span> <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02x"</span><span>,</span> <span>$0</span><span>)</span> <span>}</span><span>.</span><span>joined</span><span>()</span>\n </code></pre>\n<p>For clarity, let’s break this down and explain each part:</p>\n<ul>\n<li>The <code>map</code> method operates on each element of a sequence.\nBecause <code>Data</code> is a sequence of bytes in Swift,\nthe passed closure is evaluated for each byte in <code>device<wbr>Token</code>.</li>\n<li>The <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/swift/string/3126742-init\"><code>String(format:)</code></a> initializer\nevaluates each byte in the data\n(represented by the anonymous parameter <code>$0</code>)\nusing the <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html\"><code>%02x</code> format specifier</a>,\nto produce a zero-padded, 2-digit hexadecimal representation of\nthe byte / 8-bit integer.</li>\n<li>After collecting each byte representation created by the <code>map</code> method,\n<code>joined()</code> concatenates each element into a single string.</li>\n</ul>\n<aside>\n<p>We’d have preferred to use the <code>String(_:radix:)</code> initializer\nto create a hexadecimal (radix = 16) string representation\nfor each <code>UInt8</code> byte value,\nbut without a built-in <a rel=\"noopener noreferrer\" href=\"https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/\">left-pad</a> function\nin the Swift standard library\nto zero-pad to two digits,\nwe opted for <code>printf</code> format specifiers\n(<a href=\"https://nshipster.com/expressiblebystringinterpolation/\">despite our misgivings</a>).</p>\n<p>On Stack Overflow,\n<a rel=\"noopener noreferrer\" href=\"https://stackoverflow.com/a/24979958/157142\">the top-rated answer</a>\nadvocates for <code>"%02.2hhx"</code> instead of <code>"%02x"</code>.\nIt’s easy to get lost in the\n<a rel=\"noopener noreferrer\" href=\"http://www.opengroup.org/onlinepubs/009695399/functions/printf.html\">IEEE specification</a>,\nso here are some minimal code examples to demonstrate the difference\nbetween the two format specifiers:</p>\n<pre data-lang=\"Swift\"><code><span>// Overflow UInt.max (255)</span>\n <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02.2hhx"</span><span>,</span> <span>256</span><span>)</span> <span>// "00"</span>\n <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02x"</span><span>,</span> <span>256</span><span>)</span> <span>// "100"</span>\n <span>// Underflow UInt.min (0)</span>\n <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02.2hhx"</span><span>,</span> <span>-</span><span>1</span><span>)</span> <span>// "ff"</span>\n <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02x"</span><span>,</span> <span>-</span><span>1</span><span>)</span> <span>// "ffffffff"</span>\n </code></pre>\n<p><code>"%02.2hhx"</code> guarantees that values beyond the range of <code>UInt</code>\nproduce two hexadecimal digits\n(though one could argue whether it’s better to fail silently here).</p>\n<p>But any difference in behavior is moot,\nso long as <code>Data</code> is a collection whose <code>Element</code> is <code>UInt8</code>:</p>\n<pre data-lang=\"Swift\"><code><span>(</span><span>UInt</span><span>.</span><span>min</span><span>...</span><span>UInt</span><span>.</span><span>max</span><span>)</span><span>.</span><span>map</span> <span>{</span>\n <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02.2hhx"</span><span>,</span> <span>$0</span><span>)</span> <span>==</span> <span>String</span><span>(</span><span>format</span><span>:</span> <span>"%02x"</span><span>,</span> <span>$0</span><span>)</span>\n <span>}</span><span>.</span><span>contains</span><span>(</span><span>false</span><span>)</span> <span>// false</span>\n </code></pre>\n<p>Oh, and don’t worry about any purported performance differences\nbetween <code>reduce</code> and <code>map</code> + <code>join</code>;\nany Δ is going to be negligible,\nand totally irrelevant for an infrequent operation such as this.</p>\n</aside>\n<hr>\n<p>Was Apple irresponsible in making this particular change? <br>\nWe’d argue: <em>No, not really.</em></p>\n<p><strong>Developers shouldn’t have relied on a specific format for\nan object’s <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/objectivec/nsobjectprotocol/1418746-description\"><code>description</code></a></strong>.</p>\n<p>Dumping an entire <code>Data</code> value’s contents becomes untenable at a certain point,\nand this change to a more succinct summary\nmakes debugging larger data blobs significantly easier.</p>\n<hr>\n<p>Like we said about laws at the start of this article,\nprecedence is a form of inertia,\nnot an immutable truth.</p>\n<p><em lang=\"la\">Stare decisis</em> plays an important role\nthroughout software engineering.\nExamples like the <a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/HTTP_referer\">“Referer” [sic] header”</a> —\neven the conventions we have about\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Electric_current#Conventions\">the direction of electricity flow</a> —\ndemonstrate the value of sticking to decisions,\nunless an opportunity arises to compel change.</p>"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Miscellaneous"],"originId":"https://nshipster.com/dark-mode","fingerprint":"49169576","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:46320:18991ffa","updated":1567407600000,"author":"Mattt","summary":{"direction":"ltr","content":"<p>After waiting for what felt like an eternity (but was only, like, a year), Dark Mode is now finally coming to the iPhone and iPad iOS 13. But will your app be ready on launch day?</p>"},"alternate":[{"href":"https://nshipster.com/dark-mode/","type":"text/html"}],"crawled":1569418049005,"title":"Dark Mode on iOS 13","published":1567407600000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"<p>Today is Labor Day in the United States\n(and <span lang=\"en-CA\">Labour Day</span> in Canada),\na day to celebrate the achievement of the workers\nwho organized to bring about fair and safe conditions for employees —\nprotections that serve as the foundation for the modern workplace.</p>\n<aside>\n<p>By contrast,\nmuch of the rest of the world,\ninstead observe\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/International_Workers%27_Day\">International Worker’s Day</a> on May 1st;\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Labour_Day#/media/File:Observance_of_International_Workers%27_Day_-_Revised.svg\">the resulting map</a>\nis reminiscent of some other\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Metrication#/media/File:Metrication.svg\">world</a>\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Fahrenheit#/media/File:Countries_that_use_Fahrenheit.svg\">maps</a>.</p>\n</aside>\n<p>Labor Day is also the unofficial end of summer;\nthe long weekend acting as a buffer between\nthe lemonades, sunburns, and trashy romance novels of June and\nthe pumpkin spice lattes, flu shots, and textbooks of September.</p>\n<p>However,\nfor the stereotypical tech worker,\nwho likes the heat of summer about as much as\nthe concept of “work/life balance”,\nLabor Day frequently serves as something else entirely:\n<em>a wake-up call</em>.\nThat,\nafter a lazy summer of ideation and experimentation,\nit’s once again time to launch new products and ship new features.\nAnd if you’re an app developer specifically,\nyou may know today as,\n<em>“Oh-\uD83E\uDD2C-it’s-September-and-I-still-haven’t-implemented-Dark-Mode-in-my-app”</em> day.</p>\n<p>We’re dedicating this week’s article to anyone out there\nwho’s celebrating this esteemed holiday,\nwhether contemporaneously or in the intervening days until iOS 13 goes GM.\nWe hope that a few quick tips can help shed light\nagainst the shade of your looming deadline.</p>\n<hr>\n<p><img alt src=\"https://nshipster.com/assets/dark-mode-apple-banner-947178d479b10101e6376fc655afffb65db5c7c659b16b531f4dce78d4b2bd52.png\"></p>\n<p><dfn>Dark Mode</dfn> is an appearance preference\nthat tells the system and participating apps to adopt darker color palettes.\nWhereas an app may display dark text on a light background by default,\nit may instead show white text on a dark background.</p>\n<p>Last year,\nDark Mode was <em>the</em> killer feature of macOS 10.14 Mojave,\nand its contemporaneous launch with Safari 12\nrippled throughout the World-Wide Web,\ngaining steady adoption among websites\n(<a rel=\"noopener noreferrer\" href=\"https://twitter.com/nshipster/status/1055174887168339968?lang=en\">like yours truly</a>)\nand\n<a rel=\"noopener noreferrer\" href=\"https://caniuse.com/#search=prefers-color-scheme\">other browsers</a>.</p>\n<p>After waiting for what felt like an eternity\n(but was only, like, a year),\nDark Mode is now <em>finally</em> coming to the iPhone and iPad iOS 13.\nNo longer will we have to tinker with\n<a rel=\"noopener noreferrer\" href=\"https://support.apple.com/en-us/HT207025\">Display Accommodations</a>\nto limit our light pollution when browsing Reddit late at night.</p>\n<h2>\n<a href=\"https://nshipster.com/dark-mode/#adopting-dark-mode-on-ios\"></a>Adopting Dark Mode on iOS</h2>\n<p>Apple’s done a great job designing a flexible, convenient API\nand providing <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface/choosing_a_specific_interface_style_for_your_ios_app\">excellent documentation to go with it</a>.</p>\n<p>Of course, the challenge with Apple technologies\nis that short of telling you that “you’re holding it wrong”,\nthey’ll rarely acknowledge the <em>existence</em> of prior art or alternatives,\nlet alone provide a transition document that in any way resembles\nhow everyone was doing things before we got an officially-sanctioned API.\n(<em>Then again, can you really blame them?</em>)</p>\n<p>If you were following 100% of Apple’s guidance to the letter,\nyou’d barely have to change a line or code\nto get your app ready for\n<a rel=\"noopener noreferrer\" href=\"https://www.apple.com/apple-events/\">next week’s special event</a>.\nBut most apps are built on solutions we built for ourselves\nto bridge the gaps in the SDK,\nand it may not be clear how to get on the new\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Happy_path\">happy path</a>\nfrom there.</p>\n<p>Apple’s guidance for adopting Dark Mode is fantastic for new projects\nbut doesn’t quite hit all of the points you should be aware of\nwhen preparing your existing app for iOS 13.\nSo without further ado,\nhere’s 6 action items for how to get your app ready for Dark Mode.</p>\n<hr>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#cancel-color-literals\"></a>#Cancel Color Literals</h3>\n<p>In Xcode,\na <dfn>color literal</dfn>\nis code with the prefix <code>#color<wbr>Literal</code>\nthat is rendered as a color swatch in the editor.\nFor example,\nthe code <code>#color<wbr>Literal(red: 1, green: 0.67, blue: 0, alpha: 1)</code>\nis rendered in Xcode as <span></span>.\nColor literals can be drag-and-dropped from\nthe Media Browser in Xcode 10,\nwhich has been consolidated with Snippets into the new Library panel in Xcode 11.</p>\n<p>Both color literals and their cousin, image literals,\nwere introduced in support of Xcode Playgrounds.\nBut don’t let their appearance fool you:\nneither have a place in your app’s codebase.</p>\n<aside>\n<p>Image literals could almost immediately be ruled out\ndue to their flakey rendering,\nbut it’s only with the advent Dark Mode\nthat we have a strong compelling reason to give them the boot:\n<strong>color literals don’t support dynamic / named colors</strong>.</p>\n<p>Attempting to drag a named color into the editor\nresults in two adjacent <code>#color<wbr>Literal</code> expressions —\nan invalid statement in Swift.\nIf you were to set one as the color property for a view mistakenly,\nit wouldn’t change its appearance for dark or light mode.</p>\n</aside>\n<p>Dark Mode or not,\nyou can replace all usage of color literals throughout your codebase\nfrom the command line:</p>\n<pre data-lang=\"Terminal\"><code><span>$</span> find <span>.</span> <span>-name</span> <span>'*.swift'</span> <span>\\</span>\n <span>-exec</span> <span>sed</span> <span>-i</span> <span>''</span> <span>-E</span> <span>'s/#color<wbr>Literal\\(red: (.*), green: (.*), blue: (.*), alpha: (.*)\\)/UIColor(red: \\1, green: \\2, blue: \\3, alpha: \\4)/ {} \\;\n</span></code></pre>\n<p>But before you do,\nyou might as well do one better\nand replace it with something that will stand the test of time.</p>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#nix-uicolor-hexadecimal-initializers\"></a>Nix UIColor Hexadecimal Initializers</h3>\n<p>Among the most common extensions you’ll find in a\nSwiss Army Knife-style CocoaPod\nis a category on <code>UIColor</code> that initializes from a hexadecimal string.\nSomething along the lines of this:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Swifty<wbr>Swift<wbr>Color<wbr>Swift</span>\n <span>let</span> <span>orange</span> <span>=</span> <span>UIColor</span><span>(</span><span>hex</span><span>:</span> <span>"#FB8C00"</span><span>)</span> <span>// \uD83D\uDC4E</span>\n </code></pre>\n<p>Setting aside any qualms about how they’re typically employed and implemented,\nyou’d be well-advised to have these go the way of color literals\nfor the same reasons we described in the previous section.</p>\n<p>But worry not!\nYou’ll still have a way to define colors\nusing those hex codes that your designer sent over,\nas we’ll see in our discussion of <a rel=\"noopener noreferrer\" href=\"https://nshipster.com/dark-mode/#color-assets\">named colors</a>.</p>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#find-replace-fixed-colors\"></a>Find & Replace Fixed Colors</h3>\n<p><code>UIColor</code> defines several class properties\nthat return colors by their common name.\nThese properties are problematic in iOS 13,\nbecause they don’t automatically adjust for light or dark appearance.\nFor example,\nsetting a label’s color to <code>.black</code> looks fine\nagainst the default <code>UITable<wbr>View<wbr>Cell</code> background,\nbut it’s illegible when that background becomes black\nwhen Dark Mode is enabled.</p>\n<p>To make your app ready for Dark Mode on iOS 13,\nyou’ll most likely want to replace any instance of the following\n<code>UIColor</code> class properties:</p>\n<div>\n<ul>\n<li><code>red</code></li>\n<li><code>orange</code></li>\n<li><code>yellow</code></li>\n<li><code>brown</code></li>\n<li><code>green</code></li>\n<li><code>cyan</code></li>\n<li><code>blue</code></li>\n<li><code>purple</code></li>\n<li><code>magenta</code></li>\n</ul>\n<p><br></p>\n<ul>\n<li><code>white</code></li>\n<li><code>light<wbr>Gray</code></li>\n<li><code>gray</code></li>\n<li><code>dark<wbr>Gray</code></li>\n<li><code>black</code></li>\n</ul>\n</div>\n<p>Hopefully you aren’t using the built-in\n<abbr title=\"Red-Orange-Yellow-Green-Blue-Indigo-Violet\">ROYGBIV</abbr>\n<code>UIColor</code> constants for much other than occasional layout debugging,\nbut chances you’ll probably find a few instances of <code>.black</code> or <code>.white</code>\npeppered throughout your codebase somewhere.</p>\n<p>In any case,\nthe easiest change to support Dark Mode would be to\nreplace any of the aforementioned fixed color properties with\nthe corresponding <code>system</code>-prefixed adaptable color below:</p>\n\n<table>\n<thead>\n<tr>\n<th rowspan=\"2\">Name</th>\n<th rowspan=\"2\">API</th>\n<th colspan=\"2\">Light</th>\n<th colspan=\"2\">Dark</th>\n</tr>\n<tr>\n<th>Default</th>\n<th>Accessible</th>\n<th>Default</th>\n<th>Accessible</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<th>Red</th>\n<td><code>system<wbr>Red</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Orange</th>\n<td><code>system<wbr>Orange</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Yellow</th>\n<td><code>system<wbr>Yellow</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Green</th>\n<td><code>system<wbr>Green</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Teal</th>\n<td><code>system<wbr>Teal</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Blue</th>\n<td><code>system<wbr>Blue</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Indigo</th>\n<td><code>system<wbr>Indigo</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Purple</th>\n<td><code>system<wbr>Purple</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Pink</th>\n<td><code>system<wbr>Pink</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n</tbody>\n<tbody>\n<tr>\n<th>Gray</th>\n<td><code>system<wbr>Gray</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Gray (2)</th>\n<td><code>system<wbr>Gray2</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Gray (3)</th>\n<td><code>system<wbr>Gray3</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Gray (4)</th>\n<td><code>system<wbr>Gray4</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Gray (5)</th>\n<td><code>system<wbr>Gray5</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n<tr>\n<th>Gray (6)</th>\n<td><code>system<wbr>Gray6</code></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n<td><div></div></td>\n</tr>\n</tbody>\n</table>\n<p>You may notice that this table doesn’t provide direct correspondences for\nblack or white (or brown, but disregard that for now).</p>\n<p>Black and white don’t have adaptive colors\nbecause their names would cease to be descriptive in Dark Mode;\nif <code>.system<wbr>Black</code> existed, it’d pretty much have to be <code>.white</code>\nto be visible in a dark color pallet.</p>\n<p>Which gets to a deeper point about color management in an era of Dark Mode…</p>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#use-semantic-colors\"></a>Use Semantic Colors</h3>\n<p>The best way to ensure consistent rendering of your UI\non any device in any appearance mode\nis to use <dfn>semantic colors</dfn>,\nnamed according to their function rather than appearance.</p>\n<p>Similar to how <a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/\">Dynamic Type</a>\nuses semantic tags like “Headline” and “Body” to\nautomatically set the most suitable font for the user’s display preferences,\nsemantic colors —\nor what Apple’s calling\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors\">UI Element Colors</a> —\nprovide future-proof behavior for your views and controls.</p>\n<p>When styling your component,\nset the color to the closest <code>UIColor</code> class property below:</p>\n<div>\n<dl>\n<dt>Label Colors</dt>\n<dd><code>label</code></dd>\n<dd><code>secondary<wbr>Label</code></dd>\n<dd><code>tertiary<wbr>Label</code></dd>\n<dd><code>quaternary<wbr>Label</code></dd>\n<dt>Text Colors</dt>\n<dd><code>placeholder<wbr>Text</code></dd>\n<dt>Link Colors</dt>\n<dd><code>link</code></dd>\n<dt>Separator Colors</dt>\n<dd><code>separator</code></dd>\n<dd><code>opaque<wbr>Separator</code></dd>\n<dt>Fill Colors</dt>\n<dd><code>system<wbr>Fill</code></dd>\n<dd><code>secondary<wbr>System<wbr>Fill</code></dd>\n<dd><code>tertiary<wbr>System<wbr>Fill</code></dd>\n<dd><code>quaternary<wbr>System<wbr>Fill</code></dd>\n<dt>Background Colors</dt>\n<dd><code>system<wbr>Background</code></dd>\n<dd><code>secondary<wbr>System<wbr>Background</code></dd>\n<dd><code>tertiary<wbr>System<wbr>Background</code></dd>\n<dt>Grouped Background Colors</dt>\n<dd><code>system<wbr>Grouped<wbr>Background</code></dd>\n<dd><code>secondary<wbr>System<wbr>Grouped<wbr>Background</code></dd>\n<dd><code>tertiary<wbr>System<wbr>Grouped<wbr>Background</code></dd>\n</dl>\n</div>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#upgrade-homegrown-semantic-color-palettes\"></a>Upgrade Homegrown Semantic Color Palettes</h3>\n<p>If you’ve given any thought to color management in your app\nyou’ll have likely landed on some form of the following strategy,\nwhereby you define semantic colors according to fixed colors\nwithin a namespace or in an extension to <code>UIColor</code>.</p>\n<p>For example,\nthe following example shows how an app might define\n<code>UIColor</code> constants from the\n<a rel=\"noopener noreferrer\" href=\"https://material.io/design/color/#color-usage-palettes\">Material UI color system</a>\nand reference them from semantic <code>UIColor</code> class properties:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>UIKit</span>\n <span>extension</span> <span>UIColor</span> <span>{</span>\n <span>static</span> <span>var</span> <span>custom<wbr>Accent</span><span>:</span> <span>UIColor</span> <span>{</span> <span>return</span> <span>Material<wbr>UI</span><span>.</span><span>red500</span> <span>}</span>\n <var>...</var>\n <span>}</span>\n <span>fileprivate</span> <span>enum</span> <span>Material<wbr>UI</span> <span>{</span>\n <span>static</span> <span>let</span> <span>orange600</span> <span>=</span> <span>UIColor</span><span>(</span><span>red</span><span>:</span> <span>0x<wbr>FB</span> <span>/</span> <span>0x<wbr>FF</span><span>,</span>\n <span>green</span><span>:</span> <span>0x8C</span> <span>/</span> <span>0x<wbr>FF</span><span>,</span>\n <span>blue</span><span>:</span> <span>0x00</span> <span>/</span> <span>0x<wbr>FF</span><span>,</span>\n <span>alpha</span><span>:</span> <span>1</span><span>)</span> <span>// #FB8C00</span>\n <var>...</var>\n <span>}</span>\n </code></pre>\n<p>If your app uses a pattern like this,\nyou can make it Dark Mode compatible\nusing the new\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/uikit/uicolor/3238041-init\"><code>init(dynamic<wbr>Provider:)</code></a>\n<code>UIColor</code> initializer in iOS 13 like so:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>UIKit</span>\n <span>extension</span> <span>UIColor</span>\n <span>static</span> <span>var</span> <span>custom<wbr>Accent</span><span>:</span> <span>UIColor</span> <span>{</span>\n <span>if</span> <span>#available(i<wbr>OS 13, *)</span> <span>{</span>\n <span>return</span> <span>UIColor</span> <span>{</span> <span>(</span><span>trait<wbr>Collection</span><span>:</span> <span>UITrait<wbr>Collection</span><span>)</span> <span>-></span> <span>UIColor</span> <span>in</span>\n <span>if</span> <span>trait<wbr>Collection</span><span>.</span><span>user<wbr>Interface<wbr>Style</span> <span>==</span> <span>.</span><span>dark</span> <span>{</span>\n <span>return</span> <span>Material<wbr>UI</span><span>.</span><span>orange300</span>\n <span>}</span> <span>else</span> <span>{</span>\n <span>return</span> <span>Material<wbr>UI</span><span>.</span><span>orange600</span>\n <span>}</span>\n <span>}</span>\n <span>}</span> <span>else</span> <span>{</span>\n <span>return</span> <span>Material<wbr>UI</span><span>.</span><span>orange600</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n<p>Nothing about the fixed Material UI color constants has to change\nwith this approach.\nInstead, the semantic color property <code>custom<wbr>Accent</code> provides a dynamic color\nthat uses the color most appropriate for the current rendering context.\nWhen Dark Mode is enabled,\na lighter orange is used to contrast against the darker color palette;\notherwise, the behavior is unchanged from the original implementation.</p>\n<p>The extra <code>#available</code> check creates some bloat in the implementation,\nbut it’s a small price to pay for the flexibility this approach provides.</p>\n<aside>\n<p>For bonus points,\nyou can extend this approach to\nsupport both light and dark modes in default and high contrast modes:</p>\n<pre data-lang=\"Swift\"><code><span>// i<wbr>OS >= 13</span>\n <span>UIColor</span> <span>{</span> <span>(</span><span>trait<wbr>Collection</span><span>:</span> <span>UITrait<wbr>Collection</span><span>)</span> <span>-></span> <span>UIColor</span> <span>in</span>\n <span>switch</span><span>(</span><span>trait<wbr>Collection</span><span>.</span><span>user<wbr>Interface<wbr>Style</span><span>,</span>\n <span>trait<wbr>Collection</span><span>.</span><span>accessibility<wbr>Contrast</span><span>)</span>\n <span>{</span>\n <span>case</span> (.dark, .high): return Material<wbr>UI.orange<wbr>A200\n <span>case</span> (.dark, _): return Material<wbr>UI.orange300\n <span>case</span> (_, .high): return Material<wbr>UI.orange<wbr>A700\n <span>default</span><span>:</span> <span>return</span> <span>Material<wbr>UI</span><span>.</span><span>orange600</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n</aside>\n<p>Unfortunately,\nthere’s one crucial shortcoming to using color properties in this way:\n<strong>they can’t be referenced from Interface Builder</strong>.</p>\n<p>If your app uses either Storyboards or XIBs,\nthe best approach is to use color assets.</p>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#manage-colors-with-an-asset-catalog\"></a>Manage Colors with an Asset Catalog</h3>\n<p><dfn>Color assets</dfn> let you manage colors in an Xcode Asset Catalog\nin the same way that you do for\nimages, <a href=\"https://nshipster.com/nsdataasset/\">data</a>, or other resources.</p>\n<picture>\n<source srcset=\"https://nshipster.com/assets/dark-mode-color-asset--dark-e41c0e84ef9d3b5664d8a483c71626eb4e6a7d098ede6028fc89011ead44455b.png\" media=\"(prefers-color-scheme: dark)\">\n<img alt=\"Asset Catalog Color Asset\" src=\"https://nshipster.com/assets/dark-mode-color-asset--light-178506ce85dc2be5e06c21ff6404422176194d6444892b8cc82cc40e8c92e6ef.png\">\n</picture>\n<p>To create a color set,\nopen an Asset Catalog in your Xcode project,\nclick the <kbd>+</kbd> button on the bottom left-hand corner,\nand select “New Color Set”.\nIn the Attributes inspector,\nselect “Any, Dark” appearance.\nColors are frequently expressed in the form (“#RRGGBB”);\nyou can enter colors in this form by\nselecting “8-bit Hexadecimal” from the “Input Method” drop-down.</p>\n<picture>\n<source srcset=\"https://nshipster.com/assets/dark-mode-color-asset-attributes-inspector--dark-40ee7fb921483065091de4518f0c42c92f0a533259930c42cfe10cc7dd7c4ac6.png\" media=\"(prefers-color-scheme: dark)\">\n<img alt=\"Asset Catalog Color Asset Attributes Inspector\" width=\"300\" src=\"https://nshipster.com/assets/dark-mode-color-asset-attributes-inspector--light-a1578de6f2f9a07ecaf790d80a548f4c291da85ce2523ca7a574c62d89f61742.png\">\n</picture>\n<p>Here,\nwe’ve done the same as before,\nexcept instead of defining fixed <code>UIColor</code> constants like <code>orange300</code> in code,\nwe set those in the color set itself.\nNow when it comes time to reference the color asset\nby the existing semantic class property,\nwe can use the <code>UIColor</code> named initializer:</p>\n<pre data-lang=\"Swift\"><code><span>extension</span> <span>UIColor</span> <span>{</span>\n <span>@available(i<wbr>OS 11, *)</span>\n <span>var</span> <span>custom<wbr>Accent</span><span>:</span> <span>UIColor</span><span>!</span> <span>{</span>\n <span>return</span> <span>UIColor</span><span>(</span><span>named</span><span>:</span> <span>"Accent"</span><span>)</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n<aside>\n<p>A single app can have multiple Asset Catalogs.\nConsider creating a separate one just for your color assets.</p>\n</aside>\n<p>Your opinion about color assets will largely be informed by\nyour preference or dispreference towards specialized Xcode document editors.\nSome folks like to have everything spelled out in code,\nwhile others appreciate the affordances provided by a bespoke UI\nlike the one provided by Xcode for color sets.</p>\n<p>In fact, your opinion of color assets\nis probably concomitant with your feelings about Storyboards —\nwhich is convenient,\nbecause the killer feature of color assets is that\nthey can be referenced from within Interface Builder.\n<em>(If you’re not on team IB, then you can safely skip this whole discussion.)</em></p>\n<h3>\n<a href=\"https://nshipster.com/dark-mode/#replace-instances-of-custom-color-in-xibs-and-storyboards\"></a>Replace Instances of “Custom Color” in XIBs and Storyboards</h3>\n<p>The “Custom Color” option in Interface Builder\nthat brings up the macOS system-native color picker\nsuffers the same problem as the color literals and fixed colors\nwe talked about earlier.\nIf you want your XIB-powered views to look good on iOS 13,\nyou’ll need to migrate to named colors.</p>\n<p>This can be done easily:\nselect any component in your scene,\nand you can set its color attribute using the same, named color\ndefined in your Asset Catalog.</p>\n<picture>\n<source srcset=\"https://nshipster.com/assets/dark-mode-interface-builder-named-color--dark-3f513ae1eb83d602e3cda21594327e8e60f87afccd968597f052dfde4041e124.png\" media=\"(prefers-color-scheme: dark)\">\n<img alt=\"Interface Builder Named Color\" width=\"300\" src=\"https://nshipster.com/assets/dark-mode-interface-builder-named-color--light-172c2109e834114d0d78e0f0aaccde22a4b7afe6228f2f4e825c20c0717c9fab.png\">\n</picture>\n<p>For a small project,\nthis can be done by hand for all your screens in under an hour.\nHowever,\nfor a larger app,\nthis is a process you’ll want to automate.</p>\n<h4>\n<a href=\"https://nshipster.com/dark-mode/#xib-anatomy\"></a>XIB Anatomy</h4>\n<p>Under the hood,\nXIB and Storyboard files are merely XML files like any other:</p>\n<pre data-lang=\"XML\"><code><span><?xml version="1.0" encoding="UTF-8"?></span>\n <span><document</span> <span>type=</span><span>"com.apple.Interface<wbr>Builder3.Cocoa<wbr>Touch.Storyboard.XIB"</span> <span><#...#</span><span>></span>>\n <span><device</span> <span>id=</span><span>"retina6_1"</span> <span>orientation=</span><span>"portrait"</span><span>></span>\n <span><adaptation</span> <span>id=</span><span>"fullscreen"</span><span>/></span>\n <span></device></span>\n <span><dependencies></span>\n <span><deployment</span> <span>identifier=</span><span>"i<wbr>OS"</span><span>/></span>\n <span><plug<wbr>In</span> <span>identifier=</span><span>"com.apple.Interface<wbr>Builder.IBCocoa<wbr>Touch<wbr>Plugin"</span> <span>version=</span><span>"14490.49"</span><span>/></span>\n <span><capability</span> <span>name=</span><span>"Safe area layout guides"</span> <span>min<wbr>Tools<wbr>Version=</span><span>"9.0"</span><span>/></span>\n <span><capability</span> <span>name=</span><span>"documents saved in the Xcode 8 format"</span> <span>min<wbr>Tools<wbr>Version=</span><span>"8.0"</span><span>/></span>\n <span></dependencies></span>\n <span><scenes></span>\n <span><!--View Controller--></span>\n <span><scene</span> <span>scene<wbr>ID=</span><span>"nsh-ips-ter"</span><span>></span>\n <span><objects></span>\n <span><view<wbr>Controller</span> <span>id=</span><span>"dar-kmo-de1"</span> <span>custom<wbr>Class=</span><span>"View<wbr>Controller"</span> <span>custom<wbr>Module=</span><span>"NSHipster"</span> <span>custom<wbr>Module<wbr>Provider=</span><span>"target"</span> <span>scene<wbr>Member<wbr>ID=</span><span>"view<wbr>Controller"</span><span>></span>\n <span><view</span> <span>key=</span><span>"view"</span> <span>content<wbr>Mode=</span><span>"scale<wbr>To<wbr>Fill"</span> <span>id=</span><span>"mai-nv-iew"</span><span>></span>\n <span><rect</span> <span>key=</span><span>"frame"</span> <span>x=</span><span>"0.0"</span> <span>y=</span><span>"0.0"</span> <span>width=</span><span>"414"</span> <span>height=</span><span>"896"</span><span>/></span>\n <span><color</span> <span>key=</span><span>"background<wbr>Color"</span> <span>red=</span><span>"1"</span> <span>green=</span><span>"0.69019607843137254"</span> <span>blue=</span><span>"0.0"</span> <span>alpha=</span><span>"1"</span> <span>color<wbr>Space=</span><span>"custom"</span> <span>custom<wbr>Color<wbr>Space=</span><span>"s<wbr>RGB"</span><span>/></span>\n <span></view></span>\n <span></view<wbr>Controller></span>\n <span><placeholder</span> <span>placeholder<wbr>Identifier=</span><span>"IBFirst<wbr>Responder"</span> <span>id=</span><span>"dkx-z0-nzr"</span> <span>scene<wbr>Member<wbr>ID=</span><span>"first<wbr>Responder"</span><span>/></span>\n <span></objects></span>\n <span></scene></span>\n <span></scenes></span>\n <span></document></span>\n </code></pre>\n<p>We wouldn’t want to write this from scratch by hand,\nbut there’s nothing too mysterious going on here.</p>\n<p>So consider what happens when you use Xcode\nto switch the custom background color of the main view to a named color:</p>\n<pre data-lang=\"XML\"><code><span><?xml version="1.0" encoding="UTF-8"?></span>\n <span><document</span> <span>type=</span><span>"com.apple.Interface<wbr>Builder3.Cocoa<wbr>Touch.Storyboard.XIB"</span> <span><#...#</span><span>></span>>\n <span><device</span> <span><#...#</span><span>></span>/>\n <span><dependencies></span>\n <span><</span>#...#>\n <span><capability</span> <span>name=</span><span>"Named colors"</span> <span>min<wbr>Tools<wbr>Version=</span><span>"9.0"</span><span>/></span> <span><!-- ❶ --></span>\n <span></dependencies></span>\n <span><scenes></span>\n <span><!-- scenes.scene.objects.view<wbr>Controller.view --></span>\n <span><</span>#...#>\n <span><view</span> <span>key=</span><span>"view"</span> <span>content<wbr>Mode=</span><span>"scale<wbr>To<wbr>Fill"</span> <span>id=</span><span>"mai-nv-iew"</span><span>></span>\n <span><</span>#...#>\n <span><color</span> <span>key=</span><span>"background<wbr>Color"</span> <span>name=</span><span>"Accent"</span><span>/></span> <span><!-- ❷ --></span>\n <span></view></span>\n <span></scenes></span>\n <span><resources></span>\n <span><named<wbr>Color</span> <span>name=</span><span>"Accent"</span><span>></span> <span><!-- ❸ --></span>\n <span><color</span> <span>red=</span><span>"1"</span> <span>green=</span><span>"0.69019607843137254"</span> <span>blue=</span><span>"0.0"</span> <span>alpha=</span><span>"1"</span> <span>color<wbr>Space=</span><span>"custom"</span> <span>custom<wbr>Color<wbr>Space=</span><span>"s<wbr>RGB"</span><span>/></span>\n <span></named<wbr>Color></span>\n <span></resources></span>\n <span></document></span>\n </code></pre>\n<dl>\n<dt>❶</dt>\n<dd>A new <code>"Named colors"</code> capability is added as a dependency\nfor opening the document\n(Xcode uses this to determine whether it can edit files\ncreated by newer versions).</dd>\n<dt>❷</dt>\n<dd>The <code>red</code>, <code>green</code>, <code>blue</code>, and <code>alpha</code> components on the <code>color</code> element\nwere replaced by a single <code>name</code> attribute.</dd>\n<dt>❸</dt>\n<dd>A corresponding <code>named<wbr>Color</code> element was added to the top-level <code>resources</code> element.</dd>\n</dl>\n<p>Based on this understanding,\nwe should know enough to make this change <span>en masse</span>\nwith our own tooling!</p>\n<h4>\n<a href=\"https://nshipster.com/dark-mode/#finding-all-custom-colors\"></a>Finding All Custom Colors</h4>\n<aside>\n<p>The following examples use\nthe <a rel=\"noopener noreferrer\" href=\"http://xmlstar.sourceforge.net/overview.php\">XMLStarlet</a> command-line tools\nto query XIB and Storyboard files using\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/XPath\">XPath</a>.</p>\n<p>You can install it for yourself and follow along using\n<a href=\"https://nshipster.com/homebrew/\">Homebrew</a>:</p>\n<pre data-lang=\"Terminal\"><code><span>$</span> brew <span>install </span>xmlstarlet\n </code></pre>\n</aside>\n<p>The first order of business when migrating your Storyboards and XIBs for Dark Mode\nis to find all of the instances of custom colors.\nYou <em>could</em> go through each by hand and click each of the visual elements…\nor you could run the following command:</p>\n<pre data-lang=\"Terminal\"><code><span>$</span> find <span>.</span> <span>-name</span> <span>'*.xib'</span> <span>-or</span> <span>-name</span> <span>'*.storyboard'</span> <span>\\</span>\n <span>-exec</span> <span>echo</span> <span>{}</span> <span>\\;</span> <span>\\</span>\n <span>-exec</span> xmlstarlet sel <span>-t</span> <span>\\</span>\n <span>-m</span> <span>"//color[@color<wbr>Space='custom']"</span> <span>-c</span> <span>.</span> <span>-n</span> <span>{}</span> <span>\\;</span>\n <span>\nMain.storyboard\n</span><span><color red="1" green="0.69019607843137254" blue="0.0" alpha="1" color<wbr>Space="custom" custom<wbr>Color<wbr>Space="s<wbr>RGB"/></span>\n </code></pre>\n<p>This command prints the name of each file\nfollowed by each custom, unnamed <code>color</code> element it found\n(denoted by the <code>color<wbr>Space="custom"</code> attribute).</p>\n<p>The resulting list serves as a battle plan for the next phase of attack:</p>\n<h4>\n<a href=\"https://nshipster.com/dark-mode/#finding-each-distinct-custom-color\"></a>Finding Each Distinct Custom Color</h4>\n<p>Apps tend to reuse a small set of colors\nacross their views — <em>as they should</em>!\nBy running the following command,\nyou can generate a sorted, unique’d list of\nevery custom color in every XIB or Storyboard:</p>\n<pre data-lang=\"Terminal\"><code><span>$</span> find <span>.</span> <span>-name</span> <span>'*.xib'</span> <span>-or</span> <span>-name</span> <span>'*.storyboard'</span> <span>\\</span>\n <span>-exec</span> xmlstarlet sel <span>-t</span> <span>\\</span>\n <span>-m</span> <span>"//color[@color<wbr>Space='custom']"</span> <span>\\</span>\n <span>-v</span> <span>"concat( @red,' ',@green,' ',@blue,' ',@alpha)"</span> <span>-n</span> <span>{}</span> <span>\\;</span> <span>\\</span>\n | <span>sort</span> <span>-u</span>\n <span>\n1 0.69019607839999997 0.0 1\n</span></code></pre>\n<p>Some entries may be equivalent, within a small delta of each other\n(because, you know… floating-point numbers).\nTo account for account for this,\nand to transform our output into something easier to work with,\nyou can write the output to a file and process it with\na Ruby script like this one:</p>\n<pre data-lang=\"language-ruby highlighter-rouge\"><code><span>colors</span> <span>=</span> <span>File</span><span>.</span><span>readlines</span><span>(</span><span>'distinct_colors.txt'</span><span>).</span><span>map</span> <span>do</span> <span>|</span><span>line</span><span>|</span>\n <span>components</span> <span>=</span> <span>line</span><span>.</span><span>strip</span><span>.</span><span>split</span><span>(</span><span>/\\s+/</span><span>).</span><span>flat_map</span><span>(</span><span>&</span><span>:to_f</span><span>)</span>\n <span>red</span><span>,</span> <span>green</span><span>,</span> <span>blue</span> <span>=</span> <span>components</span><span>[</span><span>0</span><span>..</span><span>2</span><span>].</span><span>map</span><span>{</span> <span>|</span><span>c</span><span>|</span> <span>(</span><span>c</span> <span>*</span> <span>255</span><span>).</span><span>floor</span> <span>}</span>\n <span>alpha</span> <span>=</span> <span>(</span><span>components</span><span>.</span><span>last</span> <span>*</span> <span>100</span><span>).</span><span>floor</span>\n <span>[</span><span>red</span><span>,</span> <span>green</span><span>,</span> <span>blue</span><span>,</span> <span>alpha</span><span>]</span>\n <span>end</span>\n <span>colors</span><span>.</span><span>uniq</span><span>.</span><span>each</span> <span>do</span> <span>|</span><span>color</span><span>|</span>\n <span>puts</span> <span>"#%02X%02X%02X (%d%%)"</span> <span>%</span> <span>color</span>\n <span>end</span>\n </code></pre>\n<p>From here,\nthe final step is to map each set of\n<abbr title=\"red green blue alpha\">RGBA</abbr> values\nto the corresponding named color that you want to replace it with.</p>\n<aside>\n<p>If your data set is large,\nor if you just don’t trust your eyes to quantize these colors correctly,\nyou can use a clustering algorithm to do that for you.</p>\n<p>You may be familiar with\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/K-means_clustering\"><em>k</em>-means clustering</a>,\nbut that requires us to specify the target number of clusters ahead of time.\nWhen you don’t know how many groups there are <em lang=\"la\">a priori</em>,\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/DBSCAN\"><abbr title=\"density-based spatial clustering of applications with noise\">DBSCAN</abbr></a>\ncan be a nice alternative.</p>\n<p>Ruby conveniently has a <a rel=\"noopener noreferrer\" href=\"https://rubygems.org/gems/dbscan/versions/0.2\">gem</a>\nthat suits our purpose<sup>*</sup>:</p>\n<pre data-lang=\"language-ruby highlighter-rouge\"><code><span>require</span> <span>'dbscan'</span>\n <span>dbscan</span> <span>=</span> <span>DBSCAN</span><span>(</span><span>colors</span><span>,</span> <span>epsilon: </span><span>8</span><span>,</span> <span>min_points: </span><span>1</span><span>,</span> <span>distance: :euclidean_distance</span><span>)</span>\n <span>puts</span> <span>dbscan</span><span>.</span><span>results</span>\n </code></pre>\n<p><em><sup>*</sup> For best results,\nconvert <abbr>RGBA</abbr> values to\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/CIELUV\">a color space with perceptual uniformity</a>\nbefore clustering.</em></p>\n</aside>\n<h4>\n<a href=\"https://nshipster.com/dark-mode/#replacing-custom-colors\"></a>Replacing Custom Colors</h4>\n<p>At this point,\nwe’re well beyond the point where shell one-liners seem like a good idea.\nSo here’s a Ruby script we wrote up\nthat makes all of the changes we understand to take place\nwhen replacing a custom color with a named color in Interface Builder:</p>\n<aside>\n<p>I can’t vouch for the correctness of this script,\nso use it at your own risk.</p>\n</aside>\n<pre data-lang=\"language-ruby highlighter-rouge\"><code><span>require</span> <span>'nokogiri'</span>\n <span>def</span> <span>name_for_rgba_components</span><span>(</span><span>red</span><span>,</span> <span>green</span><span>,</span> <span>blue</span><span>,</span> <span>alpha</span><span>)</span>\n <span>case</span> <span>format</span><span>(</span><span>'#%02X%02X%02X%02X'</span><span>,</span> <span>red</span><span>,</span> <span>green</span><span>,</span> <span>blue</span><span>,</span> <span>alpha</span><span>)</span>\n <span># Return named color matching RGBA components</span>\n <span># e.g. "#F8CB00FF" => "Accent"</span>\n <span>end</span>\n <span>end</span>\n <span>def</span> <span>name_for_white_and_alpha_components</span><span>(</span><span>white</span><span>,</span> <span>alpha</span><span>)</span>\n <span># Return named color matching white and alpha components</span>\n <span># e.g. 0.9 => "Off-White"</span>\n <span>end</span>\n <span># Process each Storyboard and XIB file</span>\n <span>Dir</span><span>[</span><span>'**/*.{storyboard,xib}'</span><span>].</span><span>each</span> <span>do</span> <span>|</span><span>xib</span><span>|</span>\n <span>doc</span> <span>=</span> <span>Nokogiri</span><span>::</span><span>XML</span><span>(</span><span>File</span><span>.</span><span>read</span><span>(</span><span>xib</span><span>))</span>\n <span>names</span> <span>=</span> <span>[]</span>\n <span># Collect each custom color and assign it a name</span>\n <span>doc</span><span>.</span><span>xpath</span><span>(</span><span>'//objects//color'</span><span>).</span><span>each</span> <span>do</span> <span>|</span><span>color</span><span>|</span>\n <span>next</span> <span>if</span> <span>color</span><span>[</span><span>'name'</span><span>]</span>\n <span>name</span> <span>=</span> <span>nil</span>\n <span>color_space</span> <span>=</span> <span>color</span><span>[</span><span>'color<wbr>Space'</span><span>]</span>\n <span>color_space</span> <span>=</span> <span>color</span><span>[</span><span>'custom<wbr>Color<wbr>Space'</span><span>]</span> <span>if</span> <span>color_space</span> <span>==</span> <span>'custom'</span>\n <span>case</span> <span>color_space</span>\n <span>when</span> <span>'s<wbr>RGB'</span><span>,</span> <span>'calibrated<wbr>RGB'</span>\n <span>components</span> <span>=</span> <span>color</span><span>.</span><span>attributes</span>\n <span>.</span><span>values_at</span><span>(</span><span>'red'</span><span>,</span> <span>'green'</span><span>,</span> <span>'blue'</span><span>,</span> <span>'alpha'</span><span>)</span>\n <span>.</span><span>map</span><span>(</span><span>&</span><span>:value</span><span>)</span>\n <span>.</span><span>map</span><span>(</span><span>&</span><span>:to_f</span><span>)</span>\n <span>.</span><span>map</span> <span>{</span> <span>|</span><span>c</span><span>|</span> <span>c</span> <span>*</span> <span>255</span> <span>}</span>\n <span>name</span> <span>=</span> <span>name_for_rgba_components</span><span>(</span><span>*</span><span>components</span><span>)</span>\n <span>when</span> <span>'generic<wbr>Gamma22Gray<wbr>Color<wbr>Space'</span><span>,</span> <span>'calibrated<wbr>White'</span>\n <span>components</span> <span>=</span> <span>color</span><span>.</span><span>attributes</span>\n <span>.</span><span>values_at</span><span>(</span><span>'white'</span><span>,</span> <span>'alpha'</span><span>)</span>\n <span>.</span><span>map</span><span>(</span><span>&</span><span>:value</span><span>)</span>\n <span>.</span><span>map</span><span>(</span><span>&</span><span>:to_f</span><span>)</span>\n <span>name</span> <span>=</span> <span>name_for_white_and_alpha_components</span><span>(</span><span>*</span><span>components</span><span>)</span>\n <span>end</span>\n <span>next</span> <span>unless</span> <span>name</span>\n <span>named_color</span> <span>=</span> <span>doc</span><span>.</span><span>create_element</span><span>(</span><span>'color'</span><span>,</span>\n <span>key: </span><span>color</span><span>[</span><span>'key'</span><span>],</span>\n <span>name: </span><span>name</span><span>)</span>\n <span>color</span><span>.</span><span>replace</span><span>(</span><span>named_color</span><span>)</span>\n <span>names</span> <span><<</span> <span>name</span>\n <span>end</span>\n <span># Proceed to the next file if no named colors were found</span>\n <span>next</span> <span>if</span> <span>names</span><span>.</span><span>empty?</span>\n <span># Add the named color capability as a document dependency</span>\n <span>dependencies</span> <span>=</span> <span>doc</span><span>.</span><span>at</span><span>(</span><span>'/document/dependencies'</span><span>)</span> <span>||</span>\n <span>doc</span><span>.</span><span>root</span><span>.</span><span>add_child</span><span>(</span><span>doc</span><span>.</span><span>create_element</span><span>(</span><span>'dependencies'</span><span>))</span>\n <span>unless</span> <span>dependencies</span><span>.</span><span>at</span><span>(</span><span>"capability[@name='Named colors']"</span><span>)</span>\n <span>dependencies</span> <span><<</span> <span>doc</span><span>.</span><span>create_element</span><span>(</span><span>'capability'</span><span>,</span>\n <span>name: </span><span>'Named colors'</span><span>,</span>\n <span>min<wbr>Tools<wbr>Version: </span><span>'9.0'</span><span>)</span>\n <span>end</span>\n <span># Add each named color to the document resources</span>\n <span>resources</span> <span>=</span> <span>doc</span><span>.</span><span>at</span><span>(</span><span>'/document/resources'</span><span>)</span> <span>||</span>\n <span>doc</span><span>.</span><span>root</span><span>.</span><span>add_child</span><span>(</span><span>doc</span><span>.</span><span>create_element</span><span>(</span><span>'resources'</span><span>))</span>\n <span>names</span><span>.</span><span>uniq</span><span>.</span><span>sort</span><span>.</span><span>each</span> <span>do</span> <span>|</span><span>name</span><span>|</span>\n <span>next</span> <span>if</span> <span>resources</span><span>.</span><span>at</span><span>(</span><span>"named<wbr>Color[@name='</span><span>#{</span><span>name</span><span>}</span><span>']"</span><span>)</span>\n <span>resources</span> <span><<</span> <span>doc</span><span>.</span><span>create_element</span><span>(</span><span>'named<wbr>Color'</span><span>,</span> <span>name: </span><span>name</span><span>)</span>\n <span>end</span>\n <span># Save the changes</span>\n <span>File</span><span>.</span><span>write</span><span>(</span><span>xib</span><span>,</span> <span>doc</span><span>.</span><span>to_xml</span><span>(</span><span>indent: </span><span>4</span><span>,</span> <span>encoding: </span><span>'UTF-8'</span><span>))</span>\n <span>end</span>\n </code></pre>\n<p><em>*Phew!*</em></p>\n<p>If you’ve been facing down a deadline for Dark Mode\nat the expense of enjoying one last hurrah of summer,\nwe hope that this article was able to get you out of the office today.</p>\n<p>Its ironic that so many of us\nare eschewing our holiday weekend in a scramble to get our apps ready\nfor the annual\n<abbr title=\"next major operating system\">NMOS</abbr>\n<abbr title=\"gold master\">GM</abbr>.\nBut if it’s any consolation,\nknow that Apple engineers rarely get to observe\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Memorial_Day\">Memorial Day</a> —\nthe unofficial <em>start</em> of summer in America —\nin the run-up to WWDC.</p>"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Swift"],"originId":"https://nshipster.com/identifiable","fingerprint":"ecd98718","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:4631f:18991ffa","updated":1566802800000,"author":"Mattt","summary":{"direction":"ltr","content":"<p>Swift 5.1 gives us yet another occasion to ponder ontological questions and weigh in the relative merits of various built-in types as stable identifiers.</p>"},"alternate":[{"href":"https://nshipster.com/identifiable/","type":"text/html"}],"crawled":1569418049005,"title":"Identifiable","published":1566802800000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"<blockquote>\n<p>What constitutes the identity of an object?</p>\n</blockquote>\n<p>Philosophers have contemplated such matters throughout the ages.\nWhether it’s to do with\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Ship_of_Theseus\">reconstructed seafaring vessels from antiquity</a>\nor <a rel=\"noopener noreferrer\" href=\"https://scifi.stackexchange.com/questions/13437/in-star-trek-does-the-original-die-in-teleportation\">spacefaring vessels from science fiction</a>,\nquestions of Ontology reveal our perception and judgment to be\nmuch less certain than we’d like to believe.</p>\n<p>Our humble publication has frequented this topic with some regularity,\nwhether it was attempting to make sense of\n<a href=\"https://nshipster.com/equality/\">equality in Objective-C</a>\nor appreciating the much clearer semantics of Swift\n<em lang=\"fr\">vis-à-vis</em> the <a href=\"https://nshipster.com/equatable-and-comparable/\"><code>Equatable</code> protocol</a>.</p>\n<p>Swift 5.1 gives us yet another occasion to ponder this old chestnut\nby virtue of the new <code>Identifiable</code> protocol.\nWe’ll discuss the noumenon of this phenomenal addition to the standard library,\nand help you identify opportunities to\nrealize its potential in your own projects.</p>\n<p>But let’s dispense with the navel gazing and\njump right into some substance:</p>\n<hr>\n<p>Swift 5.1 adds the <code>Identifiable</code> protocol to the standard library,\ndeclared as follows:</p>\n<pre data-lang=\"Swift\"><code><span>protocol</span> <span>Identifiable</span> <span>{</span>\n <span>associatedtype</span> <span>ID</span><span>:</span> <span>Hashable</span>\n <span>var</span> <span>id</span><span>:</span> <span>ID</span> <span>{</span> <span>get</span> <span>}</span>\n <span>}</span>\n </code></pre>\n<p>Values of types adopting the <code>Identifiable</code> protocol\nprovide a stable identifier for the entities they represent.</p>\n<p>For example,\na <code>Parcel</code> object may use the <code>id</code> property requirement\nto track the package en route to its final destination.\nNo matter where the package goes,\nit can always be looked up by its <code>id</code>:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Core<wbr>Location</span>\n <span>struct</span> <span>Parcel</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>let</span> <span>id</span><span>:</span> <span>String</span>\n <span>var</span> <span>location</span><span>:</span> <span>CLPlacemark</span><span>?</span>\n <span>}</span>\n </code></pre>\n<aside>\n<p>Our first introduction to the <code>Identifiable</code> protocol\nactually came by way of SwiftUI;\nit’s\n<a rel=\"noopener noreferrer\" href=\"https://forums.swift.org/t/move-swiftuis-identifiable-protocol-and-related-types-into-the-standard-library/25713\">thanks to the community</a>\nthat the type was brought into the fold of the standard library.</p>\n<p>Though\n<a rel=\"noopener noreferrer\" href=\"https://github.com/search?q=%22protocol+Identifiable%22&type=Code\">as evidenced by GitHub search results</a>,\nmany of us were already working with <code>Identifiable</code> protocols of similar design…\nwhich prompts the question:\n<em>When was the <code>Identifiable</code> protocol really introduced?</em> \uD83E\uDD14</p>\n</aside>\n<p>The Swift Evolution proposal for <code>Identifiable</code>,\n<a rel=\"noopener noreferrer\" href=\"https://github.com/apple/swift-evolution/blob/master/proposals/0261-identifiable.md\">SE-0261</a>,\nwas kept small and focused in order to be incorporated quickly.\nSo, if you were to ask,\n<em>“What do you actually get by conforming to <code>Identifiable</code>?”</em>,\nthe answer right now is <em>“Not much.”</em>\nAs mentioned in the <a rel=\"noopener noreferrer\" href=\"https://github.com/apple/swift-evolution/blob/master/proposals/0261-identifiable.md#future-directions\">future directions</a>,\nconformance to <code>Identifiable</code> has the potential to unlock\nsimpler and/or more optimized versions of other functionality,\nsuch as the new <a rel=\"noopener noreferrer\" href=\"https://github.com/apple/swift-evolution/blob/master/proposals/0240-ordered-collection-diffing.md\">ordered collection diffing</a> APIs.</p>\n<p>But the question remains:\n<em>“Why bother conforming to <code>Identifiable</code>?”</em></p>\n<p>The functionality you get from adopting <code>Identifiable</code> is primarily semantic,\nand require some more explanation.\nIt’s sort of like asking,\n<em>“Why bother conforming to <code>Equatable</code>?”</em></p>\n<p>And actually, that’s not a bad place to start.\nLet’s talk first about <code>Equatable</code> and its relation to <code>Identifiable</code>:</p>\n<h2>\n<a href=\"https://nshipster.com/identifiable/#identifiable-vs-equatable\"></a>Identifiable vs. Equatable</h2>\n<p><code>Identifiable</code> distinguishes the identity of an entity from its state.</p>\n<p>A parcel from our previous example\nwill change locations frequently as it travels to its recipient.\nYet a normal equality check (<code>==</code>)\nwould fail the moment it leaves its sender:</p>\n<pre data-lang=\"Swift\"><code><span>extension</span> <span>Parcel</span><span>:</span> <span>Equatable</span> <span>{}</span>\n <span>var</span> <span>special<wbr>Delivery</span> <span>=</span> <span>Parcel</span><span>(</span><span>id</span><span>:</span> <span>"123456789012"</span><span>)</span>\n <span>special<wbr>Delivery</span><span>.</span><span>location</span> <span>=</span> <span>CLPlacemark</span><span>(</span>\n <span>location</span><span>:</span> <span>CLLocation</span><span>(</span><span>latitude</span><span>:</span> <span>37.3327</span><span>,</span>\n <span>longitude</span><span>:</span> <span>-</span><span>122.0053</span><span>),</span>\n <span>name</span><span>:</span> <span>"Cupertino, CA"</span>\n <span>)</span>\n <span>special<wbr>Delivery</span> <span>==</span> <span>Parcel</span><span>(</span><span>id</span><span>:</span> <span>"123456789012"</span><span>)</span> <span>// false</span>\n <span>special<wbr>Delivery</span><span>.</span><span>id</span> <span>==</span> <span>Parcel</span><span>(</span><span>id</span><span>:</span> <span>"123456789012"</span><span>)</span><span>.</span><span>id</span> <span>// true</span>\n </code></pre>\n<p>While this is an expected outcome from a small, contrived example,\nthe very same behavior can lead to confusing results further down the stack,\nwhere you’re not as clear about how different parts work with one another.</p>\n<pre data-lang=\"Swift\"><code><span>var</span> <span>tracked<wbr>Packages</span><span>:</span> <span>Set</span><span><</span><span>Parcel</span><span>></span> <span>=</span> <var>...</var>\n <span>tracked<wbr>Packages</span><span>.</span><span>contains</span><span>(</span><span>Parcel</span><span>(</span><span>id</span><span>:</span> <span>"123456789012"</span><span>))</span> <span>// false (?)</span>\n </code></pre>\n<p>On the subject of <code>Set</code>,\nlet’s take a moment to talk about the <code>Hashable</code> protocol.</p>\n<h2>\n<a href=\"https://nshipster.com/identifiable/#identifiable-vs-hashable\"></a>Identifiable vs. Hashable</h2>\n<p>In <a href=\"https://nshipster.com/hashable/\">our article about <code>Hashable</code></a>,\nwe described how <code>Set</code> and <code>Dictionary</code> use a calculated hash value\nto provide constant-time (<code>O(1)</code>) access to elements in a collection.\nAlthough the hash value used to bucket collection elements\nmay bear a passing resemblance to identifiers,\n<code>Hashable</code> and <code>Identifiable</code> have some important distinctions\nin their underlying semantics:</p>\n<ul>\n<li>Unlike identifiers,\nhash values are typically <em>state-dependent</em>,\nchanging when an object is mutated.</li>\n<li>Identifiers are <em>stable</em> across launches,\nwhereas hash values are calculated by randomly generated hash seeds,\nmaking them <em>unstable</em> between launches.</li>\n<li>Identifiers are <em>unique</em>,\nwhereas hash values may <em>collide</em>,\nrequiring additional equality checks when fetched from a collection.</li>\n<li>Identifiers can be <em>meaningful</em>,\nwhereas hash values are <em>chaotic</em>\nby virtue of their hashing functions.</li>\n</ul>\n<p>In short,\nhash values are similar to\nbut no replacement for identifiers.</p>\n<p><em>So what makes for a good identifier, anyway?</em></p>\n<h2>\n<a href=\"https://nshipster.com/identifiable/#choosing-id-types\"></a>Choosing ID Types</h2>\n<p>Aside from conforming to <code>Hashable</code>,\n<code>Identifiable</code> doesn’t make any other demands of\nits associated <code>ID</code> type requirement.\nSo what are some good candidates?</p>\n<p>If you’re limited to only what’s available in the Swift standard library,\nyour best options are <code>Int</code> and <code>String</code>.\nInclude Foundation,\nand you expand your options with <code>UUID</code> and <code>URL</code>.\nEach has its own strengths and weaknesses as identifiers,\nand can be more or less suited to a particular situation:</p>\n<h3>\n<a href=\"https://nshipster.com/identifiable/#int-as-id\"></a>Int as ID</h3>\n<p>The great thing about using integers as identifiers\nis that (at least on 64-bit systems),\nyou’re unlikely to run out of them anytime soon.</p>\n<p>Most systems that use integers to identify records\nassign them in an <dfn>auto-incrementing</dfn> manner,\nsuch that each new ID is 1 more than the last one.\nHere’s a simple example of how you can do this in Swift:</p>\n<pre data-lang=\"Swift\"><code><span>struct</span> <span>Widget</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>private</span> <span>static</span> <span>var</span> <span>id<wbr>Sequence</span> <span>=</span> <span>sequence</span><span>(</span><span>first</span><span>:</span> <span>1</span><span>,</span> <span>next</span><span>:</span> <span>{</span><span>$0</span> <span>+</span> <span>1</span><span>})</span>\n <span>let</span> <span>id</span><span>:</span> <span>Int</span>\n <span>init</span><span>?()</span> <span>{</span>\n <span>guard</span> <span>let</span> <span>id</span> <span>=</span> <span>Widget</span><span>.</span><span>id<wbr>Sequence</span><span>.</span><span>next</span><span>()</span> <span>else</span> <span>{</span> <span>return</span> <span>nil</span><span>}</span>\n <span>self</span><span>.</span><span>id</span> <span>=</span> <span>id</span>\n <span>}</span>\n <span>}</span>\n <span>Widget</span><span>()?</span><span>.</span><span>id</span> <span>// 1</span>\n <span>Widget</span><span>()?</span><span>.</span><span>id</span> <span>// 2</span>\n <span>Widget</span><span>()?</span><span>.</span><span>id</span> <span>// 3</span>\n </code></pre>\n<p>If you wanted to guarantee uniqueness across launches,\nyou might instead initialize the sequence with a value\nread from a persistent store like <code>User<wbr>Defaults</code>.\nAnd if you found yourself using this pattern extensively,\nyou might consider factoring everything into a self-contained\n<a href=\"https://nshipster.com/propertywrapper/\">property wrapper</a>.</p>\n<p>Monotonically increasing sequences have a lot of benefits,\nand they’re easy to implement.</p>\n<p>This kind of approach can provide unique identifiers for records,\nbut only within the scope of the device on which the program is being run\n(and even then, we’re glossing over a lot with respect to concurrency\nand shared mutable state).</p>\n<p>If you want to ensure that an identifier is unique across\n<em>every</em> device that’s running your app, then\ncongratulations —you’ve hit\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Consensus_%28computer_science%29\">a fundamental problem in computer science</a>.\nBut before you start in on\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Vector_clock\">vector clocks</a> and\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Consensus_algorithm\">consensus algorithms</a>,\nyou’ll be relieved to know that there’s a\nmuch simpler solution:\n<dfn>UUIDs</dfn>.</p>\n<aside>\n<p>Insofar as this is a concern for your app,\ndon’t expose serial identifiers to end users.\nNot only do you inadvertently disclose information about your system\n(<em>“How many customers are there? Just sign up and check the user ID!”</em>),\nbut you open the door for unauthorized parties to\nenumerate all of the records in your system\n(<em>“Just start at id = 1 and keep incrementing until a record doesn’t exist”</em>).</p>\n<p>Granted, this is more of a concern for web apps,\nwhich often use primary keys in URLs,\nbut it’s something to be aware of nonetheless.</p>\n</aside>\n<h3>\n<a href=\"https://nshipster.com/identifiable/#uuid-as-id\"></a>UUID as ID</h3>\n<p><a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Universally_unique_identifier\"><abbr title=\"universally unique identifier\">UUID</abbr>s</a>, or\nuniversally unique identifiers,\n(mostly) sidestep the problem of consensus with probability.\nEach UUID stores 128 bits —\nminus 6 or 7 format bits, depending on the\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions\">version</a> —\nwhich, when randomly generated,\nmake the chances of <dfn>collision</dfn>,\nor two UUIDs being generated with the same value,\n<em>astronomically</em> small.</p>\n<p><a href=\"https://nshipster.com/uuid-udid-unique-identifier/\">As discussed in a previous article</a>,\nFoundation provides a built-in implementation of (version-4) UUIDs\nby way of the\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/documentation/foundation/uuid\"><code>UUID</code> type</a>.\nThus making adoption to <code>Identifiable</code> with UUIDs trivial:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>struct</span> <span>Gadget</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>let</span> <span>id</span> <span>=</span> <span>UUID</span><span>()</span>\n <span>}</span>\n <span>Gadget</span><span>()</span><span>.</span><span>id</span> <span>// 584FB4BA-0C1D-4107-9EE5-C555501F2077</span>\n <span>Gadget</span><span>()</span><span>.</span><span>id</span> <span>// C9FECDCC-37B3-4AEE-A514-64F9F53E74BA</span>\n </code></pre>\n<p>Beyond minor ergonomic and cosmetic issues,\n<code>UUID</code> serves as an excellent alternative to <code>Int</code>\nfor generated identifiers.</p>\n<p>However,\nyour model may already be uniquely identified by a value,\nthereby obviating the need to generate a new one.\nUnder such circumstances,\nthat value is likely to be a <code>String</code>.</p>\n<aside>\n<p>On macOS,\nyou can generate a random UUID from Terminal\nwith the built-in <code>uuidgen</code> command:</p>\n<pre data-lang=\"Terminal\"><code><span>$</span> uuidgen\n <span>39C884B8-0A11-4B4F-9107-3AB909324DBA\n</span></code></pre>\n</aside>\n<h3>\n<a href=\"https://nshipster.com/identifiable/#string-as-id\"></a>String as ID</h3>\n<p>We use strings as identifiers all the time,\nwhether it takes the form of a username or a checksum or a translation key\nor something else entirely.</p>\n<p>The main drawback to this approach is that,\nthanks to The Unicode® Standard,\nstrings encode thousands of years of written human communication.\nSo you’ll need a strategy for handling identifiers like\n“⽜”, “\uD800\uDC8C”, “”, and “\uD83D\uDC2E”\n…and that’s to say nothing of the more pedestrian concerns,\nlike leading and trailing whitespace and case-sensitivity!</p>\n<p>Normalization is the key to successfully using strings as identifiers.\nThe easiest place to do this is in the initializer,\nbut, again, if you find yourself repeating this code over and over,\n<a href=\"https://nshipster.com/propertywrapper/\">property wrappers</a> can help you here, too.</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>fileprivate</span> <span>extension</span> <span>String</span> <span>{</span>\n <span>var</span> <span>non<wbr>Empty</span><span>:</span> <span>String</span><span>?</span> <span>{</span> <span>is<wbr>Empty</span> <span>?</span> <span>nil</span> <span>:</span> <span>self</span> <span>}</span>\n <span>}</span>\n <span>struct</span> <span>Whosit</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>let</span> <span>id</span><span>:</span> <span>String</span>\n <span>init</span><span>?(</span><span>id</span><span>:</span> <span>String</span><span>)</span> <span>{</span>\n <span>guard</span> <span>let</span> <span>id</span> <span>=</span> <span>id</span><span>.</span><span>trimming<wbr>Characters</span><span>(</span><span>in</span><span>:</span> <span>Character<wbr>Set</span><span>.</span><span>letters</span><span>.</span><span>inverted</span><span>)</span>\n <span>.</span><span>lowercased</span><span>()</span>\n <span>.</span><span>non<wbr>Empty</span>\n <span>else</span> <span>{</span>\n <span>return</span> <span>nil</span>\n <span>}</span>\n <span>self</span><span>.</span><span>id</span> <span>=</span> <span>id</span>\n <span>}</span>\n <span>}</span>\n <span>Whosit</span><span>(</span><span>id</span><span>:</span> <span>"Cow"</span><span>)?</span><span>.</span><span>id</span> <span>// cow</span>\n <span>Whosit</span><span>(</span><span>id</span><span>:</span> <span>"--- cow ---"</span><span>)?</span><span>.</span><span>id</span> <span>// cow</span>\n <span>Whosit</span><span>(</span><span>id</span><span>:</span> <span>"\uD83D\uDC2E"</span><span>)</span> <span>// nil</span>\n </code></pre>\n<h3>\n<a href=\"https://nshipster.com/identifiable/#url-as-id\"></a>URL as ID</h3>\n<p>URLs (or <dfn>URIs</dfn> if you want to be pedantic)\nare arguably the most ubiquitous kind of identifier\namong all of the ones described in this article.\nEvery day, billions of people around the world use URLs\nas a way to point to a particular part of the internet.\nSo URLs a natural choice for an <code>id</code> value\nif your models already include them.</p>\n<p>URLs look like strings,\nbut they use <a rel=\"noopener noreferrer\" href=\"https://tools.ietf.org/html/rfc3986\">syntax</a>\nto encode multiple components,\nlike scheme, authority, path, query, and fragment.\nAlthough these formatting rules dispense with much of the invalid input\nyou might otherwise have to consider for strings,\nthey still share many of their complexities —\nwith a few new ones, just for fun.</p>\n<p>The essential problem is that\nequivalent URLs may not be equal.\nIntrinsic, syntactic details like\ncase sensitivity,\nthe presence or absence of a trailing slash (<code>/</code>),\nand the order of query components\nall affect equality comparison.\nSo do extrinsic, semantic concerns like\na server’s policy to upgrade <code>http</code> to <code>https</code>,\nredirect from <code>www</code> to the apex domain,\nor replace an IP address with a\nwhich might cause different URLs to resolve to the same webpage.</p>\n<pre data-lang=\"Swift\"><code><span>URL</span><span>(</span><span>string</span><span>:</span> <span>"https://nshipster.com/?a=1&b=2"</span><span>)</span><span>!</span> <span>==</span>\n <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"http://www.NSHipster.com?b=2&a=1"</span><span>)</span><span>!</span> <span>// false</span>\n <span>try!</span> <span>Data</span><span>(</span><span>contents<wbr>Of</span><span>:</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"https://nshipster.com?a=1&b=2"</span><span>)</span><span>!</span><span>)</span> <span>==</span>\n <span>Data</span><span>(</span><span>contents<wbr>Of</span><span>:</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"http://www.NSHipster.com?b=2&a=1"</span><span>)</span><span>!</span><span>)</span> <span>// true</span>\n </code></pre>\n<aside>\n<p>Many of the same concerns apply to file URLs as well,\nwhich have the additional prevailing concern of resolving relative paths.</p>\n</aside>\n<p>If your model gets identifier URLs for records from a trusted source,\nthen you may take URL equality as an article of faith;\nif you regard the server as the ultimate source of truth,\nit’s often best to follow their lead.</p>\n<p>But if you’re working with URLs in any other capacity,\nyou’ll want to employ some combination of\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/URL_normalization\">URL normalizations</a>\nbefore using them as an identifier.</p>\n<p>Unfortunately, the Foundation framework doesn’t provide\na single, suitable API for URL canonicalization,\nbut <code>URL</code> and <code>URLComponents</code> provide enough on their own\nto let you roll your own\n(<em>though we’ll leave that as an exercise for the reader</em>):</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>fileprivate</span> <span>extension</span> <span>URL</span> <span>{</span>\n <span>var</span> <span>normalized<wbr>String</span><span>:</span> <span>String</span> <span>{</span> <var>...</var> <span>}</span>\n <span>}</span>\n <span>struct</span> <span>Whatsit</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>let</span> <span>url</span><span>:</span> <span>URL</span>\n <span>var</span> <span>id</span><span>:</span> <span>{</span> <span>url</span><span>.</span><span>normalized<wbr>String</span> <span>}</span>\n <span>}</span>\n <span>Whatsit</span><span>(</span><span>url</span><span>:</span> <span>"https://example.com/123"</span><span>)</span><span>.</span><span>id</span> <span>// example.com/123</span>\n <span>Whatsit</span><span>(</span><span>id</span><span>:</span> <span>"http://Example.com/123/"</span><span>)</span><span>.</span><span>id</span> <span>// example.com/123</span>\n </code></pre>\n<h2>\n<a href=\"https://nshipster.com/identifiable/#creating-custom-identifier-id-types\"></a>Creating Custom Identifier ID Types</h2>\n<p><code>UUID</code> and <code>URL</code> both look like strings,\nbut they use syntax rules to encode information in a structured way.\nAnd depending on your app’s particular domain,\nyou may find other structured data types that\nwould make for a suitable identifier.</p>\n<p>Thanks to the flexible design of the <code>Identifiable</code> protocol,\nthere’s nothing to stop you from implementing your own <code>ID</code> type.</p>\n<p>For example,\nif you’re working in a retail space,\nyou might create or repurpose an existing\n<a rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Universal_Product_Code\"><code>UPC</code></a> type\nto serve as an identifier:</p>\n<pre data-lang=\"Swift\"><code><span>struct</span> <span>UPC</span><span>:</span> <span>Hashable</span> <span>{</span>\n <span>var</span> <span>digits</span><span>:</span> <span>String</span>\n <var>implementation details</var>\n <span>}</span>\n <span>struct</span> <span>Product</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>let</span> <span>id</span><span>:</span> <span>UPC</span>\n <span>var</span> <span>name</span><span>:</span> <span>String</span>\n <span>var</span> <span>price</span><span>:</span> <span>Decimal</span>\n <span>}</span>\n </code></pre>\n<h2>\n<a href=\"https://nshipster.com/identifiable/#three-forms-of-id-requirements\"></a>Three Forms of ID Requirements</h2>\n<p>As <code>Identifiable</code> makes its way into codebases,\nyou’re likely to see it used in one of three different ways:</p>\n<p>The newer the code,\nthe more likely it will be for <code>id</code> to be a <dfn>stored</dfn> property —\nmost often this will be declared as a constant (that is, with <code>let</code>):</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>// Style 1: id requirement fulfilled by stored property</span>\n <span>struct</span> <span>Product</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>let</span> <span>id</span><span>:</span> <span>UUID</span>\n <span>}</span>\n </code></pre>\n<p>Older code that adopts <code>Identifiable</code>,\nby contrast,\nwill most likely satisfy the <code>id</code> requirement\nwith a <dfn>computed</dfn> property\nthat returns an existing value to serve as a stable identifier.\nIn this way,\nconformance to the new protocol is purely additive,\nand can be done in an extension:</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>struct</span> <span>Product</span> <span>{</span>\n <span>var</span> <span>uuid</span><span>:</span> <span>UUID</span>\n <span>}</span>\n <span>// Style 2: id requirement fulfilled by computed property</span>\n <span>extension</span> <span>Product</span><span>:</span> <span>Identifiable</span> <span>{</span>\n <span>var</span> <span>id</span> <span>{</span> <span>uuid</span> <span>}</span>\n <span>}</span>\n </code></pre>\n<p>If by coincidence the existing class or structure already has an <code>id</code> property,\nit can add conformance by simply declaring it in an extension\n<em>(assuming that the property type conforms to <code>Hashable</code>)</em>.</p>\n<pre data-lang=\"Swift\"><code><span>import</span> <span>Foundation</span>\n <span>struct</span> <span>Product</span> <span>{</span>\n <span>var</span> <span>id</span><span>:</span> <span>UUID</span>\n <span>}</span>\n <span>// Style 3: id requirement fulfilled by existing property</span>\n <span>extension</span> <span>Product</span><span>:</span> <span>Identifiable</span> <span>{}</span>\n </code></pre>\n<p>No matter which way you choose,\nyou should find adopting <code>Identifiable</code> in a new or existing codebase\nto be straightforward and noninvasive.</p>\n<hr>\n<p>As we’ve said <a href=\"https://nshipster.com/numericcast/\">time</a> and <a href=\"https://nshipster.com/never/\">again</a>,\noften it’s the smallest additions to the language and standard library\nthat have the biggest impact on how we write code.\n(This speaks to the thoughtful,\n<a rel=\"noopener noreferrer\" href=\"https://developer.apple.com/videos/play/wwdc2015/408/\">protocol-oriented</a>\ndesign of Swift’s standard library.)</p>\n<p>Because what <code>Identifiable</code> does is kind of amazing:\n<strong>it extends reference semantics to value types</strong>.</p>\n<p>When you think about it,\nreference types and value types differ not in what information they encode,\nbut rather how we treat them.</p>\n<p>For reference types,\nthe stable identifier is the address in memory\nin which the object resides.\nThis fact can be plainly observed\nby the default protocol implementation of <code>id</code> for <code>Any<wbr>Object</code> types:</p>\n<pre data-lang=\"Swift\"><code><span>extension</span> <span>Identifiable</span> <span>where</span> <span>Self</span><span>:</span> <span>Any<wbr>Object</span> <span>{</span>\n <span>var</span> <span>id</span><span>:</span> <span>Object<wbr>Identifier</span> <span>{</span>\n <span>return</span> <span>Object<wbr>Identifier</span><span>(</span><span>self</span><span>)</span>\n <span>}</span>\n <span>}</span>\n </code></pre>\n<p>Ever since Swift first came onto the scene,\nthe popular fashion has been to eschew all reference types for value types.\nAnd this neophilic tendency has only intensified\nwith the announcement of SwiftUI.\nBut taking such a hard-line approach makes a value judgment\nof something better understood to be a difference in outlook.</p>\n<p>It’s no coincidence that much of the terminology of programming\nis shared by mathematics and philosophy.\nAs developers, our work is to construct logical universes, after all.\nAnd in doing so,\nwe’re regularly tasked with reconciling our own mental models\nagainst that of every other abstraction we encounter down the stack —\ndown to the very way that we understand electricity and magnetism to work.</p>"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d68670508:1d8c:90d684ff","originId":"https://www.raywenderlich.com/9527-functional-programming-with-kotlin-and-arrow-getting-started","fingerprint":"5064fed4","title":"Functional Programming with Kotlin and Arrow: Getting Started [FREE]","updated":1569414645000,"summary":{"content":"In this tutorial, you will learn the fundamentals of functional programming and how various Kotlin language features enable functional programming concepts.","direction":"ltr"},"alternate":[{"href":"https://www.raywenderlich.com/9527-functional-programming-with-kotlin-and-arrow-getting-started","type":"text/html"}],"crawled":1569414645000,"published":1569414645000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more","htmlUrl":"http://www.raywenderlich.com/feed"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d635b7080:1d8b:90d684ff","originId":"https://www.raywenderlich.com/5539282-programming-in-swift-fundamentals","fingerprint":"910498d4","title":"Programming in Swift: Fundamentals [SUBSCRIBER]","updated":1569330000000,"summary":{"content":"Learn about Apple’s open source programming language, Swift, through hands-on examples! Take a deep dive into the Swift language, learning about core Swift concepts like loops, collections, types, optionals, functions, classes, and more.","direction":"ltr"},"alternate":[{"href":"https://www.raywenderlich.com/5539282-programming-in-swift-fundamentals","type":"text/html"}],"crawled":1569330000000,"published":1569330000000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more","htmlUrl":"http://www.raywenderlich.com/feed"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]}]} |