SwiftUI Tricks: Part 1

Photo by Clint Patterson on Unsplash

This is the first of mini articles in a series of SwiftUI tips and tricks.

One of the common tasks in mane applications is to determine screen width and layout items accordingly. FitRadar is not an exception. Many UI elements should be adjusted to screen size. There are two problems, tho: “screen” and “width”.

Screen

Most often developer’s think of a “view on screen” when saying “screen”. Physical screen size is equal to view controller size and vice versa. Yet we know that today (as per middle of 2021) we have iPad which allow run two apps side by side, or even one app in two “panels”. Know knows what else Apple introduce in future. We expect our fitness app to run on all possible devices: phones, tablets, watches, etc. So, we can’t rely on physical screen.

Width

Since there is no yet (as per middle of 2021) devices with changing screen, most often developers think of a width of screen as a constant value. However, changing screen zoom (General -> Display & Brightness ->Display Zoom) actually change it. The other and most common scenario: rotation.

So, knowing these two problems we cannot rely anymore on something simple as

UIScreen.main.bounds.size.width

GeometryReader

GeometryReader is second most advised way to get current view geometry. It does it best, but practise show that item places inside it acts differently on iOS 13 and iOS 14. And we actually want to avoid place our UI elements inside it.

For the trick we use PreferenceKey. Just add following code somewhere in project

struct SizePreferenceKey: PreferenceKey {   typealias Value = CGSize   static var defaultValue: Value = .zero   static func reduce(value: inout Value, nextValue: () -> Value) {    value = nextValue()   }}

Next, define some variable to keep current screen width

@State private var screenWidth: CGFloat = 0

Place geometry reader somewhere on the view which size you need to track. But other UI elements can now be placed outside GeometryReader

VStack {    GeometryReader { proxy in        Spacer()         .preference(key: SizePreferenceKey.self, value: proxy.size)    }    .frame(height: 1)    .onPreferenceChange(SizePreferenceKey.self) { preferences in        self.screenWidth = preferences.width    }    Rectangle()        .fill(Color.red)        .frame(width: screenWidth / 2, height: screenWidth / 3)}

This solution works equally on iOS 13 and 14. Enjoy!

P.S. Please visit our website: https://www.fitradar.me/ and join the mailing list! Fitness is closer than you think.

P.S.S. Text by https://dmi3j.medium.com/