What’s New in YarnBuddy

My backlog of blog posts I want to write is getting a bit ridiculous at this point. One of them is a follow-up to YarnBuddy’s “App of the Day” feature back in March, complete with screenshots and stats and the whole story of how that feature came to be. I’m not quite ready to put all of that together yet, so today, I just want to note some of the things I’ve been working on lately in YarnBuddy (from a more technical standpoint).

Themes

I’ve released 6 updates for YarnBuddy so far this year (with another one waiting for review), kicking the year off with a major design refresh that introduced the ability to change the app’s theme. I did this by creating a Theme struct that has a number of semantic colors such as “primaryAccent,” “secondaryAccent,” “headerText,” “rowBackground” etc. Next, I set up an AppTheme enum, with each case being the name of a theme. The enum has a variable called “colors” that returns a Theme struct for each case. The result is that I can do things like .background(settings.theme.colors.primaryBackground) and it just works!

However, there are some UI elements in SwiftUI that are notoriously difficult to customize, such as the background of the list view that a Picker pushes onto the stack. I realized that some of my themes could be considered “light,” while others would be more at home with dark mode defaults. In other words, a bright white background would be super jarring in my Supernova theme. So, I decided to override the user’s preferred mode based on the selected theme.

In my SettingsStore class, which manages a number of UserDefaults keys, I added the following:

var theme: AppTheme {
                set {
                        defaults.set(newValue.rawValue, forKey: DefaultKeys.appTheme)
                switch(newValue) {
                case AppTheme.system:
                    UIApplication.shared.windows.first?.overrideUserInterfaceStyle = .unspecified
                case AppTheme.dark, .midnight, .supernova:
                    UIApplication.shared.windows.first?.overrideUserInterfaceStyle = .dark
                    UIApplication.shared.statusBarStyle = .lightContent

                case AppTheme.light, .grapefruit, .creamsicle, .seaside:
                    UIApplication.shared.windows.first?.overrideUserInterfaceStyle = .light
                    UIApplication.shared.statusBarStyle = .darkContent
                default:
                    UIApplication.shared.windows.first?.overrideUserInterfaceStyle = .unspecified
                }
    }
    get { AppTheme(rawValue: defaults.string(forKey: DefaultKeys.appTheme) ?? AppTheme.system.rawValue) ?? AppTheme.system }
}

Those status bar text color overrides are deprecated but I can’t figure out how I would call their replacement in SwiftUI. For now, it works!

Data Export

The next major version, 1.5, gave users the ability to export a zip archive containing all of their photos, pattern PDFs with annotations, and metadata (in plain text files). I wanted to give users some method for getting their data out of the app as soon as possible, even if it wouldn’t be importable. Now that I’ve shipped it, I’ve begun slowly working on a true backup solution using Codable.

To export data using SwiftUI, I created a struct conforming to FileDocument and did all of the data gathering in the fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper method. I used SwiftUI’s .fileExporter modifier to trigger the export. YarnBuddy uses CoreData, so to make things a little easier I created an “exportString” variable in each NSManagedObject subclass that prints a nicely-formatted string of all metadata associated with the class. That way, I just needed to loop through all of the user’s projects and yarn, map the exportStrings to their own array and append them to the appropriate plain text files.

Time Tracking

Version 1.6 introduced time tracking for Pro users. Time tracking can be fun and useful on its own, but what I’m hoping to do is lay the groundwork for some fun “end of the year summary”-type features. For avid knitters and crocheters, it would be neat to know how many projects you completed that year, the average and total time spent on them, etc. Someday I’d also like to have a little “share card” creation studio where users could choose stats and photos to share on social media.

Time tracking is also a great feature to integrate with widgets, Shortcuts, and the watch app, so there will be plenty of low-hanging fruit for me to pick throughout the summer!

Yarn Remaining, User Guide, and Stash Export

Version 1.6.1, when approved, will be a big point update: it includes a new user guide, the ability to export your yarn stash as a CSV file, and new estimates of yarn remaining.

Previously, when a user linked a project to a yarn in their yarn stash, the amount of yarn would not automatically be subtracted from the total stashed amount. The reason for that was logistics: “In progress” projects weren’t guaranteed to be finished, projects could list yarn quantities in terms of the number of skeins (yarn balls), grams, or ounces, and the total amount of yarn in the user’s stash could be shown in terms of skeins, grams, ounces, yards, or meters. In other words, there were potentially a lot of unit conversions required, and in some cases those conversions would only work if the user supplied the length and net weight per yarn ball (which aren’t required fields).

Now, YarnBuddy will attempt to calculate an estimated amount of yarn remaining based on whatever pieces of information is has, using projects marked as finished. If the estimate is clearly wrong, users can override it with a custom value. Hopefully it’s a good compromise, and users will be happy with it.

I don’t get a ton of support email for YarnBuddy, but I wanted to have a place where users could go to get answers to basic questions and even just explore what the app has to offer. So, I wrote a little user guide in plain ol’ HTML using Textastic on my iPad and included it in the app.

YarnBuddy User Guide screenshot

Finally, users can now export their yarn stash as a CSV file. This turned out to be trivially easy, and you can see my entire implementation of it below. If you’re horrified by my lack of error handling, please know that the rest of the app is even worse; it will absolutely give you the heebie-jeebies…just a total wasteland filled with roving packs of feral errors running amok and destroying anything in their path.

struct YBStashCSV: FileDocument {

let managedObjectContext = CoreDataStack.shared.context

static var readableContentTypes: [UTType] { [UTType.commaSeparatedText] }
static var writableContentTypes: [UTType] { [UTType.commaSeparatedText] }

static let yarnWeights = ["Lace (0), Light Fingering, 1-3 ply",
                          "Super Fine (1), Fingering, 4 ply",
                          "Fine (2), Sport, 5 ply",
                          "Light (3), DK, 8 ply",
                          "Medium (4), Worsted/Aran, 10 ply",
                          "Bulky (5), Chunky, 12 ply",
                          "Super Bulky (6), Roving",
                          "Jumbo (7)"]
init() { }

init(configuration: ReadConfiguration) throws {

}

func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {

    let csvText = createCSV()
    return FileWrapper(regularFileWithContents: csvText.data(using: .utf8) ?? Data())

}

func createCSV() -> String {
    let fetchRequest = NSFetchRequest<Yarn>(entityName: "Yarn")
    fetchRequest.resultType = .managedObjectResultType

    var stashString = ""
    stashString.append(contentsOf: "Name,Colorway,Color Family,Dye Lot,Quantity,Remaining,Weight,Length,Net Weight,Fiber Type,Purchase Location,Date Added,Notes\n")

    do {
        let allYarn = try managedObjectContext.fetch(fetchRequest)
        let stash = allYarn.filter({ $0.isStashed })

        for yarn in stash {
            var notesString = ""
            for note in yarn.notesArray {
                notesString.append("\(note.wrappedText)\n")
            }
            stashString.append("\"\(yarn.wrappedName)\",\"\(yarn.wrappedColorName)\",\"\(yarn.wrappedColorFamily)\",\"\(yarn.dyeLot)\",\"\(yarn.quantityValue) \(yarn.wrappedQuantityUnit)\",\"\(yarn.customRemainingString.isEmpty ? yarn.estimatedQuantityRemainingString : yarn.customRemainingString)\",\"\(Self.yarnWeights[Int(yarn.weight)])\",\"\(yarn.lengthString)\",\"\(yarn.weightString)\",\"\(yarn.wrappedFiberType)\",\"\(yarn.wrappedPurchaseLocation)\",\"\(yarn.dateAdded ?? Date())\",\"\(notesString)\"\n")
        }
    } catch let error {
        print(error)
    }
    return stashString
    }
}

Introducing YarnBuddy

YarnBuddy, my new app for knitters and crocheters, is now available on the App Store! I’ll go ahead and get down to the deets.

Features

YarnBuddy is a project tracker and a row counter. That means its primary job is to help you keep track of all the knitting or crochet projects you’re working on (or have worked on in the past) as well as where exactly you left off on each one. Its secondary job is to help you keep an inventory of all the yarn you’ve acquired (there’s always so. much. yarn. ?).

Projects can either be “in progress” or “finished,” or you can optionally move them to the “Archived” section if you don’t want to see them in your main list.

You can import patterns from the document picker, your photo library, or a web page. I’m hoping to add the ability to import PDFs from your Ravelry library in a future release; however, I don’t really have any experience working with web APIs and OAuth, so it may take awhile for me to figure it out!

My favorite feature of YarnBuddy is the little drawer/sheet for row counters that appears at the bottom of a project’s detail view as well as its pattern view. You can add as many counters as you want, link them together, set them to repeat a range, change their color, and more. You can also expand a single counter to fill up the entire screen.

YarnBuddy allows you to add up to 10 projects and unlimited yarn for free. YarnBuddy Pro is an optional subscription that adds the ability to create unlimited projects, add tags and due date reminders, create notes with rich links for projects or yarn, add row alerts, and change the app’s icon. There is also a one-time purchase option with no expiration.

Development

YarnBuddy was built using Core Data and is almost purely SwiftUI, with the exception of a few wrapped views: UISearchBar, UITextView, UICollectionView, and UITextField (because I needed to add an accessory view with a “Done” button to dismiss the numberPad keyboard). I also had to wrap the system pickers for documents and images.

Using an app-wide gradient in the navigation bar required fiddling with the UIAppearance APIs which are also foreign to SwiftUI.

Overall, creating a new project using SwiftUI was a blast, and I highly recommend it. However, I can’t imagine completely rewriting a UIKit app in SwiftUI. The two frameworks require such drastically different mental models for data flow that I get a headache just thinking about it!

YarnBuddy on iPadOS 13

What’s Next

I’m already working on a big update for iOS 14 that will include a modern iPad UI, a watch app, and a widget (at least, that’s the plan!). I also have an enormous list of feature ideas that may or may not make it into the next release, from data export and time tracking to ways to share your progress on social media. Finally, I’m going to think long and hard about clicking that “Mac” checkbox in Xcode. If everything else is shaping up well for the fall, I would absolutely love to bring YarnBuddy to the Mac.

Well, I think that covers everything for now. If you’re in my target audience and have feature requests/suggestions, I’d love to hear ‘em!

Finding the Right Fit

There aren’t many knitting apps on the App Store, and I’m not sure why. There are a couple dedicated to teaching you how to knit, a couple that will help you find knitting patterns, and about eight that could be classified as knitting tools (crocheters, just replace the word “knitting” with “crochet” and everything still holds true).

Of those eight apps, exactly zero of them look like native iOS apps. They all have custom interfaces that seem hellbent on ignoring as many platform conventions as possible. One of them allows you to create a new project by tapping an ordinary tab bar item instead of using a modal view. A few of them pop up full screen ads seemingly at random as you tap around the app. Despite these annoyances, most of them have very good reviews from real people who genuinely find them helpful. But they could be so much better. So why aren’t there any beautiful, well-designed knitting apps?

I have a few theories. The first is that there aren’t really any companies that would be incentivized to build such an app. Red Heart, a yarn brand, isn’t going to hire an iOS dev team. It just doesn’t make sense. There is no software company dedicated to making digital tools for the fiber arts. And while I’d wager that there’s actually a fairly significant overlap between programmers and knitters, the overlap between knitters and independent iOS developers is extremely small, and perhaps is just me (and I don’t even know how to knit…I just crochet!).

Then there’s the possibility, of course, that there’s no demand for such a product…but I don’t believe that. Not when there’s so many reviews on similar apps. Yes, there’s a sense in which knitting and crocheting should be decidedly analog activities, but I believe there are ways that technology can help without getting in the way: think voice commands for controlling a row counter, or a row counter right on your wrist as an Apple Watch app.

One good comparison would be a recipe app where you can add notes to the recipes. That way, when you returned to a recipe to make it again, you could easily see what modifications you made last time. Knitting patterns are like that too, especially if you’re creating a garment in a particular size. Things like notes and photos can be really helpful.

There are also a handful of apps on the App Store that act as clients for Ravelry, the largest online fiber arts community. Ravelry has around a million monthly active users. People use the site to add patterns (both free and paid) to an enormous community database, catalog their yarn stash, share what projects they’re working on, and discuss all kinds of topics in the forums. It’s essentially a social network for knitters and crocheters (you have your own profile, can add friends, etc.).

When I think about where YarnBuddy will “fit” in the world of knitting, crocheting, and apps, I’m hoping to position it as a handy tool for keeping your place in a pattern as well as an offline alternative for tracking your projects and yarn stash. Of course, I also want it to eventually work with Ravelry, using its API to import pattern PDFs (I don’t think that will make the 1.0, though). Finally, I want it to be beautifully-designed and a delight to use.

I’m both excited and terrified to find out if there really is a place for an app like YarnBuddy. The anecdotal data I’ve gathered from friends and family so far has been encouraging. If I’m successful, I have the chance to become one of two or three major players in this niche, and that’s pretty darn exciting. For now I’ll just keep chugging along, making an app that I’d want to use myself and hoping for the best!