Technical articles about iOS development, Swift, SwiftUI, and the mobile ecosystem.
If you want a better control in line breaking inside UILabel or UITextView you can use a no-break space (NBSP).
If you want a better control in line breaking inside UILabel or UITextView you can use a no-break space that you may also know as non-breakable space (NBSP). In Swift, this is a special character and it is shown like this:
\u{00a0}
So let's imagine that you have for example a US domestic phone number like (555) 123–4567 and you have to display it inside a UILabel along with a text.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Without non-breaking space - phone number may break across lines
label.text = "Please call us at (555) 123-4567 for more information"
}
}
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// With non-breaking space - keeps phone number together
let phoneNumber = "(555)\u{00a0}123–4567"
label.text = "Please call us at \(phoneNumber) for more information"
}
}
Non-breaking spaces are particularly useful for:
The non-breaking space character (\u{00a0}) ensures that text elements that should stay together won't be separated by automatic line breaks, giving you better control over text layout in your iOS applications.
When your app crashes, it is never a good thing, but hopefully, you have ways to understand what happens. Learn how to symbolicate crash logs manually.
When your app crashes, it is never a good thing, but hopefully, you have ways to understand what happens. If you upload your app directly through Xcode you can find the crashes directly from the organizer window, symbolicate it and even open the line that crashed inside your project. You can also use some crash reporting tools that will do all the work for you.
But sometimes, especially if you work on an SDK, you may receive a crash log by email or sometimes only just the line where the crash happens.
Crashed: com.apple.main-thread
0 libswiftCore.dylib 0x3dcd08 swift_isUniquelyReferenced_nonNull_native + 38
1 YourSDK 0x63974 _hidden#10814_ + 36 (__hidden#11065_:36)
2 YourSDK 0x72394 _hidden#10850_ + 80 (__hidden#11065_:80)
3 YourSDK 0x1834c _hidden#10852_ + 4395545212 (__hidden#593_:4395545212)
Here the crash is caused by the SDK, but right now it is impossible to know what line of the code generated the crash. There are 3 lines that are really important for us to decrypt: 1, 2, and 3. To symbolicate these lines we need the .dSYM of our SDK. Hopefully when you archive your project Xcode generates the .dSYM file for you.
We are going to use the following command line atos -arch arm64 -o followed by the path of the DWARF file contained inside the .dSYM archive:
atos -arch arm64 -o YOURSDK.framework.dSYM/Contents/Resources/DWARF/YOURSDK 0x63974
The output may look like this:
YourClassThatCrash.theMethodThatCrash() (in YOURSDK) (YourClassThatCrash.swift:74)
With this it should be much easier to find the crash, correct it and make your users happy.
What Apple presented about AdTech: SKAdNetwork 4.0, Ads with SharePlay, Pasteboard access, Location attribution…
Apple released some videos relevant to the Ad Tech industry at WWDC 2022. At Teads, we take very seriously the fact of being up to date with new technologies. Here's a summary of the key announcements.
With iOS 16, there are new features about privacy and security:
The name of the app using the user location will now appear in conjunction with the location symbol system-wide — if an app uses your location, you will know which app, even outside the app.
To access the shared clipboard or simply paste the content from another app, you now have to ask permission. Apple offers controls that allow the user to paste the content of the clipboard with a tap without requiring a full permission prompt.
Before iOS 16, an app could access the device name (e.g., "Antoine's iPhone"). Now UIDevice.name will return just the model name (iPhone / iPad). You can still access the user-assigned name using a special entitlement, but sharing it with third parties is not permitted.
For Apple, privacy is a fundamental human right. Tracking is the fact of linking user or device data collected inside your app with data collected from others (apps, websites, offline data) for targeting ads or advertising measurement. If the user denies ATT, you can still gather information for internal use but cannot share it with third parties.
SKAdNetwork is Apple's privacy-preserving ad-attribution system. Version 4.0 introduces:
A method to pass information to the ad network depending on the number of installations. If the app has few installations the attribution data sent will be limited. With more installations, more data will be sent. There are 3 levels: Low, Medium, and High.
The source identifier replaces the campaign field and expands from two to four digits. The conversion value is split into a fine-grained value (6-bit, 0-63) and a coarse-grained value (low, medium, high). Which value is sent depends on the crowd anonymity level.
// Conversion value split
enum CoarseGrainedValue {
case low
case medium
case high
}
// Fine-grained value: 6-bit value (0-63)
let fineGrainedValue: Int = 42
// Only one value is sent based on crowd anonymity level
It is now possible to use SKAdNetwork for ads on the Web (Safari only) for ads that advertise an App Store page.
With iOS 13 Apple added a dark mode to its operating system. Learn how to implement it in your app.
With iOS 13 Apple added a dark mode to its operating system. Almost two months after the launch there are only few apps that already implement it. I will try to show you how you can implement it in your app and make your users happiest.
Apple shipped basic system colors with iOS 13. Instead of UIColor.blue, you can use UIColor.systemBlue that gives you a different blue on light and dark mode without any effort.
if #available(iOS 13, *) {
view.backgroundColor = .systemBlue
} else {
view.backgroundColor = .blue
}
Two other useful colors: UIColor.label (black on light, white on dark) and UIColor.systemBackground (white on light, black on dark).
With iOS 13, Apple added initWithDynamicProvider for UIColor, which lets you access the current traitCollection and know if the user is using dark mode.
protocol AppColor {
var light: UIColor { get }
var dark: UIColor { get }
var fallbackColorBeforeiOS13: UIColor { get }
func color() -> UIColor
}
extension AppColor {
func color() -> UIColor {
if #available(iOS 13, *) {
return UIColor.init { (trait) -> UIColor in
return trait.userInterfaceStyle == .dark ? self.dark : self.light
}
}
return self.fallbackColorBeforeiOS13
}
}
enum CustomColors: AppColor {
case primary, secondary
var light: UIColor {
switch self {
case .primary: return UIColor(red: 0, green: 0.8, blue: 0.69, alpha: 1)
case .secondary: return UIColor(red: 219/255, green: 230/255, blue: 228/255, alpha: 1.0)
}
}
var dark: UIColor {
switch self {
case .primary: return light
case .secondary: return UIColor(red: 0, green: 205/255, blue: 177/255, alpha: 0.3)
}
}
var fallbackColorBeforeiOS13: UIColor {
return light
}
}
For the launch screen, create a new color set in your assets catalog and enable dark mode appearance. For images, the process is the same: enable dark mode on the image asset and provide dark appearance variants. Your app will automatically load the correct variant based on the user's appearance setting.
Apple made it really easy for developers to implement dark mode, so we should — if we care about our users.
Step-by-step instructions on how to build a countdown timer using the SwiftUI framework.
Apple launched SwiftUI, a framework that allows us to make app interfaces in a 100% swifty way. Here's how to make a countdown timer.
To start with SwiftUI, download the latest build of Xcode 11 and macOS Catalina 10.15. SwiftUI is only available on iOS 13, macOS 10.15, watchOS 6 and iPadOS 13.
To refresh our interface every second, we use a timer and declare a date for our countdown:
import SwiftUI
struct ContentView: View {
@State var date = Date()
let referenceDate = Date()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
Text("Hello World")
.onReceive(timer) { input in
date = input
}
}
}
Note here the @State recreates our interface every time the date is updated.
func countDownString(from referenceDate: Date, until date: Date) -> String {
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.day, .hour, .minute, .second],
from: referenceDate,
to: date)
return String(format: "%02d:%02d:%02d:%02d",
components.day ?? 0,
components.hour ?? 0,
components.minute ?? 0,
components.second ?? 0)
}
import SwiftUI
struct ContentView: View {
@State var date = Date()
let referenceDate = Date()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
Text("Countdown Timer")
.font(.largeTitle)
.padding()
Text(countDownString(from: referenceDate, until: date))
.font(.system(size: 40, design: .monospaced))
.padding()
.onReceive(timer) { input in
date = input
}
.onAppear(perform: {
date = Date()
})
}
}
func countDownString(from referenceDate: Date, until date: Date) -> String {
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.day, .hour, .minute, .second],
from: referenceDate,
to: date)
return String(format: "%02d:%02d:%02d:%02d",
components.day ?? 0,
components.hour ?? 0,
components.minute ?? 0,
components.second ?? 0)
}
}
This creates a simple countdown timer that updates every second using SwiftUI's @State property wrapper and the Timer.publish method.