When using the writingToolsBehavior API on a TextField and the app compiled with the iOS 26 SDK is run on an iOS 18 device, the app crashes with a symbol not found error.
It only crashes on the release build configuration and not on debug.
dyld[5274]: Symbol not found: _$s7SwiftUI17EnvironmentValuesV21_writingToolsBehaviorAA07WritingfG0VSgvg Referenced from: <1306655E-6DF7-3B2A-94A3-7202149E82F3> /private/var/containers/Bundle/Application/88E47904-4884-4279-9E96-0EC366970389/WritingToolsTest.app/WritingToolsTest Expected in: <165D3305-401E-37C2-8387-C1BFB54CFFDE> /System/Library/Frameworks/SwiftUI.framework/SwiftUI
Feedback ID: FB17980516
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button ("Button 1") {
print ("Button 1");
}
.keyboardShortcut("k", modifiers: .command)
Button ("Button 2") {
print ("Button 2");
}
.keyboardShortcut("k", modifiers: .command)
}
}
}
I the above snippet, I have assigned the same keyboard shortcut (cmd +k) to 2 different buttons. According to the docs, if multiple controls are associated with the same shortcut, the first one found is used.
How do I figure out if Button 1 would be found first during the traversal or Button 2 ?
Is it based on the order of declaration? Is it always the case that Button 1 would be found first since it was declared before Button 2 ?
Hi,
I'm facing an issue with EKEventStore and recurring events using EKRecurrenceRule. When I create a repeat event with an until date using EKRecurrenceEnd(end:), the until date is not retained correctly if the event's start date is in the present or future.
Scenario:
If I create a recurring event where the start date is in the past, the until date appears correctly when I fetch the event later.
But when the event starts in the present or future, the until date is missing (i.e. recurrenceEnd?.endDate becomes nil).
Environment:
macOS Version: 15.4.1
Tested on Mac app using EKEventStore
Is this a known EventKit behavior or a bug? Would appreciate any insights or workaround recommendations.
Thanks!
Topic:
UI Frameworks
SubTopic:
AppKit
I noticed that sometimes TextKit2 decides to crop some text instead of soft-wrapping it to the next line.
This can be reproduced by running the code below, then resizing the window by dragging the right margin to the right until you see the text with green background (starting with “file0”) at the end of the first line.
If you now slowly move the window margin back to the left, you’ll see that for some time that green “file0” text is cropped and so is the end of the text with red background, until at some point it is soft-wrapped on the second line.
I just created FB18289242. Is there a workaround?
class ViewController: NSViewController {
override func loadView() {
let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
let string = NSMutableAttributedString(string: "file0\t143548282\t1970-01-01T00:00:00Z\t1\t1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75", attributes: [.foregroundColor: NSColor.labelColor, .backgroundColor: NSColor.red.withAlphaComponent(0.2)])
string.append(NSAttributedString(string: "file0\t143548290\t1970-01-01T00:05:00Z\t 2\t0f6460d0ed7825fed6bda0f4d9c14942d88edc7ff236479212e69f081815e6f1742c272753b77cc6437f06ef93a46271c6ff9513c68945075212434080e60c82", attributes: [.foregroundColor: NSColor.labelColor, .backgroundColor: NSColor.green.withAlphaComponent(0.2)]))
textView.textContentStorage!.textStorage!.setAttributedString(string)
textView.autoresizingMask = [.width, .height]
let scrollView = NSScrollView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
scrollView.documentView = textView
scrollView.hasVerticalScroller = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
view = scrollView
}
}
When using UITabBarController and set a custom tabbar:
TabBarViewController.swift
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class HomeViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
navigationItem.title = "Home"
tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), tag: 0)
}
}
class PhoneViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .purple
navigationItem.title = "Phone"
tabBarItem = UITabBarItem(title: "Phone", image: UIImage(systemName: "phone"), tag: 1)
}
}
class PhotoViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
navigationItem.title = "Photo"
tabBarItem = UITabBarItem(title: "Photo", image: UIImage(systemName: "photo"), tag: 1)
}
}
class SettingViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
navigationItem.title = "Setting"
tabBarItem = UITabBarItem(title: "Setting", image: UIImage(systemName: "gear"), tag: 1)
}
}
class TabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let homeVC = HomeViewController()
let homeNav = NavigationController(rootViewController: homeVC)
let phoneVC = PhoneViewController()
let phoneNav = NavigationController(rootViewController: phoneVC)
let photoVC = PhotoViewController()
let photoNav = NavigationController(rootViewController: photoVC)
let settingVC = SettingViewController()
let settingNav = NavigationController(rootViewController: settingVC)
viewControllers = [homeNav]
let dataSource = [
CustomTabBar.TabBarModel(title: "Home", icon: UIImage(systemName: "house")),
CustomTabBar.TabBarModel(title: "Phone", icon: UIImage(systemName: "phone")),
CustomTabBar.TabBarModel(title: "Photo", icon: UIImage(systemName: "photo")),
CustomTabBar.TabBarModel(title: "Setting", icon: UIImage(systemName: "gear"))
]
let customTabBar = CustomTabBar(with: dataSource)
setValue(customTabBar, forKey: "tabBar")
}
}
CustomTabBar.swift:
import UIKit
class CustomTabBar: UITabBar {
class TabBarModel {
let title: String
let icon: UIImage?
init(title: String, icon: UIImage?) {
self.title = title
self.icon = icon
}
}
class TabBarItemView: UIView {
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = .black
titleLabel.textAlignment = .center
return titleLabel
}()
lazy var iconView: UIImageView = {
let iconView = UIImageView()
iconView.translatesAutoresizingMaskIntoConstraints = false
iconView.contentMode = .center
return iconView
}()
private var model: TabBarModel
init(model: TabBarModel) {
self.model = model
super.init(frame: .zero)
setupSubViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubViews() {
addSubview(iconView)
iconView.topAnchor.constraint(equalTo: topAnchor).isActive = true
iconView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
iconView.widthAnchor.constraint(equalToConstant: 34).isActive = true
iconView.heightAnchor.constraint(equalToConstant: 34).isActive = true
iconView.image = model.icon
addSubview(titleLabel)
titleLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
titleLabel.heightAnchor.constraint(equalToConstant: 16).isActive = true
titleLabel.text = model.title
}
}
private var dataSource: [TabBarModel]
init(with dataSource: [TabBarModel]) {
self.dataSource = dataSource
super.init(frame: .zero)
setupTabBars()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
let safeAreaBottomHeight: CGFloat = safeAreaInsets.bottom
sizeThatFits.height = 52 + safeAreaBottomHeight
return sizeThatFits
}
private func setupTabBars() {
backgroundColor = .orange
let multiplier = 1.0 / Double(dataSource.count)
var lastItemView: TabBarItemView?
for model in dataSource {
let tabBarItemView = TabBarItemView(model: model)
addSubview(tabBarItemView)
tabBarItemView.translatesAutoresizingMaskIntoConstraints = false
tabBarItemView.topAnchor.constraint(equalTo: topAnchor).isActive = true
tabBarItemView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
if let lastItemView = lastItemView {
tabBarItemView.leadingAnchor.constraint(equalTo: lastItemView.trailingAnchor).isActive = true
} else {
tabBarItemView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
tabBarItemView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier).isActive = true
lastItemView = tabBarItemView
}
}
}
UIKit show both custom tabbar and system tabbar:
the Xcode version is: Version 26.0 beta 2 (17A5241o)
and the iOS version is: iOS 26 (23A5276f)
I’m trying to add a TextField to the toolbar using .principal placement, and I want it to either fill the screen width or expand based on the surrounding content. However, it’s not resizing as expected — the TextField only resizes correctly when I provide a hardcoded width value. This behavior was working fine in previous versions of Xcode, but seems to be broken in Xcode 26. Not sure if this is an intentional change or a bug. i am using iOS26 beta and Xcode 26 beta
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
TextField("Search", text: .constant(""))
.textFieldStyle(.roundedBorder)
.frame(maxWidth: .infinity)
// .frame(width: 300)
Button("cancel") {
}
}
.frame(maxWidth: .infinity)
}
}
}
}
#Preview {
NavigationView {
ContentView()
}
}
@Observable seems not to work well with generic typed throw.
The following code using @Observable with non-generic typed throw builds good:
@Observable
class ThrowsLoadingViewModel<R, E: Error> {
private(set) var isLoading = true
private(set) var error: E? = nil
private(set) var data: R? = nil
private var task: () throws(Error) -> R
init(task: @escaping () throws(E) -> R) {
self.task = task
}
func load() {
do throws(Error) {
self.data = try task()
} catch {
// self.error = error
}
self.isLoading = false
}
}
But if I change Line 7 and 14 to generic, it'll breaks the build with a "Command SwiftCompile failed with a nonzero exit code" message :
@Observable
class ThrowsLoadingViewModel<R, E: Error> {
private(set) var isLoading = true
private(set) var error: E? = nil
private(set) var data: R? = nil
private var task: () throws(E) -> R
init(task: @escaping () throws(E) -> R) {
self.task = task
}
func load() {
do throws(E) {
self.data = try task()
} catch {
// self.error = error
}
self.isLoading = false
}
}
A the same time, if I remove @Observable, the generic typed throw works again:
class ThrowsLoadingViewModel<R, E: Error> {
private(set) var isLoading = true
private(set) var error: E? = nil
private(set) var data: R? = nil
private var task: () throws(E) -> R
init(task: @escaping () throws(E) -> R) {
self.task = task
}
func load() {
do throws(E) {
self.data = try task()
} catch {
// self.error = error
}
self.isLoading = false
}
}
Currently the possible solution seems to fall back to use ObservableObject...
When I run my iOS app on a Mac using Mac Catalyst, several sharing options that show up on an iOS device in a share sheet are absent on the Mac. Clicking on Edit Extensions, I see Mail, Message and AirDrop, their switches are on and disabled. All three items show up when I share from Safari or Notes.
How can I make Mail, Message and AirDrop available?
For example, when sharing data, no share extensions are shown.
For text, only Simulator, Shortcuts and Copy are shown.
I'm using SwiftUI's TextEditor. I'd like to include an undo button in my UI that operates on the TextEditor. I don't see a way to hook this up. You can get the UndoManager from the environment, but this is not the undo manager the TextEditor is using. I know that UITextView uses an undocumented UndoManager (_UITextUndoManager) and I've accessed that before when using a UIViewRepresentable wrapper around UITextView. I'd like to achieve the same with TextEditor.
Topic:
UI Frameworks
SubTopic:
SwiftUI
I would like to have different fill colors in my chart. What I want to achieve is that if the values drop below 0 the fill color should be red. If they are above the fill color should be red. My code looks as follows:
import SwiftUI
import Charts
struct DataPoint: Identifiable {
let id: UUID = UUID()
let x: Int
let y: Int
}
struct AlternatingChartView: View {
enum Gradients {
static let greenGradient = LinearGradient(gradient: Gradient(colors: [.green, .white]), startPoint: .top, endPoint: .bottom)
static let blueGradient = LinearGradient(gradient: Gradient(colors: [.white, .blue]), startPoint: .top, endPoint: .bottom)
}
let data: [DataPoint] = [
DataPoint(x: 1, y: 10),
DataPoint(x: 2, y: -5),
DataPoint(x: 3, y: 20),
DataPoint(x: 4, y: -8),
DataPoint(x: 5, y: 15),
]
var body: some View {
Chart {
ForEach(data) { data in
AreaMark(
x: .value("Data Point", data.x),
y: .value("amount", data.y))
.interpolationMethod(.catmullRom)
.foregroundStyle(data.y < 0 ? Color.red : Color.green)
LineMark(
x: .value("Data Point", data.x),
y: .value("amount", data.y))
.interpolationMethod(.catmullRom)
.foregroundStyle(Color.black)
.lineStyle(StrokeStyle.init(lineWidth: 4))
}
}
.frame(height: 200)
}
}
#Preview {
AlternatingChartView()
}
The result looks like this:
I also tried using
foregroundStyle(by:)
and
chartForegroundStyleScale(_:)
but the result was, that two separate areas had been drawn. One for the below and one for the above zero datapoints.
So, what would be the right approach to have two different fill colors?
I have added multiple status icons to my project, in the form of .icon files created with Icon Composer. The main app icon works, but the status icons are not working.
I am attempting to load the images from the asset catalog using NSImage imageNamed:, and apply them to the NSApp dockTile using NSGlassEffectContainerView. I don't even know if that attempt is going to work, as I never get past the stage of NSImage loading the icons.
Maybe someone on the forums knows what to do there? I'd be willing to use one of my coding support incidents to work through this if necessary, as my two incidents will expire as my subscription rolls over in August anyway.
My project lives at https://github.com/losnoco/cog/, and the Tahoe attempt WIP lives in the wip.tahoe branch, with the latest commit as of this post being the attempt to adapt the Dock Icon generation.
I'd love to know if I can adapt this easily. I'm also still trying to support existing non-Glass custom .png icons the user can add to their profile folder with buttons in the preferences, as well as supporting legacy status icons on pre-Tahoe installs. I also try to add a progress bar to the dock tile view when the app is processing something at length.
how can i watch the LiveCommunicationKit event?
i have codes likes this:
import UIKit
import LiveCommunicationKit
@available(iOS 17.4, *)
class LiveCallKit: NSObject, ConversationManagerDelegate {
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, conversationChanged conversation: Conversation) {
}
@available(iOS 17.4, *)
func conversationManagerDidBegin(_ manager: ConversationManager) {
}
@available(iOS 17.4, *)
func conversationManagerDidReset(_ manager: ConversationManager) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) {
switch action.state
{
case .idle:
self.completionHandler!(InterfaceKind.reject,self.payload!)
case .running:
self.completionHandler!(InterfaceKind.reject,self.payload!)
case .complete:
self.completionHandler!(InterfaceKind.reject,self.payload!)
case .failed(let reason):
self.completionHandler!(InterfaceKind.reject,self.payload!)
default:
self.completionHandler!(InterfaceKind.reject,self.payload!)
}
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, timedOutPerforming action: ConversationAction) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, didActivate audioSession: AVAudioSession) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, didDeactivate audioSession: AVAudioSession) {
}
@objc public enum InterfaceKind : Int, Sendable, Codable, Hashable {
/// 拒绝/挂断
case reject
/// 接听.
case answer
}
var sessoin: ConversationManager
var callId: UUID
var completionHandler: ((_ actionType: InterfaceKind,_ payload: [AnyHashable : Any]) -> Void)?
var payload: [AnyHashable : Any]?
@objc init(icon: UIImage!) {
let data:Data = icon.pngData()!;
let cfg: ConversationManager.Configuration = ConversationManager.Configuration(ringtoneName: "ring.mp3",
iconTemplateImageData: data,
maximumConversationGroups: 1,
maximumConversationsPerConversationGroup: 1,
includesConversationInRecents: false,
supportsVideo: false,
supportedHandleTypes: Set([Handle.Kind.generic]))
self.sessoin = ConversationManager(configuration: cfg)
self.callId = UUID()
super.init()
self.sessoin.delegate = self
}
@objc func toIncoming(_ payload: [AnyHashable : Any], displayName: String,actBlock: @escaping(_ actionType: InterfaceKind,_ payload: [AnyHashable : Any])->Void) async {
self.completionHandler = actBlock
do {
self.payload = payload
self.callId = UUID()
var update = Conversation.Update(members: [Handle(type: .generic, value: displayName, displayName: displayName)])
let actNumber = Handle(type: .generic, value: displayName, displayName: displayName)
update.activeRemoteMembers = Set([actNumber])
update.localMember = Handle(type: .generic, value: displayName, displayName: displayName);
update.capabilities = [ .playingTones ];
try await self.sessoin.reportNewIncomingConversation(uuid: self.callId, update: update)
try await Task.sleep(nanoseconds: 2000000000);
} catch {
}
}
}
i want to watch the buttons action,how should i do?
Topic:
UI Frameworks
SubTopic:
SwiftUI
Looking at the superclass of CPImmersiveScene is surprisingly is a UIWindowScene.
Is this intentional? Should I treat it as a window scene, and provide an UIWindow for it?
Or is it only an implementation detail for managing the internal CPSceneLayerEventWindow and UITextEffectsWindow?
Hi, I'm working on RealityView and I have two entities in RCP. In order to set views for both entities, I have to create two separate attachments for each entity. What I want to achieve is that when I hover (by eye) on one entity's attachment, it would trigger the hover effect of the other entity's attachment. I try to use the hoverEffectGroup, but it would only activate the hover effect in a subview, instead a complete separate view. I refer to the following WWDC instruction for the hover effect.
https://developer.apple.com/videos/play/wwdc2024/10152/
There has been a long lasting UIStackView bug dating back to 2016 that still exists in the latest Xcode 16.3 and SDKs, where calling setHidden:true multiple times (lets say twice) on a subview of that stack view requires calling setHidden:false twice before the subview shows up again.
This was originally documented via Radar #25087688.
Hopefully a Frameworks Engineer here on the forums can raise it to the attention of the appropriate engineers. It would be really nice if this eventually gets fixed, because it's one of those odd issues where you end up wasting a lot of time trying to debug because everything looks correct.
App update in which there were no changes regarding the widget. Just after it updated, the widget turns black in some cases. It also appears black in the widget gallery. Removing and adding it again did not work in this case, only after an iOS restart it works fine again
This is the log
2025-03-20 02:14:05.961611 +0800 Content load failed: unable to find or unarchive file for key: [com.aa.bb::com.aa.bb.widget:cc_widget:systemMedium::360.00/169.00/23.00:(null)~(null)] on no host. The session may still produce one shortly. Error: Using url file:///private/var/mobile/Containers/Data/PluginKitPlugin/51C5E4F2-6F1F-4466-A428-73C73B9CC887/SystemData/com.apple.chrono/placeholders/cc_widget/systemMedium----360.00w--169.00h--23.00r--1f--0.00t-0.00l-0.00b0.00t.chrono-timeline ... Error Domain=NSCocoaErrorDomain Code=4 "file“systemMedium----360.00w--169.00h--23.00r--1f--0.00t-0.00l-0.00b0.00t.chrono-timeline”not exist。" UserInfo={NSFilePath=/private/var/mobile/Containers/Data/PluginKitPlugin/51C5E4F2-6F1F-4466-A428-73C73B9CC887/SystemData/com.apple.chrono/placeholders/cc_widget/systemMedium----360.00w--169.00h--23.00r--1f--0.00t-0.00l-0.00b0.00t.chrono-timeline, NSUnderlyingError=0xa693d3a80 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
My app inputs electrical waveforms from an IV485B39 2 channel USB device using an AVAudioSession. Before attempting to acquire data I make sure the input device is available as follows:
AVAudiosSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory :AVAudioSessionCategoryRecord error:&err];
NSArray *inputs = [audioSession availableInputs];
I have been using this code for about 10 years.
My app is scriptable so a user can acquire data from the IV485B29 multiple times with various parameter settings (sampling rates and sample duration). Recently the scripts have been failing to complete and what I have notice that when it fails the list of available inputs is missing the USBAudio input. While debugging I have noticed that when working properly the list of inputs includes both the internal microphone as well as the USBAudio device as shown below.
VIB_TimeSeriesViewController:***Available inputs = (
"<AVAudioSessionPortDescription: 0x11584c7d0, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Front>",
"<AVAudioSessionPortDescription: 0x11584cae0, type = USBAudio; name = 485B39 200095708064650803073200616; UID = AppleUSBAudioEngine:Digiducer.com :485B39 200095708064650803073200616:000957 200095708064650803073200616:1; selectedDataSource = (null)>"
)
But when it fails I only see the built in microphone.
VIB_TimeSeriesViewController:***Available inputs = (
"<AVAudioSessionPortDescription: 0x11584cef0, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Front>"
)
If I only see the built in microphone I immediately repeat the three lines of code and most of the "inputs" contains both the internal microphone and the USBAudioDevice
AVAudiosSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory :AVAudioSessionCategoryRecord error:&err];
NSArray *inputs = [audioSession availableInputs];
This fix always works on my M2 iPadPro and my iPhone 14 but some of my customers have older devices and even with 3 tries they still get faults about 1 in 10 tries.
I rolled back my code to a released version from about 12 months ago where I know we never had this problem and compiled it against the current libraries and the problem still exists. I assume this is a problem caused by a change in the AVAudioSession framework libraries. I need to find a way to work around the issue or get the library fixed.
So I am looking to use a custom NSWindow application (so I can implement some enhanced resizing/dragging behavior which is only possible overriding NSWindow).
The problem is my whole application is currently SwiftUI-based (see the project here: https://github.com/msdrigg/Roam/blob/50a2a641aa5f2fccb4382e14dbb410c1679d8b0c/Roam/RoamApp.swift).
I know there is a way to make this work by dropping my @main SwiftUI app and replacing it with a SwiftUI root view hosted in a standard AppKit root app, but that feels like I'm going backwards.
Is there another way to get access (and override) the root NSWindow for a SwiftUI app?
Hey, I've been having a problem with scroll views in combination with the .geometryGroup() modifier.
I have filed a Feedback (FB17698293) but I also wanted to post this here in case someone maybe has a better workaround for the problem.
Problem
Whenever you conditionally insert a ScrollView inside a VStack that is modified with a .geometryGroup() modifier, the scroll view content offset resets itself after the insertion animation is done, even if you started scrolling inside the scroll view during the animation and haven't let go of the screen. This happens consistently and is fully reproducible (see below), both using a simulator and a real device.
Unfortunately, this is a very annoying glitch that ruins a lot of cool UX components that rely on .geometryGroup().
The weird thing is that the glitch entirely disappears, if you add a simple, non-zero (but greater than 1) .padding() modifier to the VStack (.padding().geometryGroup()).
I have no idea why this fixes the glitch, but it does. However, adding a padding is not feasible in many situations, so this workaround is not ideal.
Steps to reproduce
Launch the code below (using a simulator or a real device) and tap "Toggle Expansion" to insert the scroll view.
As the view is animating in, drag the scroll content and hold it scrolled away from the top.
Wait for the animation to complete. The scroll view will reset the content offset, even though the drag gesture is still active (i.e. you haven't lifted your finger to release the scroll view)
On a real device, this sometimes even leads to an even worse visual artifact where the scroll view is rendered twice for a few frames; once with the correct offset, and once with the reset offset.
I wanted to include a link to a gif/video showing the glitch, but it tells me that imgur is not allowed on the forums.
Expected Behavior
I want the scroll view to respect the content offset, even if I started changing it mid-animation.
Xcode Version
I am using Xcode 16.4 (16F6) but this problem has been occurring since the .geometryGroup() modifier has been release. I was only now able to pinpoint this problem exactly, so I'm filing this feedback.
Code
The entire code that reproduces the problem:
import SwiftUI
struct ContentView: View {
@State private var isExpanded: Bool = false
var body: some View {
VStack {
if isExpanded {
ScrollView {
Text(loremIpsum)
}
}
Button("Toggle Expansion") {
isExpanded.toggle()
}
}
// .padding(10) // Adding a non-zero padding makes the glitch disappear
.frame(maxWidth: .infinity)
.geometryGroup()
.animation(.default, value: isExpanded)
}
}
#Preview {
ContentView().preferredColorScheme(.dark)
}
// MARK: - Mock Data
let loremIpsum = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt \
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco \
laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \
mollit anim id est laborum.
"""
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hello,
In a new app I am working on I noticed the FamilyActivityTitleView that displays "ApplicationToken" has wrong (black) color when phone is set to light mode but app is using dark mode via override.
We display user's selected apps and the labels are rendered correctly at first, but then when user updates selection with FamilyActivityPicker, then those newly added apps are rendered with black titles.
The problem goes away when I close the screen and open it again. It also doesn't happen when phone is set to dark theme.
I am currently noticing the issue on iOS 18.4.1.
I have tried various workarounds like forcing white text in the custom label style, forcing re-render with custom .id value but nothing helped.
Is there any way how to fix this?