Caching data using NSCache in iOS

Introduction I was curious about caching data using NSCache for an iOS app. So, I did some digging. Here is what I found: Quick Overview NSCache helps store data in memory. When the application gets killed, it frees memory; it鈥檚 not persisted on disk. Storing data is carried out using a key-value pair mechanism like Dictionary. You can set automatic eviction to delete objects automatically. NSCache has multi-platform support: iOS, iPadOS, watchOS, macOS, and tvOS. Caveats NSCache has Objective-C roots. It can鈥檛 use struct because it is constrained to conform to AnyObject, meaning you must use class and NSString instead of String. ...

June 5, 2024 路 3 min 路 Dmytro Chumakov

Accessibility iOS SwiftUI

Introduction Previously, I posted about Accessibility for UIKit. The idea behind this post is to find differences between UIKit Accessibility and SwiftUI features. Similarities: Both UIKit and SwiftUI have accessibilityLabel and accessibilityHints APIs. Differences: To use dynamic type for fonts, you need additional modifiers in SwiftUI. struct ScaledFont: ViewModifier { @Environment(\.sizeCategory) var sizeCategory var name: String var size: Double func body(content: Content) -> some View { let scaledSize = UIFontMetrics.default.scaledValue(for: size) return content.font(.custom(name, size: scaledSize)) } } extension View { func scaledFont(name: String, textSize size: Double) -> some View { return self.modifier(ScaledFont(name: name, size: size)) } } To step over elements in a list, you need to add .accessibilityElement(children: .combine) to each row in SwiftUI. struct FruitCaloriesCounter: View { var body: some View { NavigationView { List(fruits) { fruit in FruitRow(fruit: fruit) .accessibilityElement(children: .combine) } .navigationTitle("Fruits Calories Counter") .accessibilityElement(children: .contain) .navigationBarTitleDisplayMode(.inline) } } } In UIKit, you can insert and remove accessibilityTraits depending on the button state: if button.isSelected { button.accessibilityTraits.insert(.header) } else { button.accessibilityTraits.remove(.header) } In SwiftUI, you need to pass .accessibilityAddTraits(selected ? [.isSelected, .isButton] : .isButton) to one modifier. Button(action: { selected.toggle() }) { Image(systemName: selected ? "star.fill" : "star") .frame(width: 44, height: 44) .accessibilityLabel("favourite") .accessibilityHint(selected ? "removes favourite" : "makes favourite") .accessibilityAddTraits(selected ? [.isSelected, .isButton] : .isButton) } .buttonStyle(.plain) Complete Sample import SwiftUI let fruits = [ Fruit(name: "Apple", calories: 52), Fruit(name: "Banana", calories: 89), Fruit(name: "Orange", calories: 47), Fruit(name: "Pineapple", calories: 50), Fruit(name: "Strawberry", calories: 32) ] struct Fruit: Identifiable { var id: String { name } let name: String let calories: Int } struct FruitRow: View { @State private var selected = false let fruit: Fruit var body: some View { HStack(spacing: 8) { VStack(alignment: .leading, spacing: 8) { Text(fruit.name) .scaledFont(name: "Helvetica", textSize: 20) .accessibilityLabel(fruit.name) Text("\(fruit.calories) per 100g") .scaledFont(name: "Helvetica", textSize: 15) .accessibilityLabel("\(fruit.calories) calories per 100 grams") } Spacer() Button(action: { selected.toggle() }) { Image(systemName: selected ? "star.fill" : "star") .frame(width: 44, height: 44) .accessibilityLabel("favourite") .accessibilityHint(selected ? "removes favourite" : "makes favourite") .accessibilityAddTraits(selected ? [.isSelected, .isButton] : .isButton) } .buttonStyle(.plain) } } } struct FruitCaloriesCounter: View { var body: some View { NavigationView { List(fruits) { fruit in FruitRow(fruit: fruit) .accessibilityElement(children: .combine) } .navigationTitle("Fruits Calories Counter") .accessibilityElement(children: .contain) .navigationBarTitleDisplayMode(.inline) } } } struct ContentView: View { var body: some View { FruitCaloriesCounter() } } #Preview { ContentView() } struct ScaledFont: ViewModifier { @Environment(\.sizeCategory) var sizeCategory var name: String var size: Double func body(content: Content) -> some View { let scaledSize = UIFontMetrics.default.scaledValue(for: size) return content.font(.custom(name, size: scaledSize)) } } extension View { func scaledFont(name: String, textSize size: Double) -> some View { return self.modifier(ScaledFont(name: name, size: size)) } } Thank you for reading! 馃槉

June 2, 2024 路 3 min 路 Dmytro Chumakov

Accessibility iOS UIKit

Introduction I was curious to find out how to make an application more accessible. You can look at popular applications like YouTube or Netflix; they all have accessibility features like VoiceOver and dynamic fonts. I decided to create this example for a fruit calorie counter. It contains a list of fruits with the fruit name, fruit calories, and a favorite button. Where to Start Before diving into implementation details, I want to highlight some information about the existing accessibility features and what I will be focusing on. ...

May 30, 2024 路 3 min 路 Dmytro Chumakov

Text To Speech iOS

Introduction I was eager to learn how converting Text To Speech works in iOS. Here is what I discovered: First Step The first step is to add AVSpeechSynthesizer, an object that produces synthesized speech from text utterances. @State private var speechSynthesizer = AVSpeechSynthesizer() Second Step The second step is to add AVSpeechUtterance, an object that encapsulates the text for speech synthesis. private var utterance: AVSpeechUtterance { let inputMessage = "Hello world!" let utterance = AVSpeechUtterance(string: inputMessage) utterance.voice = AVSpeechSynthesisVoice(language: "en-US") return utterance } Optional You can configure pitch, rate, and voice parameters. ...

May 24, 2024 路 1 min 路 Dmytro Chumakov

Speech To Text iOS

Introduction I always wanted an iOS app that would allow me to economize my time by converting speech to text. I know this option is built into the keyboard, but you first need to click the text field, then tap on the microphone, and finally speak. I wanted a one-click option with the possibility to integrate it into all my daily routines. Here is what I discovered: ...

May 23, 2024 路 4 min 路 Dmytro Chumakov