Adding Push Notifications to an iOS App

Introduction If you start a project from scratch, you need to always create some kind of service like PushNotificationService that will be responsible for handling push notification events. In this article, I want to explore a simple implementation of PushNotificationService to be able to reuse and customize it in future projects. First Step The first step is to add the Push Notifications capability to your project. Go to your project -> Signing & Capabilities -> Tap + Capability -> Search for Push Notifications. ...

June 14, 2024 · 2 min · Dmytro Chumakov

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’s 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’t 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