With the introduction of the new matchedTransitionSource from iOS 18, we can apply a zoom transition in the navigation view using .navigationTransition(.zoom) This works well for zoom animations.
However, when I try to apply a matched geometry effect to views that are similar in the source and destination views, the zoom transition works, but those views don't transition seamlessly as they do with a matched geometry effect.
Is it possible to still use matched geometry for subviews of the source and destination views along with the new navigationTransition?
Here’s a little demo that reproduces this behaviour:
struct ContentView: View {
let colors: [[Color]] = [
[.red, .blue, .green],
[.yellow, .purple, .brown],
[.cyan, .gray]
]
@Namespace() var namespace
var body: some View {
NavigationStack {
Grid(horizontalSpacing: 50, verticalSpacing: 50) {
ForEach(colors, id: \.hashValue) { rowColors in
GridRow {
ForEach(rowColors, id: \.self) { color in
NavigationLink {
DetailView(color: color, namespace: namespace)
.navigationTransition(
.zoom(
sourceID: color,
in: namespace
)
)
.edgesIgnoringSafeArea(.all)
} label: {
ZStack {
RoundedRectangle(cornerRadius: 5)
.foregroundStyle(color)
.frame(width: 48, height: 48)
Image(systemName: "star.fill")
.foregroundStyle(Material.bar)
.matchedGeometryEffect(id: color,
in: namespace,
properties: .frame, isSource: false)
}
}
.matchedTransitionSource(id: color, in: namespace)
}
}
}
}
}
}
}
struct DetailView: View {
var color: Color
let namespace: Namespace.ID
var body: some View {
ZStack {
color
Image(systemName: "star.fill")
.resizable()
.foregroundStyle(Material.bar)
.matchedGeometryEffect(id: color,
in: namespace,
properties: .frame, isSource: false)
.frame(width: 100, height: 100)
}
.navigationBarHidden(false)
}
}
#Preview {
ContentView()
}
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
In a SwiftUI macOS application, when removing the title bar by setting window.titleVisibility to .hidden and window.titlebarAppearsTransparent to true, the title bar space is still accounted for in the window height. This results in a delta (red area) in the height of the window that cannot be ignored by usual SwiftUI view modifiers like .edgesIgnoringSafeArea(.top). My actual view is the blue area.
I want to have the view starting in the top safeArea and the window to be the exact size of my view. I am struggling to achieve this. I have also tried with window.styleMask.insert(.fullSizeContentView) to no effect.
Can I get some help? 🙂
Here is my source code to reproduce this behavior:
windowApp.swift
@main
struct windowApp: App {
@NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
.edgesIgnoringSafeArea(.top)
.background(.red)
.border(.red)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
if let window = NSApplication.shared.windows.first {
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
window.styleMask.insert(.fullSizeContentView)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.frame(minWidth: 400, minHeight: 200)
.background(.blue)
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hello everyone,
I'm working on a SwiftUI app that requires location services, and I've implemented a LocationManager class to handle location updates and permissions. However, I'm facing an issue where the location permission popup does not appear when the app is launched.
Here is my current implementation:
LocationManager.swift:
import CoreLocation
import SwiftUI
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var userLocation: CLLocation?
@Published var isAuthorized = false
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
override init() {
super.init()
locationManager.delegate = self
checkAuthorizationStatus()
}
func startLocationUpdates() {
locationManager.startUpdatingLocation()
}
func stopLocationUpdates() {
locationManager.stopUpdatingLocation()
}
func requestLocationAuthorization() {
print("Requesting location authorization")
DispatchQueue.main.async {
self.locationManager.requestWhenInUseAuthorization()
}
}
private func checkAuthorizationStatus() {
print("Checking authorization status")
authorizationStatus = locationManager.authorizationStatus
print("Initial authorization status: \(authorizationStatus.rawValue)")
handleAuthorizationStatus(authorizationStatus)
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print("Authorization status changed")
authorizationStatus = manager.authorizationStatus
print("New authorization status: \(authorizationStatus.rawValue)")
handleAuthorizationStatus(authorizationStatus)
}
private func handleAuthorizationStatus(_ status: CLAuthorizationStatus) {
switch status {
case .authorizedAlways, .authorizedWhenInUse:
DispatchQueue.main.async {
self.isAuthorized = true
self.startLocationUpdates()
}
case .notDetermined:
requestLocationAuthorization()
case .denied, .restricted:
DispatchQueue.main.async {
self.isAuthorized = false
self.stopLocationUpdates()
print("Location access denied or restricted")
}
@unknown default:
DispatchQueue.main.async {
self.isAuthorized = false
self.stopLocationUpdates()
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
DispatchQueue.main.async {
self.userLocation = locations.last
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location manager error: \(error.localizedDescription)")
}
}
MapzinApp.swift:
@main
struct MapzinApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@StateObject private var locationManager = LocationManager()
var body: some Scene {
WindowGroup {
Group {
if locationManager.authorizationStatus == .notDetermined {
Text("Determining location authorization status...")
} else if locationManager.isAuthorized {
CoordinatorView()
.environmentObject(locationManager)
} else {
Text("Location access is required to use this app. Please enable it in Settings.")
}
}
}
}
}
Log input:
Checking authorization status
Initial authorization status: 0
Requesting location authorization
Authorization status changed
New authorization status: 0
Requesting location authorization
Despite calling requestWhenInUseAuthorization() when the authorization status is .notDetermined, the permission popup never appears. Here are the specific steps I have taken:
Checked the Info.plist to ensure the necessary keys for location usage are present:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
Verified that the app's target settings include location services capabilities.
Tested on a real device to ensure it's not a simulator issue.
I'm not sure what I might be missing. Any advice or suggestions to resolve this issue would be greatly appreciated. Thank you!
This is a report of an issue that appears to be a regression regarding NavigationStack.
I have been aware of an issue where views are being automatically popped within NavigationView / NavigationStack since iOS 15, and it seems to be reoccurring in iOS 18.0 beta2.
Below is the reproducible code. Additionally, in my environment, this problem does not occur iOS 18 simulator, but it does happen on an iPhone XS Max(real device) with iOS 18 beta 2.
Environment:
Xcode: Version 16.0 beta (16A5171c)
iOS: 18.0 (22A5297f)
iPhone: XS Max (real device)
import SwiftUI
@main
struct iOS16_4NavigationSample2App: App {
var body: some Scene {
WindowGroup {
NavigationStack {
NavigationLink {
ContentView()
} label: {
Text("Content")
}
}
}
}
}
enum Kind { case none, a, b, c }
struct Value: Hashable, Identifiable {
let id: UUID = UUID()
var num: Int
}
@MainActor
class ContentModel: ObservableObject {
@Published var kind: Kind = .a
@Published var vals: [Value] = {
return (1...5).map { Value(num: $0) }
}()
init() {}
}
struct ContentView: View {
@StateObject private var model = ContentModel()
@State private var selectedData: Value?
@State private var isShowingSubView = false
@Environment(\.dismiss) private var dismiss
init() {
}
var body: some View {
if #available(iOS 16.0, *) {
List(selection: $selectedData) {
ForEach(model.vals) { val in
NavigationLink(value: val) {
Text("\(val.num)")
}
}
}
.navigationDestination(isPresented: .init(get: {
selectedData != nil
}, set: { newValue in
if !newValue && selectedData != nil {
selectedData = nil
}
}), destination: {
SubView(kind: model.kind)
})
}
}
}
struct SubView: View {
init(kind: Kind) {
print("init(kind:)")
}
init() {
print("init")
}
var body: some View {
Text("Content")
}
}
This code was shared in a different issue [iOS 16.4 NavigationStack Behavior Unstable].
I’m seeing a crash in production for a small percentage of users, and have narrowed it down based on logging to happening as or very shortly after an alert is presented using SwiftUI.
This seems to be isolated to iOS 17.5.1, but since it’s a low-volume crash I can’t be sure there aren’t other affected versions. What can I understand from the crash report?
Here’s a simplified version of the code which presents the alert, which seems so simple I can’t understand why it would crash. And following that is the crash trace.
// View (simplified)
@MainActor public struct MyView: View {
@ObservedObject var model: MyViewModel
public init(model: MyViewModel) {
self.model = model
}
public var body: some View {
myViewContent
.overlay(clearAlert)
}
var clearAlert: some View {
EmptyView().alert(
"Are You Sure?",
isPresented: $model.isClearAlertVisible,
actions: {
Button("Keep", role: .cancel) { model.clearAlertKeepButtonWasPressed() }
Button("Delete", role: .destructive) { model.clearAlertDeleteButtonWasPressed() }
},
message: {
Text("This cannot be undone.")
}
)
}
}
// Model (simplified)
@MainActor public final class MyViewModel: ObservableObject {
@Published var isClearAlertVisible = false
func clearButtonWasPressed() {
isClearAlertVisible = true
}
func clearAlertKeepButtonWasPressed() {
// No-op.
}
func clearAlertDeleteButtonWasPressed() {
// Calls other code.
}
}
Incident Identifier: 36D05FF3-C64E-4327-8589-D8951C8BAFC4
Distributor ID: com.apple.AppStore
Hardware Model: iPhone13,2
Process: My App [379]
Path: /private/var/containers/Bundle/Application/B589E780-96B2-4A5F-8FCD-8B34F2024595/My App.app/My App
Identifier: com.me.MyApp
Version: 1.0 (1)
AppStoreTools: 15F31e
AppVariant: 1:iPhone13,2:15
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.me.MyApp [583]
Date/Time: 2024-06-21 20:09:20.9767 -0500
Launch Time: 2024-06-20 18:41:01.7542 -0500
OS Version: iPhone OS 17.5.1 (21F90)
Release Type: User
Baseband Version: 4.50.06
Report Version: 104
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a69998c0
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [379]
Triggered by Thread: 0
Kernel Triage:
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
Thread 0 name:
Thread 0 Crashed:
0 libswiftCore.dylib 0x00000001a69998c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144)
1 AttributeGraph 0x00000001d0cd61a4 Attribute.init<A>(body:value:flags:update:) + 352 (Attribute.swift:473)
2 SwiftUI 0x00000001ac034054 closure #1 in Attribute.init<A>(_:) + 128 (<compiler-generated>:0)
3 SwiftUI 0x00000001ac033cac partial apply for closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0)
4 libswiftCore.dylib 0x00000001a6ad0450 withUnsafePointer<A, B>(to:_:) + 28 (LifetimeManager.swift:128)
5 SwiftUI 0x00000001ad624d14 closure #2 in UIKitDialogBridge.startTrackingUpdates(actions:) + 268 (UIKitDialogBridge.swift:370)
6 SwiftUI 0x00000001ad624ae0 UIKitDialogBridge.startTrackingUpdates(actions:) + 248 (UIKitDialogBridge.swift:369)
7 SwiftUI 0x00000001ad6250cc closure #4 in UIKitDialogBridge.showNewAlert(_:id:) + 72 (UIKitDialogBridge.swift:471)
8 SwiftUI 0x00000001abfdd050 thunk for @escaping @callee_guaranteed () -> () + 36 (:-1)
9 UIKitCore 0x00000001aa5722e4 -[UIPresentationController transitionDidFinish:] + 1096 (UIPresentationController.m:651)
10 UIKitCore 0x00000001aa571d88 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.114 + 320 (UIPresentationController.m:1390)
11 UIKitCore 0x00000001aa5cb9ac -[_UIViewControllerTransitionContext completeTransition:] + 116 (UIViewControllerTransitioning.m:304)
12 UIKitCore 0x00000001aa34a91c __UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__ + 36 (UIView.m:16396)
13 UIKitCore 0x00000001aa34a800 -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 624 (UIView.m:16429)
14 UIKitCore 0x00000001aa349518 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 436 (UIView.m:0)
15 UIKitCore 0x00000001aa356b14 -[UIViewAnimationState animationDidStop:finished:] + 192 (UIView.m:2400)
16 UIKitCore 0x00000001aa356b84 -[UIViewAnimationState animationDidStop:finished:] + 304 (UIView.m:2422)
17 QuartzCore 0x00000001a96f8c50 run_animation_callbacks(void*) + 132 (CALayer.mm:7714)
18 libdispatch.dylib 0x00000001aff61dd4 _dispatch_client_callout + 20 (object.m:576)
19 libdispatch.dylib 0x00000001aff705a4 _dispatch_main_queue_drain + 988 (queue.c:7898)
20 libdispatch.dylib 0x00000001aff701b8 _dispatch_main_queue_callback_4CF + 44 (queue.c:8058)
21 CoreFoundation 0x00000001a808f710 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1780)
22 CoreFoundation 0x00000001a808c914 __CFRunLoopRun + 1996 (CFRunLoop.c:3149)
23 CoreFoundation 0x00000001a808bcd8 CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420)
24 GraphicsServices 0x00000001ecf3c1a8 GSEventRunModal + 164 (GSEvent.c:2196)
25 UIKitCore 0x00000001aa6c490c -[UIApplication _run] + 888 (UIApplication.m:3713)
26 UIKitCore 0x00000001aa7789d0 UIApplicationMain + 340 (UIApplication.m:5303)
27 SwiftUI 0x00000001ac27c148 closure #1 in KitRendererCommon(_:) + 168 (UIKitApp.swift:51)
28 SwiftUI 0x00000001ac228714 runApp<A>(_:) + 152 (UIKitApp.swift:14)
29 SwiftUI 0x00000001ac2344d0 static App.main() + 132 (App.swift:114)
30 My App 0x00000001001e7bfc static MyApp.$main() + 52 (MyApp.swift:0)
31 My App 0x00000001001e7bfc main + 64
32 dyld 0x00000001cb73de4c start + 2240 (dyldMain.cpp:1298)
Hi,
When I use 'listSectionSeparator' on hide the section separator on a List 'section', it works as expected on iOS, but doesn't have any effect on macOS. Is that a known issue? Are there any workarounds for this?
Here's a basic reproducible example:
import SwiftUI
struct TestItem: Identifiable, Hashable {
let id = UUID()
let itemValue: Int
var itemString: String {
get {
return "test \(itemValue)"
}
}
}
struct TestListSelection: View {
let testArray = [TestItem(itemValue: 1), TestItem(itemValue: 2), TestItem(itemValue: 3), TestItem(itemValue: 4)]
@State private var selectedItem: TestItem? = nil
var body: some View {
List (selection: $selectedItem) {
Section("Header") {
ForEach (testArray, id: \.self) { item in
Button {
print("main row tapped for \(item.itemValue)")
} label: {
HStack {
Text(item.itemString)
Spacer()
Button("Tap me") {
print("button tapped")
}
.buttonStyle(.borderless)
}
}
.buttonStyle(.plain)
}
}
.listSectionSeparator(.hidden) // has no effect on macOS
Section("2nd Header") {
ForEach (testArray, id: \.self) { item in
Text(item.itemString)
}
}
.listSectionSeparator(.hidden) // has no effect on macOS
}
.listStyle(.plain)
}
}
#Preview {
TestListSelection()
}
How can I use availability checks with the new defaultWindowPlacement API so I can ensure visionOS 2 users get the correct window placement and visionOS 1 users fall back to the old window placement mechanism?
I have tried using if #available and @available in different ways, but end up with either an error that says control flow statements aren't allowed in SceneBuilders or that the return type of the two branches of my control flow statement do not match if I omit the SceneBuilder property.
An example of how to use .defaultWindowPlacement with visionOS 1 support would be nice. Thank you!
Hello, I'm new to app development and have a question that I have been searching all over the place to find an answer to. I have followed Apple's own guides and don't know how to move around the strange issue I am experiencing.
I have a project using NavigationSplitView and have the code setup so that it navigates through an array of items. The two issues I am experiencing that I hope to find some help with are:
I would like to be able to have my add button, instead of presenting a sheet, just navigate to a new item within the NavigationSplitView. This would be more natural for the type of data application I have created. At this time I create the item with a .sheet modifier and then the item is created and added to the array. However, I then have to tap on the newly created item to get it to be selected. Is this how SwiftUI should be working?
Whenever an item is deleted from the list, and it was the last item selected or the last item in the array, the NavigationSplitView will continue showing that remaining item. The only way to get it to show an empty content screen is to force close the application and then reopen it.
Since this is my first time posting, I'm not sure what information would be helpful, but would be happy to provide anything which could be of assistance. :-)
For whatever reason SwiftUI sheets don't seem to be resizable anymore.
The exact same code/project produces resizable Sheets in XCode 15.4 but unresizable ones with Swift included in Xcode 16 beta 2.
Tried explicitly providing .fixedSize(horizontal false, vertical: false) everywhere humanly possible hoping for a fix but sheets are still stuck at an awkward size (turns out be the minWidth/minHeight if I provide in .frame).
Hello,
I have a SwiftUI view with the following state variable:
@State private var startDate: Date = Date()
@State private var endDate: Date = Date()
@State private var client: Client? = nil
@State private var project: Project? = nil
@State private var service: Service? = nil
@State private var billable: Bool = false
Client, Project, and Service are all SwiftData models. I have some view content that binds to these values, including Pickers for the client/project/service and a DatePicker for the Dates.
I have an onAppear listener:
.onAppear {
switch state.mode {
case .editing(let tt):
Task {
await MainActor.run {
startDate = tt.startDate
endDate = tt.endDate
client = tt.client
project = tt.project
service = tt.service
billable = tt.billable
}
}
default:
return
}
}
This works as expected. However, if I remove the Task & MainActor.run, the values do not fully update. The DatePickers show the current date, the Pickers show a new value but tapping on them shows a nil default value.
What is also extremely strange is that if tt.billable is true, then the view does update as expected.
I am using Xcode 15.4 on iOS simulator 17.5. Any help would be appreciated.
Hi everyone,
I'm currently working on an iOS app using SwiftUI, and I'm facing an issue with a vertical ScrollView. My goal is to have the ScrollView take up all the safe area space plus the top inset (with the bottom inset being an ultra-thin material) and enable paging behavior. However, I'm encountering two problems:
The initial height of the ScrollView is too high (dragging the view (even without changing the page) adjusts the size).
The paging offset of the ScrollView is incorrect (page views are not aligned).
I’ve tried many things and combinations in desperation, including padding, safeAreaPadding, contentMargins, frame, fixedSize, containerRelativeFrame with callback, custom layout, and others, but nothing seems to work.
If by any chance someone can help me find a solution, I’d greatly appreciate it.
I suspect there are issues with the height defined by the ScrollView when some safe areas are ignored, leading to strange behavior when trying to set the height. It's challenging to explain everything in a simple post, so if someone from the SwiftUI team could look into this potential bug, that would be incredibly helpful.
Thank you!
import SwiftUI
struct ScrollViewIssue: View {
@State private var items: [(String, Color)] = [
("One", .blue),
("Two", .green),
("Three", .red),
("Four", .purple)
]
var body: some View {
ZStack(alignment: .top) {
ScrollView(.vertical) {
VStack(spacing: 0) {
ForEach(items, id: \.0) { item in
ItemView(text: item.0, color: item.1)
.containerRelativeFrame([.horizontal, .vertical])
}
}
}
.printViewSize(id: "ScrollView")
.scrollTargetBehavior(.paging)
.ignoresSafeArea(edges: .top)
VStack {
Text("Title")
.foregroundStyle(Color.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.black.opacity(0.2))
Spacer()
}
}
.printViewSize(id: "ZStack")
.safeAreaInset(edge: .bottom) {
Rectangle()
.frame(width: .infinity, height: 0)
.overlay(.ultraThinMaterial)
}
}
}
struct ItemView: View {
let text: String
let color: Color
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 20)
.fill(color.opacity(0.2))
.overlay(
color,
in: RoundedRectangle(cornerRadius: 20)
.inset(by: 10 / 2)
.stroke(lineWidth: 10)
)
Text(text)
}
.printViewSize(id: "item")
}
}
struct ViewSizeKey: PreferenceKey {
static var defaultValue = CGSize()
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
extension View {
func printViewSize(id: String) -> some View {
background(
GeometryReader { proxy in
Color.clear
.preference(key: ViewSizeKey.self, value: proxy.size)
}
.onPreferenceChange(ViewSizeKey.self) { value in
print("\(id) size:\(value)")
}
)
}
}
#Preview {
ScrollViewIssue()
}
Hi, I can't get onScrollPhaseChange to fire when using a List. It works as expected when using a ScollView and LazyVStack.
Interestingly, onScrollGeometryChange gets called as expected for both List and ScrollView.
Has anyone successfully used onScrollPhaseChange with a List?
Hi,
I was testing my project running on macOS 15 Beta and Xcode 16 Beta and noticed that lists look slightly different. They generally look somewhat smaller, like the font size or spacing is now different.
It's kinda hard to describe so I made screenshots depicting what the list looks like on macOS 14 + Xcode 15 and macOS 15 Beta + Xcode 16 Beta to show what I mean.
Did you experience similar issues?
Topic:
UI Frameworks
SubTopic:
SwiftUI
I am in the process of adding my company's brand font to our SwiftUI app. I am able to implement the font using the provided public APIs so that text styles / dynamic type and the font weight modifier in SwiftUI work correctly.
However we are unable to implement custom font in such a way that text styles / dynamic type, the font weight modifier, and the bold text accessibility setting all work at the same time. Am I missing an implementation detail so that all these features work correctly?
Font Setup
The font files were modified to better support SwiftUI:
The font style name metadata was modified to match the name the .fontWeight(...) modifier expects. This was done with Typelight.
The font weight value (100/200/300) was modified so that the underlying weight value matches the value the .fontWeight(...) modifier expects. See "Using custom fonts with SwiftUI" by Matthew Flint.
The font files were imported via the Info.plist.
Examples
Font Weight Comparison
San Fransisco:
Text("#100")
.font(.largeTitle)
.fontWeight(.ultraLight)
Overpass by Name:
Text("#100")
.font(.custom("Overpass-UltraLight", size: 34, relativeTo: .largeTitle))
Overpass by Weight:
Text("#100")
.fontWeight(.ultraLight)
.font(.custom("Overpass", size: 34, relativeTo: .largeTitle))
Legibility Weight Test
When using the .fontWeight(...) modifier, the custom font does not change weights when the bold text accessibility setting is enabled. Dynamic type size works as expected.
Normal legibility weight:
Bold legibility weight:
Dynamic Type Size:
Use UIFont
Using UIFont to load the custom font files and initializing a Font with the UIFont breaks dynamic type:
Bold type also does not work:
Custom Modifier
Creating a custom modifier allows us to support dynamic type and manually handle bold text. However it creates a conflicting API to SwiftUI's .fontWeight(...) modifier.
struct FontModifier: ViewModifier {
enum UseCase {
case paragraph
case headline
}
enum Weight {
case light
case regular
case heavy
}
@Environment(\.legibilityWeight) var legibilityWeight
var useCase: UseCase
var weight: Weight
init(_ useCase: UseCase, _ weight: Weight) {
self.useCase = useCase
self.weight = weight
}
var resolvedHeadlineWeight: String {
let resolvedLegibilityWeight = legibilityWeight ?? .regular
switch weight {
case .light:
switch resolvedLegibilityWeight {
case .regular:
return "Light"
case .bold:
return "Semibold"
@unknown default:
return "Light"
}
case .regular:
switch resolvedLegibilityWeight {
case .regular:
return "Regular"
case .bold:
return "Bold"
@unknown default:
return "Regular"
}
case .heavy:
switch resolvedLegibilityWeight {
case .regular:
return "Heavy"
case .bold:
return "Black"
@unknown default:
return "Heavy"
}
}
}
var resolvedParagraphWeight: Font.Weight {
switch weight {
case .light:
return .light
case .regular:
return .regular
case .heavy:
return .heavy
}
}
var resolvedFont: Font {
switch useCase {
case .paragraph:
return .system(.largeTitle).weight(resolvedParagraphWeight)
case .headline:
return .custom("Overpass-\(resolvedHeadlineWeight)", size: 34, relativeTo: .largeTitle)
}
}
func body(content: Content) -> some View {
content
.font(resolvedFont)
}
}
GridRow {
Text("Aa")
.modifier(FontModifier(.paragraph, .regular))
Text("Aa")
.modifier(FontModifier(.paragraph, .heavy))
Text("Aa")
.modifier(FontModifier(.headline, .regular))
Text("Aa")
.modifier(FontModifier(.headline, .heavy))
}
Font Environment Value
The font environment value does not contain font weight information when the fontWeight(...) modifier is used.:
struct DumpFontTest: View {
@Environment(\.font) var font
var body: some View {
Text("San Fransisco")
.onAppear {
print("------------")
dump(font)
}
}
}
DESCRIPTION OF PROBLEM
I have changed my app to the @Observable-Macro.
When using an iPhone (on simulator and on real device) the navigation from a player to the player detail view and back breaks. In the attached video on my GitHub you can see me tapping on both players in the team, but the navigation ist not showing the detail view.
What is the reason? Is my usage/understanding of @Observable wrong? Is it wrong to have the selectedPlayer within the PlayerController which is @Observable? And why does it sometimes work and sometimes not?
The project can be found here: GitHub Project
STEPS TO REPRODUCE
Start the App, add one or two demo teams, tap on a team and add two or more demo players.
tap a player and then go back, tap the player again and back again. After a while (number of taps is always different), the navigation breaks. See my video attached.
PLATFORM AND VERSION
iOS
Development environment: Xcode 15.4, macOS 14.5 (23F79)
Run-time configuration: iOS 17.5,
I have arrived at a certain architectural solution for my SwiftUI code which is helped by, in certain situations, modifying the state while the body is being evaluated.
Of course, I am always open to realizing that a given solution may be creating difficulties precisely because it is fundamentally ill-advised. However, in this post I won't attempt to explain the details of my architecture or justify my reasoning regarding wanting to change the state in the middle of a view update. I just want to ask, why exactly is it prohibited? Is it not rather like normal recursion, which can of course produce infinite loops if done wrong but which is perfectly logically sound as long as the recursing function eventually stabilizes?
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hello.
I've seen some other posts about NavigationStack, but my variation seems a little different.
FYI, I have not migrated from ObservableObject to Observable yet. However, having a path in either seems to be a factor in this issue.
My code has no issues when built with Xcode 15.
When built with Xcode 16 I keep hitting scenarios where the .onAppear for my first tab gets called over and over again endlessly.
If I go to my second tab, which uses a NavStack with a path and then navigate anywhere my .onAppear for my FIRST tab gets call endlessly. I’ll sometimes see a “double push” to the stack. (Someone posted a video of this happening on Mastodon, which apparently I’m not allowed to link to here.) The second tab is accessing the path property via an @EnvironmentObject.
I can stop this endless loop by removing @Published from the property in my ObservableObject that holds my path.
But then if I go to my third tab, which does NOT use a path, the .onAppear for my FIRST tab again gets called endlessly.
So far on Mastodon I’ve seen three people encountering problems possibly related to storing a path in something being observed.
Feedback requires a sample project, which I am having trouble creating to show the problem.
Hi, I'm working on visionOS and find I can't get onDisappear event just on the first window after app launch. It comes like that:
WindowGroup(id:"WindowA"){
MyView()
.onDisappear(){
print("WindowA disappear")
}
}
WindowGroup(id:"WindowB"){
MyView()
.onDisappear(){
print("WindowB disappear")
}
}
WindowGroup(id:"WindowC"){
MyView()
.onDisappear(){
print("WindowC disappear")
}
}
When the app first launch, it will open WindowA automatically
And then I open WindowB and WindowC programatically.
Then I tap the close button on window bar below window.
If I close WindowB/WindowC, I can receive onDisappear event
If I close WindowA, I can't receive onDisappear event
If I reopen WindowA after it is closed and then close it again by tap the close button below window, I can receive onDisappear event
Is there any logic difference for the first window on app launch? How can I get onDisappear Event for it.
I'm using Xcode 16 beta 2
Hello,
With iOS 18, when NavigationStack is in new TabView, with path parameter containing current navigation state is set, the navigation destination view is pushed twice.
See below with example that pushes twice on iOS 18 but is correct on iOS 17
@MainActor
class NavigationModel: ObservableObject {
static let shared = NavigationModel()
@Published var selectedTab: String
@Published var homePath: [Route]
@Published var testPath: [Route]
}
struct ContentView: View {
@StateObject private var navigationModel: NavigationModel = NavigationModel.shared
var body: some View {
TabView(selection: $navigationModel.selectedTab){
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag("home")
TestView()
.tabItem {
Label("Test", systemImage: "circle")
}
.tag("test")
}
}
}
struct HomeView: View {
@StateObject private var navigationModel: NavigationModel = NavigationModel.shared
var body: some View {
NavigationStack(path: $navigationModel.homePath){
VStack{
Text("home")
NavigationLink(value: Route.test1("test1")){
Text("Go to test1")
}
}
.navigationDestination(for: Route.self){ route in
NavigationModelBuilder.findFinalDestination(route:route)
}
}
}
}
I don't what causes the issue because it works well on iOS 16 and iOS 17. I think the path is somehow reset but I don't why (maybe by the TabView ?)
Note that the bug only occurs with TabView.
Don't really know if it is a TabView bug or if it is on my side.
I filed a feedback with sample project FB14312064
I'm trying to add a ControlWidget to my WidgetBundle like this:
struct MyWidgets: WidgetBundle {
var body: some Widget {
if #available(iOSApplicationExtension 18.0, *) {
LauncherControl()
}
MyLiveActivityWidget()
HomeScreenWidget()
LockScreenWidget()
}
This works exactly as expected on iOS 18. However on iOS 17 my app seems to have no widgets at all.
The workaround described here (https://www.avanderlee.com/swiftui/variable-widgetbundle-configuration/) does not work either since WidgetBundleBuilder.buildBlock does not accept ControlWidget as an argument.
What is the correct way to include a Control widget conditionally on iOS 18?