Jekyll2026-03-11T10:47:21+00:00https://codechimp.org/feed.xmlCodeChimpAndrew Jackson[email protected]How To Securely Access Your User’s Clipboard in iOS 142021-01-19T14:00:36+00:002021-01-19T14:00:36+00:00https://codechimp.org/how-to-securely-access-your-users-clipboard-in-ios14Header Image

With the introduction of iOS 14, Apple has given users new insight into what apps are doing. One key feature involves the often-overlooked pasteboard (or as most know it, the clipboard). Previously, any app could inspect the pasteboard contents whenever they wanted. Usually, this was for automatically extracting links or phone numbers, but it left the gates open for more malicious snooping.

Now, whenever an app inspects the value of the pasteboard, the user is notified by a temporary popup telling them that they have potentially given important information to this app.

Demo Image

This led to some high-profile apps being called out for what seemed like unreasonable behaviour, forcing them to quickly explain why they were snooping on the pasteboard in the first place. In many cases, they quickly removed pasteboard inspection.

iOS has always had the ability to determine the content type on the pasteboard so you could quickly determine if you wanted to dig further. Checking content type alone won’t trigger the security popup, but it is triggered when digging deeper into the content— particularly string values.

To allow legitimate pasteboard inspection, Apple has provided a new API to help apps determine whether that string is worth digging into and triggering that popup.

This article will explain how to use this feature and also some unexpected limitations. To help you explore whilst learning this new API, I’ve created a very simple SwiftUI application that uses all the options of the API. To set up the sample, create a new SwiftUI application in Xcode, calling it whatever you like, and paste the code below into the ContentView.swift file:

The main body of the code is simple if you are familiar with SwiftUI. It creates a button and a text view. The button will look at the pasteboard, conditionally get the contents, and display them in the text view. The piece we’ll concentrate on is the inspectPasteboard function, breaking it down section by section. If you aren’t using SwiftUI, don’t worry. The pasteboard methods work with UIKit as well, but for this example, getting something on the screen is easier with SwiftUI.

Starting at the top, we have:

if !UIPasteboard.general.hasStrings { return }

This is not new. hasStrings is a very convenient method to determine the type of data in the pasteboard. For our example, we are only interested in textual data, so immediately exit if no strings are present.

The next part is new:

UIPasteboard.general.detectPatterns(for: [UIPasteboard.DetectionPattern.probableWebURL, UIPasteboard.DetectionPattern.number, UIPasteboard.DetectionPattern.probableWebSearch], completionHandler: {result in ...

Now we get into the new bits introduced with iOS 14. detectPatterns allows us to go beyond basic type checking and have some probability that the string will contain something useful. If we inspect it, we will probably be adding useful functionality to our app.

The for parameter takes a set of DetectionPatterns. There are currently three possible patterns you can use. For this example, we will check all three. But in real-world use cases, we’d probably only be checking one of these. We will cover each in turn, explaining what they check and some typical use cases. First, however, we need to talk about how that completionHandler works.

Within that completion handler, we have:

switch result {
case .success(let detectedPatterns):
  ...
case .failure(let error):
  ...
}

That .success result does not mean we’ve found a match to our provided patterns. It just means that the pattern detection has completed. The detectedPatterns value contains a set of matched patterns, so we need to check that. The .failure result would be set if the detectPatterns method encountered an issue accessing the pasteboard — something I’ve never seen happen, but you should gracefully accept that you are unable to look at the pasteboard right now.

Finally, we get to the interesting part: those three if statements checking in turn if the detected patterns contain one we are interested in and we can act upon it. It’s at this point that we know we likely have a value we want to read and call that UIPasteboard.general.string method that will trigger the user popup.

Apple has not documented the exact criteria of each of these patterns, but I will explain how they work. It is highly likely that Apple will refine these over time and possibly add additional patterns to further prevent apps from accessing the pasteboard unnecessarily.


DetectionPattern.probableWebURL

This is probably the most frequently used of the patterns. It determines if the string contains a URL we may want to extract and use. It’s important to note that this will be matched if the string has a URL anywhere within it, so you will have to manipulate the string further yourself if you want to extract the actual URL portion.

The obvious use case is for “read it later”-style apps to take a URL from another app and offer to parse or save it.

This is probably the safest pattern. You are most likely to find something you can actually use when you access the pasteboard value. Things get a little vaguer with the remaining patterns.

DetectionPattern.number

This pattern seems to match a lot of numeric use cases. As well as a simple whole number, numbers with decimal places, and numbers with currency symbols, it also matches strings that start with a number and formulas (1+2, etc.).

Whilst this broad matching may be useful for spreadsheet-/calculator-style applications, you will get a false positive if you are, for example, creating a sales tax calculator and the user had a numbered list item in the pasteboard. Until you call that popup producing UIPasteboard.general.string, you won’t know if you can truly handle the value.

DetectionPattern.probableWebSearch

This is the broadest pattern of them all. It seems to match anything we have in the pasteboard as long as it’s text. I have copied whole paragraphs in and this pattern still matches. It is worth noting that it also produces a positive match for URLs and numbers, so if you are acting on multiple patterns, always handle this last.

I don’t have a particular use case for this one. It’s so broad that I would be hitting the pasteboard value (and thereby showing the popup) regardless.


Conclusion

Whilst the new detection patterns won’t stop the popup, they will help your app determine if it’s going to be useful to inspect the pasteboard. Seeing this popup less frequently as a user gives reassurance that your app isn’t overreaching on the data it collects, though it’s probably worth explaining why you are reading the pasteboard when you have to.

I feel this is the first step in the pasteboard pattern sets. Watch out for changes in the future as Apple refines these further, which will be both good for you as an app developer and for user privacy.

]]>
Andrew Jackson[email protected]
Multiple Complications in watchOS 72020-12-03T14:00:36+00:002020-12-03T14:00:36+00:00https://codechimp.org/multiple-complications-in-watchos7Header Image

With watchOS 7, you can now add multiple complications to your app, exposing different types of views onto your data. Whilst the demo at WWDC 2020 showed how to add multiple complications, it did not detail how to handle interacting with them. For a good user experience when a complication is tapped, you want to open your app in a state that’s relevant to that particular complication.

This article will show you how to add different complications, update them when their descriptions change, and also respond to a particular complication type being tapped.

For our example, we’ll create a simple tabbed watch app showing the headline of three publications. You will be able to add any of these publications to a watch face, and when tapped, it will open the app with the correct tab showing.

We will just create one complication family for this example — a graphicRectangular, which is great for text. So when testing, ensure that you are using a watch face that allows this complication. Infograph Modular will be fine and is my preferred test watch face, as it allows several different complication families. Once you’ve added one family, it’s just rinse-and-repeat to support as many families as you want.

Here’s our app in action:

Demo Image

Throughout this article, I will call out the relevant code you will need to add to an existing watch app project.


Setting Up the Watch App

Before we get to the complications, we need to create the actual watch app.

For this example, we will create a simple static data model shared between the app and the complications:

To view this in the app, we’ll reutilise the default ContentView provided by a new watch app template, just adding a publication state variable and displaying the properties:

For the tabs, we will reuse this single ContentView and use the new SwiftUI app lifecycle to add tabs, passing in the publication to make them distinct. In your App.swift file, change the WindowGroup to add the tabs with an @state variable to maintain the currently selected tab. The tag modifiers of each ContentView are important. These are what map the selectedTab state to each view:

That’s all you need for the app at the moment. You’ll have three views you can swipe through, each showing their publication name and latest headline.


Creating the Complications

If you’ve created basic complications, you’ll know ComplicationController is where all the magic happens to expose your complications. Most of this is boilerplate. The only part we’ll change is the actual information displayed on the complication. We’ll also register our different complication types for each of the tabs.

The getLocalizableSampleTemplate function is what will get presented on the preview for adding a new complication. It’s important to note that this is cached and so cannot be updated to show real complication data. The complication descriptors are what we use to give uniqueness to each complication (which we will handle soon), so this template just shows placeholder text for the publication and the latest headline:

The createTemplate function is very simple. We’ll get the publication for the identifier selected for this complication and display the publication name and latest headline:

Nothing new so far. The important element added with watchOS 7 is the getComplicationDescriptors function. This allows us to describe what complications we are making available:

What we are doing here is creating descriptors for our tabs of data. The displayName is what’s used when editing a watch face to describe the particular complication instance. The Dictionary and NSUserActivity are how we identify the complication type the user has tapped on when we re-enter our app, which we’ll get into next.

It’s important to note that there is a limit to the number of descriptors you can add. Though not documented, it appears that the limit is currently ten for third-party apps.


Responding to Complication Taps

Back to our app code. We need to know when a complication is tapped. This is achieved by registering for onContinueUserActivity events for your activity type you specified when creating the descriptors:

Here, we are adding onContinueUserActivity to our NavigationView and changing the selectedTab state variable to the ID passed in the userActivity, which will change our tab view to the relevant tab.

Updating Complication Descriptions

Whilst not necessary in this example, if your complications change, you will want to update the descriptors and probably the timeline for existing complications. For this, we’ll use the new scenePhase change to detect when our app goes into the background and notify watchOS that we have changed things:


Wrapping Up

Although this is a simple example, I hope the techniques described here allow you to add multiple-complication support to your watch apps, allowing users to choose data that’s most relevant to them and display it on their watch faces.

A full example project is available on GitHub.

]]>
Andrew Jackson[email protected]
Organizing Yourself as an Indie Developer2020-10-15T14:00:36+00:002020-10-15T14:00:36+00:00https://codechimp.org/organizing-yourself-as-an-indie-developerHeader Image

Managing All the Things

As an indie developer, you don’t have anyone else to rely on to do certain tasks. You’re responsible for everything pre- and post-launch:

  • Idea creation — You have that little lightbulb moment and you need to get that down quickly and flesh out a few points you’re trying to achieve with it.
  • Functional aspects — As your idea grows, you start thinking about how people will use the app and what they will expect to be able to do.
  • Technical requirements — All the behind-the-scenes stuff you’ll need to implement and either re-use or research if it’s the first time you’ve ever done a particular thing.
  • Design — There will be a lot of iteration here. The name, the icon, the color theme — they’ll all change as you keep working on your app.
  • Testing — Gaining friends and willing volunteers to see if your creation works the way people expect it to and ironing out those last few bugs.
  • Marketing — Your App Store listing, website, social media presence, creating press packs.
  • Support — Dealing with users and keeping it efficient to avoid spending all your time responding to enthusiastic users.
  • Future roadmap — All those things you had a fuzzy idea about wanting later or feature requests from enthusiastic users once you’ve launched.
  • The next big idea — It’s rare that an indie creates just one app and sits there waiting for the money to roll in. As creatives, we’re always looking for new problems to solve, so you go back to step 1 and start creating an idea, all whilst continuing to improve and support your previous apps.

At my day job, I work in a big corporation where all those tasks are handled by separate teams, with another team streamlining the process and creating all the handoff points and status reporting to yet another team. There are a lot of task management tools created to handle all these tasks, but they are too complicated, focusing on that assignment, handoff, and reporting.

As an indie, this won’t be of any relevance and the way the tools are designed will just slow you down. The last thing you want is to spend time managing the task list rather than working on the tasks themselves.

I have tried many different ways of organizing as an indie over the years (Kanban boards such as Trello, GitHub issues, a OneNote document), but they’ve all lacked in flexibility to handle all the tasks we need to do as an indie with just enough organization capability to keep you on track and remember all the activities required. They are great at what they specialize in, and if your app grows, you can move to some of these. I particularly like GitHub issues for open source projects to plan new features and get feedback from users, but at the start of an app, I need something far more freeform. Putting ideas and user stories in GitHub issues just doesn’t work when your thoughts are flowing and changing rapidly at that early stage.

As an indie, you also don’t need to capture the same detail level as you would when handing off to other team members. A short sentence will usually remind you of what’s required for a particular task.


Mind Mapping — It’s Not Just for Initial Doodles

My solution to keeping track of all these tasks in a lightweight manner is to use a mind-mapping tool. At first, my opinion was tainted by the way that I observed people use these tools in a big corp environment: as a glorified doodle they could hand off to others involved in the process.

But a new app idea made me give mind mapping tools a shot. The idea was only half-formed, but I was enthusiastic and wanted to try a way of building that initial seed into something usable and hopefully what people would want. The more I was putting into this tool and liking the way it helped form ideas, the more I explored its capabilities and found I could do far more than just shape that initial idea. I could create checklists, store links to research, group things into different high-level tasks, and organise random items into features and releases. And with the primary view being just a title, it gave me that mental jog on what’s required.

There are a lot of mind-mapping tools available, all with different features and price tags. Since this was my first time using a mind-mapping tool, I set some very basic selection criteria:

  • It must be available and sync between all the platforms I care about so I could capture ideas whilst sitting on the sofa, on the train, and at my desk.
  • Have the minimum amount of structure. I wanted to spend as little time with this tool performing housekeeping as possible. Even if it had optional dates or priorities, I avoided them. Just seeing those fields on a form encourages me to be a completionist and come up with arbitrary values I’d never meet or care about.
  • Have a design aesthetic I would enjoy working with every day. If a tool didn’t conform to a platform’s design guidelines, it would grate on me every time I used it and quickly lead to me abandoning it.
  • Have an export capability so if I find a better tool later, I could take my work with me (most tools allow importing OPML and FreeMind formats, so look out for these).

With these criteria, I settled on MindNode. This article is in no way a pitch for this particular app. It met all my criteria to start exploring using a mind-mapping tool, but there are many available that would function just as well.

If you want to start getting into mind mapping, then create your own basic criteria. Don’t worry about comparing features on things you don’t even know if you will use yet. You’re exploring a new way of organizing — not setting a big corp strategy — and if you find a better tool later, having one that allows you to export will make the transition easy. Concentrate on the features that will make you use it and stick with it.


Shaping an Idea

I started off using a mind-mapping tool as everyone thinks of them. That starting bubble, just one or two words that sum up the app. It usually gets changed to the app name later, but for now, you need that title to identify this mind map. A few I’ve used for apps are Countdown, Timer, Shopping List, and Barcodes. Even though you don’t know what apps they became, you know a little of what they are for already.

From that initial bubble, it’s easy to just start adding every random thought that comes into your head that you want to capture, which mind-mapping tools are designed to handle well. With that said, I do like to add a very minimal starting structure. I add three nodes immediately: Purpose, Technical, and Design. These will later be split into more nodes, but at this point, my mind is flitting between these aspects, so I need to group things very loosely.

Purpose

This is where the idea of the app is formed. They are either statements or multiple nodes in a question-and-answer format. That way, as I progress, I can add more answers. None are right — they are all options at this stage. I also chain nodes to back up a statement, which can often lead to questioning that statement later. Nodes here will often become technical items or user stories that you can either move from purpose or link to, depending on whether the idea is fully formed.

Technical

As you start defining the purpose of your app and sometimes the design, you’ll start running into things you’ve never done technically. Add them here. You’re building your research list for later and getting that worry about how hard something is out of your head.

Design

Like the technical node, this is a holding area to quickly get thoughts out of your head, allowing you to revisit them and probably iterate on your previous thoughts. Design, for me, covers everything on how I want to present the app.

I always start with Name and Icon, which are usually the last things to get finalized, but as the app progresses, new ideas keep occurring.

I’ll also start capturing thoughts on how I want the app to look, which is usually heavily influenced by the latest trends and anything I haven’t done before, so it will be nav bars, tab views, single screen. It inevitably won’t stick as the purpose and technical aspects evolve, but again, the idea is to stop dwelling on this irrelevant thought now and comforting yourself that it won’t be forgotten.

The start of an idea

After a few minutes, you’ll get a mind map you can start fleshing out with more and more detail until you are ready to create actionable items:

Mindmap

Every mind-mapping tool gives options to style things as much as you want, changing node shapes, colors, adding images. If it helps to add visual clues to where your thoughts are going, do so. I tend to stick to the default formatting applied. For some really big topics, I will add an icon to make it easy to identify later.


Functions and Research

Normally, most people think mind mapping stops there. But after discovering the Notes feature, I saw a way to continue using this very graphical tool to gather the next level of detail I needed before starting to write code.

I need two specific things:

  1. Functional items to build
  2. Research on how to build those things

I already have a technical node with some scary titles of things I don’t know. I start researching these items, gathering as many articles as I can find, and storing the links in the notes to later draw on these to implement that new technical capability. For the functional items, I create a new Functional node off of the root. Many organizations call these user stories, but for our purposes, these are the things we will need to build that will break down into achievable tasks and draw on what we’ve been researching under the technical branch. We don’t need to be as prescriptive as a user story typically is.

Only be as detailed as you need to be. If there’s some really complicated logic you’ve thought about, then add a note about it. If you are adhering to an industry design guideline, you don’t need to detail how navigation will work. Save time. If you will forget a particular nuance, then detail it. If you only see one option, then don’t. You are not writing long-living technical documentation. You are trying to make an app and organize your work as efficiently as possible.

Mindmap

Tasks

Now that you have your purpose nailed, your functional items listed, and research into the technical areas you need to implement noted, you are probably ready to start coding.

This is where all those nodes under Functional will come into their own. You can add tasks to them, allowing you to easily track the status of how far you are with implementing them. The functional list won’t be complete and you will keep adding child nodes to items as you work through them, but this acts as your task list and spurs you on as you start checking off tasks and completing a function.

Most mind-mapping tools come with a handy outline view where you can filter nodes. You’ll want to display only the ones set as a task and then you immediately have your worklist organized by function. It’s a great incentive to tick those tasks off and, with MindNode, watch the rings close on functional aspects of your app.

Mindmap

Getting Ready for Release

Remember, I wanted a mind-mapping tool that would sync across all my devices. As inspiration hit on the sofa, I was adding thoughts on my unique selling pitch and there are now lots of disconnected thoughts in the design branch to allow me to draw out my best ideas and make my store listing.

Your purpose and functional nodes will allow you to draw out your unique features for your store listing. You can either cross-link the nodes or create new refined ones for the pitch items.

I also use the design branch for making a list of release tasks such as screenshots, store listing, social posts, etc., though a separate release branch could be used if you start having a lot of tasks build up.

Mindmap

Post-Release

Even though you think you’ve made the most intuitive app in the world, not everyone thinks like you and support questions will inevitably come in. To reduce the burden of answering these, I build up a Support branch with questions and my responses so I can quickly copy/paste answers. When I get enough people asking the same questions, I either revisit my app design to try to make it easier or add that particular question to an FAQ page for my app.

Mindmap

As enthusiastic users suggest new features, you can start adding them to your functional/technical branches, building out your new tasks for the next version.

Wrap-Up

I hope you found my use of mind mapping insightful. You can use just one tool for the whole lifecycle of an app, and it becomes a living history and documentation for your app with just the right amount of detail that you need as an indie.

Depending on the size of your app, the amount of support you have to do, or if your team grows, you may find that some of these uses for mind mapping start to become suboptimal. The important thing is to try things and evolve your usage over time, but as an indie, having one tool to organize your thoughts and tasks is a good first step.

]]>
Andrew Jackson[email protected]
Creating a curated color picker in SwiftUI2020-09-20T14:00:36+00:002020-09-20T14:00:36+00:00https://codechimp.org/creating-a-curated-color-picker-in-swiftuiHeader Image

Color pickers are used frequently in both iOS and Mac apps to differentiate folders/lists/items in Apple’s own app’s like Shortcuts and Reminders and many indie app’s offer similar functionality.

With SwiftUI 2 Apple added the ColorPicker control and you think great, one quick line of code and I have a color picker, but this isn’t constrained, users can pick any color imaginable, great for paint apps, not so great where we are trying to maintain a style to our app and deal with overlaid text or graphics.

Recently watching a non-tech family member discover they can customize their messaging app and the awful contrast choices made you really don’t want to let users have this much power. Even Apple don’t use the generic full palette picker in their apps, instead providing a curated palette to choose from, colors that they know will look good. When thinking of a solution to this I first started with throwing some RGB colored circles in a list and storing the value. That gets you part of the way but doesn’t allow for colors that adapt well between dark/light mode, high contrast or platform variations for iPhone, iPad, Mac, Apple Watch, TV and CarPlay which all have their own subtle requirements.

In this article I’ll walk you through how I solved this and create a color picker, which I’ve called ColorSwatchView, that can cope with all these different requirements and always present the user with a pleasing color scheme. That’s as long as we can create a pleasing palette for them to choose from in the first place! Creating the colors

Within Xcode’s Assets file where we usually add our app icon we can also add other assets, one of these being Colors. Adding an asset color allows us to give a name to a color and then add variants for all the different devices/settings we want to cater for. This way we can have varying RGB values but if we always refer to it by name we will get the appropriate variant for the current scenario.

Adding a Color Set to our assets.

Within our project open the Assets.xcassets file and then use the + button at the bottom of the asset list and select add Color Set, then give our new color set a name, here I have chosen to prefix all my color sets with swatch_ so I can easily tell which I’m using for my swatch view.

Assets Image

Once we’ve created our color set we get a single universal color which we can set using the standard color pickers.

When I first started with Xcode I was confused as to why a single color was called a Color Set. It’s to allow us to create alternative colors for different scenarios which we’ll do now.

Creating alternative colors

Whilst a single universal color is a good starting point we will probably want to create variants at least for dark mode. To do this we can use the Attribute Inspector button at the top right to get on this pane where we can choose what color variants we want to add. Here I have used the Appearances section to add a dark color but we can use as many combinations of Devices and Appearances as we need to give the best colors for the particular device/setting.

Colors Image

Creating the Color Swatch View

Now we understand why we should use named color assets and not native colors this part is pretty easy. We define our color assets as an array of strings, in the order we want them displayed in the swatch grid.

The @binding property is used to set/hold the currently selected color asset name.

We’re using a LazyVGrid introduced in SwiftUI 2 to automatically layout our color swatch circles, automatically adjusting for the width of the view. We do that by defining our Columns to be adaptive with a minimum width and let LazyVGrid do all the work, it’s not called Lazy for nothing 😀

Within the LazyVGrid we iterate round our named swatches and create a filled circle, colouring the circle by creating a color from our current swatch name. We add an onTapGesture to this to set the selection to be this swatch name.

To show the current selection we conditionally add a slightly larger circle, this time we just want an outline rather than filled. We conditionally show this based on whether the current swatch we are iterating round is equal to the selection.

Because both circles are wrapped in a ZStack they’ll appear on top of each other giving us a nice outlined circle for the current selection.

Using the Color Swatch View

To use our newly created ColorSwatchView control we just add it to our view, assign it a @state and for demo purposes I’ve put a rectangle beneath that reflects the currently selected color by setting a fill and creating a Color with our stored asset name. In a real world scenario we’d save this asset name away in our user defaults or other preferred persistent storage.

Wrap Up

When we run the app we’ll see the Color Swatches displayed in a grid that automatically accommodates the horizontal space available. Choosing a color will change the sample rectangle view below and changing to dark mode will use the slightly more vibrant green color we set.

Demo Image

This is purely a sample and I’m not an expert in color theory so I’ll leave it up to you to decide what variants you need for your particular use cases.

I hope you found this article useful in both how to use Color Sets and also creating a view control for selecting your curated colors.

]]>
Andrew Jackson[email protected]
Implementing multiple action sheets from toolbar buttons with SwiftUI2020-09-15T14:00:36+00:002020-09-15T14:00:36+00:00https://codechimp.org/implementing-multiple-action-sheets-from-toolbar-buttons-with-swiftuiHeader Image

SwiftUI allows you to very easily create action sheets, those slide up dialog’s that almost fill the screen in iOS. There are many different ways to create these but most have subtle side effects, from un-clickable toolbar buttons to un-dismissable sheets. In this article I’ll detail how to reliably create multiple toolbar buttons that display multiple action sheets with buttons to dismiss them.

We are using functionality that is part of SwiftUI 2 supported by iOS 14 and up. If you are targeting anything lower this will not work.

Creating the sheet views

We’ll create the action sheet views first to stop Xcode continually throwing errors about the views not existing when we create the main content view. We’ll create two views, one for Settings which just has a Done button and one for an Add which has the typical Cancel and Done buttons.

SettingsView

This is very straight forward, we create a NavigationView and give it a title. The toolbar has a single primaryAction ToolbarItem that dismisses the sheet. Notice that the toolbar is a modifier of the child of NavigationView, not the NavigationView itself.

AddView

This is a little more complex as we have two ToolbarItem’s. A cancel button with a placement of cancellationAction which just dismisses the sheet, and a Done button with a confirmationAction which will save your data and then dismiss the sheet. I’ve also added a little logic to disable the Done button if value is empty. For a real world implementation you can build out this condition to validate all your fields.

There is nothing particularly special about these two views, the very specific implementation to get these Done and Cancel toolbar buttons to actually dismiss the sheets happens in the main content view which we’ll create next.

Creating the main content view

When you need multiple sheets from buttons on a view there are many examples of adding separate .sheet modifiers to individual toolbar buttons. Whilst this works to display the sheet, if you are programatically dismissing the sheet with toolbar buttons using self.presentationMode.wrappedValue.dismiss() then this doesn’t work. The only way to dismiss the sheet is to swipe down on it, if you have a sheet like AddView where you need Done and Cancel buttons this isn’t going to be useful.

What we need is a single .sheet modifier to the parent that knows which sheet to display. To do this we’ll create an enum of the sheets and a @state to hold the active sheet. The enum must conform to Identifiable so we need to add a computed property id which returns the hash value. Within each toolbar item we’ll create a button that sets the active sheet state variable.

We can then add a single .sheet modifier with a switch statement to display the appropriate sheet based on the current state. Notice we also set the active sheet to nil within onDismiss to remove the currently active sheet.

Now when we run the code we’ll see two toolbar buttons for Settings and Add, tapping on those will open the relevant sheet and we can either slide down to dismiss the sheet or, more importantly tap the Cancel or Done buttons to properly dismiss the sheets.

Demo Image

With SwiftUI it’s extremely easy to create functional UI’s but small unexplainable restrictions make it frustrating at times. Hopefully this article has shown you how to solve a very common requirement that can easily stall your progress if you don’t know exactly where you should be putting your sheets.

]]>
Andrew Jackson[email protected]
Creating and syncing personal snippets in VS Code2020-09-15T13:28:36+00:002020-09-15T13:28:36+00:00https://codechimp.org/creating-and-syncing-personal-snippets-in-vs-codeI recently started a new side project in a language (Perl) and framework (Logitech Media Server AKA Slim Server) I’d never used before. This combination and my lack of experience meant there was no way of debugging, it was log everything and muddle your way through.

Don’t panic! What I’m going to describe is completely agnostic to the language/frameworks used.

I use Visual Studio Code as my editor of choice for most things these days and I was getting fed up with typing the same old things over and over, so I looked into what options VS Code had to make some of this repetition easier.

It turns out VS Code has a very mature snippet facility and getting it working was pretty trivial. This isn’t going to be a tutorial on everything snippet related but a get you started with what I found useful.

Snippets are at 3 levels; Project, Language and Global. For my use case I wanted some language level ones and some global ones since I’m looking at more than one project using Slim.

Within VS Code you can add snippets using File > Preferences > User Snippets (Code > Preferences > User Snippets on macOS)

  • If you are creating a language snippet choose the language.
  • If you are creating a global (framework) snippet choose New Global Snippets File.
  • If you are creating a project level snippet choose New Snippets File for <your project>

With all of these you get a commented out starter json file.

First I created some perl language helpers.

Language Snippets

This is fairly straight forward, a simple IF and FOR statement. The $1 and $2 are completion tabs, the $0 is where you want your cursor after the completions are made. Unfortunately Perl makes extensive use of $ symbols itself so on the FOR statement I had to escape them. The prefix is what you need to type to have these snippets suggested.

With this file saved when I type “if” within a Perl file I get a suggestion, the snippet is actually the second one offered, pressing down then tab inserts the snippet and positions the cursor at the $1 position ready for you to type your condition.

Global Snippets

Global snippets are very similar to language snippets except they can contain elements relevant to multiple languages. I wanted one relevant to the Slim framework which is a mixture of Perl and HTML code. Here’s an extract of what I ended up with.

The only thing to hi-light here is the “scope” element to indicate which language a snippet is relevant for. For the “prefix” element I started all my snippets with slim… to make it easy to get suggestions for something to do with the slim framework.

Project Snippets

These are exactly the same as global snippets but they are located within the .vscode folder of your project.

Syncing

I also wanted to share these snippets I’d created with my partner in crime on this project so I wanted a way to sync them. With Project level snippets that’s easy, just check in the relevant files within the .vscode folder and it’s done. But I wanted to share the language and global snippets.

These weren’t going to change much so I wasn’t after an automated continual syncing mechanism, just a way to easily grab them and update our local copies. I opted for using GitHub to share a project containing these snippets and setting up symbolic links to put them in the right folder for VS Code to use.

On MacOS these files are stored in your User/Library whilst Windows it’s User/AppData. Here’s the commands I used to create those symlinks from my usual projects folder over to where VS Code is looking for them. If you already have a snippets folder in your app data folder the symlink command will fail, you’ll have to delete the snippets folder to be able to create the symlink (just make sure to backup any existing snippets you may have).

Mac

ln -s /Users/<USER>/<YOUR FOLDER>/vscode-snippets/snippets/ /Users/<USER>/Library/Application\ Support/Code/User/snippets

Windows

mklink /D c:\Users\<USER>\AppData\Roaming\Code\User\snippets c:\<YOUR FOLDER>\vscode-snippets\snippets

Any changes you make to the snippets within either the app data folder or your checkout folder will be instantly updated (actually they are the same file) so you can push/pull your git repo to update your snippets. Publishing Snippets as an Extension

Whilst I am not going to publish these snippets as an extension, if you create a great set with wide appeal you can wrap up your snippets into an extension. I’m not going to cover that, but if you are interested there’s more info on the official Microsoft VS Code help site here

I hope you found this article useful and it inspires you to create some snippets of your own and hopefully share them to aid a newbie to a particular language/framework/project.

]]>
Andrew Jackson[email protected]
Versioning multi APK projects with Jenkins2017-05-24T13:28:36+00:002017-05-24T13:28:36+00:00https://codechimp.org/versioning-multi-apk-projects-with-jenkinsI have used build numbers to version APK’s for a while but with the advent of Android Wear 2 multi APK publishing has forced me to think of a creative solution to continue to use build numbers.

The example here is specifically for Android Wear but the principals could easily be adopted for other multi APK needs such as resource or min-sdk specific versions. Likewise most CI platforms will expose build numbers in a similar way to Jenkins so a similar method could be used regardless of your CI platform.

The problem with multi APK publishing is that each version code must be higher than anything previous.

To solve this I decided to multiply my build numbers by 10 and then add a specific digit for the variant. In the case of Android Wear 2 this is in both the Wear and Mobile app modules, for resource or min-sdk specific versions this would be in the Flavours section of your app build.gradle. So for example say I have Mobile and Wear apps, that’s 2 different versions I need. If my build number is 27, for the mobile app I multiply by 10 and then add 1 to give me a version code of 271, for the Wear app I add 2 giving me a version code of 272. For build 28 I get 281 and 282.

This way I get nice blocks of version codes to easily group releases together and I get 9 potential variants (10 if I went for a 0 based increment)

For release tagging in Git I suffix a 0 onto the end of the build number for the tag version and title so it matches the range I’ve published.

I also suffix the version code onto the version name to give a complete version number, and change the archive name to have the version name suffixed as well.

Finally, I like to maintain manual versions for local ad-hoc builds so I check if the Jenkins environment variable is present and fallback to my project level version codes if not.

The Gist below shows the relevant sections of build.gradle files for a typical mobile and wear app along with my static manual version codes in gradle properties and the string mashing I do within Jenkins git publisher.

]]>
Andrew Jackson[email protected]
Restarting your application on upgrade and boot2015-07-11T13:28:36+00:002015-07-11T13:28:36+00:00https://codechimp.org/restarting-your-application-on-upgrade-and-bootI have an application which displays an ongoing notification and although it’s well documented how to start something on boot it’s less well known how to do so when your app is upgraded. So every time my app was upgraded users would have to manually start the app or wait for the next reboot. It’s pretty simple to get code to fire on an upgrade, just use the MY_PACKAGE_REPLACED intent. I usually combine this with the BOOT_COMPLETED to fire the same receiver to set things up properly. It’s also important to set your install location to internalOnly otherwise your external storage may not be mounted and the receiver won’t fire. Here’s the relevant manifest entry and a simple broadcast receiver to do it.

AndroidManifest.xml

<manifest android:installLocation="internalOnly" ...>

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application ...>
       <receiver android:name=".OnStartReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

If you only want to do something on upgrade then you can drop the RECEIVE_BOOT_COMPLETED permission.

OnStartReceiver.java

package com.example.myapp

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class OnStartReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

         //TODO - add your code here to start your notification/whatever
    }
}
]]>
Andrew Jackson[email protected]
Unified versioning of android wear and mobile apps2015-06-09T13:28:36+00:002015-06-09T13:28:36+00:00https://codechimp.org/unified-versioning-of-android-wear-and-mobile-appsHaving to update both mobile and wear version numbers & codes soon becomes a pain when you’re releasing frequently and it’s easy to forget. I dug into Gradle a little and found you can have project level variables so I placed the version numbers in the project level and both the wear and mobile build files reference this.

gradle.properties (project level)

...
VERSION_NAME=1.0.0
VERSION_CODE=1
build.gradle (module level)

android {
    defaultConfig {
        versionName project.VERSION_NAME
        versionCode Integer.parseInt(project.VERSION_CODE)
        ...
    }
...
]]>
Andrew Jackson[email protected]
Reference project level debug.keystore within Gradle build2015-05-09T13:28:36+00:002015-05-09T13:28:36+00:00https://codechimp.org/reference-project-level-debug-keystore-within-gradle-buildEvery time you build/deploy to your device from a different machine, or just install a shared debug apk you’ll have to uninstall/reinstall the app due to the debug certificate changing. It’s also essential to use the same debug certificate if you’re associating certificates against cloud API’s, many Google services require this.

One option is to make sure everyone’s keystore is the same by distributing the debug.keystore and everyone copying it to their ~/.android/ folder. If you work across multiple teams this becomes impossible, and it’s an extra step in on-boarding new members of the project even if you are only one team.

Since the debug.keystore is not confidential my preference is to add it to source control and then reference that specific keystore within my build files. I do it at project root level so that I don’t have to duplicate the keystore between mobile and wear apps.

This small gist shows how you should configure your signingConfigs section to use this keystore.

]]>
Andrew Jackson[email protected]