I have a ScrollView which has inside a LazyVStack and multiple rows. On the appearing of the ScrollView, I scroll to row 250.
When having the VoiceOver running, after opening the app, the focus is on the navigation title as it should be, than, after swiping right to enter the ScrollView, one would expect the focus to be placed on the first visible row, but this is not the case, it scrolls back to some row and focus on that, than reads it.
Here is the code to reproduce it, and if you ask me, this is pretty standard stuff I don't do anything special.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
ScrollViewReader { scrollProxy in
ScrollView {
LazyVStack {
ForEach(1...500, id: \.self) { index in
NavigationLink(destination: DetailView(row: index)) {
Text("Row \(index)")
.padding()
.frame(maxWidth: .infinity)
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
.padding(.horizontal)
}
}
}
}
.onAppear {
// Scroll to row 250 when the view appears
withAnimation {
scrollProxy.scrollTo(250, anchor: .center)
}
}
}
.navigationTitle("Rows")
}
}
}
struct DetailView: View {
let row: Int
var body: some View {
VStack {
Text("Detail for Row \(row)")
.font(.largeTitle)
.padding()
Spacer()
}
.navigationTitle("Row \(row) Details")
}
}
@main
struct ScrollViewExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Post
Replies
Boosts
Views
Activity
Starting from iOS18 UIHostingController behaves weirdly that it resets the state of the view when you scroll up and down of the UITableView, even though associated UITableViewCell is unique and does reuse
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SwiftUICell", for: indexPath)
let swiftUIView = SwiftUIView() // A SwiftUI View
cell.contentConfiguration = UIHostingConfiguration { // <- Here is the bug
swiftUIView
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "NormalCell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
Also in iOS18 release notes thee mentioned some fix related to this but looks like issue is not fixed
Please see the attached video
A UI test case that checks for the existence of a SwiftUI Text element initialized with an empty string previously reliably passed in iOS 17.
In iOS 18 the assertion reliably fails.
I searched the iOS 18 developer release notes for anything possibly related to this but didn't find much. I'd like to point to a conclusive change in iOS handling before changing the test permantently. Can anyone confirm that this is indeed a change in iOS 18?
Any CoreData experts know what could be going on here when trying to display to-many entity type relationship data?
On initialisation my model does an fetch request for an entity called Expense, and some prefetching for related entities (to-many type) like this: request.relationshipKeyPathsForPrefetching = ["splits"]
I was under the impression that calling a property on one of the related entities should fire the fault (if there is one) and give me the value. Here’s where it starts to go wrong. I have a property in my extension that I use in my detailed view:
public var viewExpenseSplits: [ExpenseSplit] {
return splits?.allObjects as? [ExpenseSplit] ?? []
}
and then a list in my detail view:
List(expense.viewExpenseSplits, id: \.self) { split in
Text(split.amount ?? "")
}
Result:
What's even more baffling is that I have no problem viewing these splits in the parent view. I thought perhaps I would have more luck passing the viewExpenseSplits through to the detail view instead of trying to access them from the Expense entity itself. Whilst I can print the data out when the view loads, my list still has the same problem.
I thought this would be trivial. Can anyone point to me where I'm going wrong?
Thanks!
Hello. I am here to report a problem.
I was investigating the strange behaviour of my text animation, and I've found out that unrelated TimelineView is causing the problem.
text is animating on change and there are colors on background (in ZStack) that change using TimelineView.
As a result, when the text changes, it twitches, until it fills the whole screen width. If I pause TimelineView animation, everything works smoothly
The problem occurs only on a real device. I've tested on iOS 17 and 18
Example code
struct ContentView: View {
@State var open = false
let colors: [Color] = [.cyan, .red, .green, .gray, .blue, .orange, .pink, .purple]
@State var i: Int = 0
@State
var text: String = chunks[0]
@State var pause = false
var body: some View {
ZStack {
TimelineView(.animation(minimumInterval: 1.2, paused: pause)) { context in
let color = colors[Int(context.date.timeIntervalSinceReferenceDate) % colors.count]
ZStack {
color
.animation(.easeInOut(duration: 1.2))
Rectangle().fill(Material.regular).edgesIgnoringSafeArea(.all)
}
}
VStack {
Spacer()
Text(text)
.font(.system(size: 32))
.multilineTextAlignment(.center)
.padding()
.animation(.linear(duration: 0.1), value: text)
Spacer()
Button("Add text") {
if i < (chunks.count - 1) {
i += 1
} else {
i = 0
text = ""
}
text.append(" " + chunks[i])
}
Button("Toggle background animation") {
pause.toggle()
}
}
}
}
}
let lyrics = "I'm born to run down rocky cliffs Give me grace, bury my sins Shattered glass and black holes Can't hold me back from where I need to go"
let chunks = lyrics.components(separatedBy: " ")
I can't wrap my head around how to make two buttons the same width, without resorting to ugly hacks like GeometryReader. What is otherwise easy to implement using UIStackView or AutoLayout, becomes a nightmare in SwiftUI.
I tried the following, plus some other variations, but nothing works. What is the "official way" to make two vertically aligned buttons of the same width?
1)
VStack(alignment: .leading) {
Button("Short", action: {})
.frame(maxWidth: .infinity)
Button("Long title", action: {})
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
2)
VStack(alignment: .leading) {
Button(action: {}) {
Text("Short")
.frame(maxWidth: .infinity)
}
Button(action: {}) {
Text("Long title")
.frame(maxWidth: .infinity)
}
}
.frame(maxWidth: .infinity)
3)
VStack(alignment: .leading) {
Button(action: {}) {
Text("Short")
}.frame(maxWidth: .infinity)
Button(action: {}) {
Text("Long title")
}.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
I'm trying to make a Swift Chart where 24 AreaMarks an hour apart on X axis over a day display a vertical gradient.
The gradient is vertical and is essentially [Color.opacity(0.1),Colour,Color.opacity(0.1]
The idea here is where the upper and lower points of each AreaMark are the same or close to each other in the Y axis, the chart essentially displays a line, where they are far apart you get a nice fading vertical gradient.
However, it seems that the .alignsMarkStylesWithPlotArea modifier is always set for AreaMarks even if manually applying it false.
Investigating further, I've learnt that with AreaMarks in a series, Swift Charts seems to only listen to the first foreground style set in. I've created some sample code to demonstrate this.
struct DemoChartView: View {
var body: some View {
Chart {
AreaMark(x: .value("Time", Date().addingTimeInterval(0)), yStart: .value("1", 40), yEnd: .value("2", 60))
.foregroundStyle(LinearGradient(colors: [.pink, .teal], startPoint: .top, endPoint: .bottom))
.alignsMarkStylesWithPlotArea(false)
AreaMark(x: .value("Time", Date().addingTimeInterval(3600)), yStart: .value("1", 44), yEnd: .value("2", 58))
.foregroundStyle(LinearGradient(colors: [.orange, .yellow], startPoint: .top, endPoint: .bottom))
.alignsMarkStylesWithPlotArea(false)
AreaMark(x: .value("Time", Date().addingTimeInterval(03600*2)), yStart: .value("1", 50), yEnd: .value("2", 90))
.foregroundStyle(LinearGradient(colors: [.green, .blue], startPoint: .top, endPoint: .bottom))
.alignsMarkStylesWithPlotArea(false)
}
}
}
Which produces this:
So here, all the different .foregroundStyle LinearGradients are being ignored AND the .alignsMarkStylesWithPlotArea(false) is also ignored - the amount of pink on the first mark is different to the second and third 🤷♂️
Has anyone encountered this. Are AreaMarks the correct choice or are they just not setup to create this type of data display. Thanks
Hello,
I have a live activity widget that displays the passing time during a meeting. It uses a TimelineView to update a custom circular progress bar (that overlaps) and a passing timer.
It used to work before Xcode 16, now for some reason the completion handler it's called exactly 2 times and no more, no matter how I set the schedule interval (used to be .animation, but I've tried to update every seconds too).
In comparison a Text(timeInterval: ...) view is updated correctly.
I've also tried the same code under Xcode 16.1 RC and iOS 18.1 but the results are the same. This behavior happens on the preview, the simulator and actual device. Is there something changed that prevents the use of TimelineViews in widgets on iOS 18?
I've encountered a major issue with the iOS 18.1 RC and watchOS 11.1 RC. It appears that complications running on WidgetKit cannot be synced as .watchface to these new release candidates. The error message indicates that "the Watch Faces app and complication are not available," which is affecting all apps utilizing WidgetKit.
This issue renders all WidgetKit-based complications unusable on watchOS 11.1 RC. It’s a serious problem for those of us who rely on these complications for our apps and for users expecting consistent functionality.
APPLE, PLEASE FIX THIS ISSUE ASAP!
This bug is a significant setback for developers and users alike, and any guidance or updates would be greatly appreciated.
I have a simple example of a List with multiple selection. When I run it on macOS, and select an item from the list, it works fine but I get a warning in the console:
Publishing changes from within view updates is not allowed, this will cause undefined behavior
Interestingly, it doesn't produce a purple 'issue' in the Issues navigator, but as I change selection, I keep getting this warning.
Also, the warning doesn't show when running the same code on iOS.
Here is code to reproduce it:
struct TestListSelection: View {
let testArray = [TestItem(itemValue: 1), TestItem(itemValue: 2), TestItem(itemValue: 3), TestItem(itemValue: 4)]
@ObservedObject var listOptions: TestListViewModel
var body: some View {
List (selection: $listOptions.multipleSelection) {
Section("Header") {
ForEach (testArray, id: \.self) { item in
Button {
print("row tapped - \(item.itemValue)")
} label: {
VStack {
HStack {
Text(item.itemString)
}
}
}
.buttonStyle(.plain)
}
}
}
.listStyle(.plain)
}
}
public struct TestItem: Identifiable, Hashable {
public let id = UUID()
let itemValue: Int
var itemString: String {
get {
return "test \(itemValue)"
}
}
}
@MainActor
public class TestListViewModel: NSObject, ObservableObject {
@Published public var multipleSelection = Set<TestItem>()
}
I annotated the view model with @MainActor as suggested in other threads, but it doesn't silence the warning.
If I move the multipleSelection into the view itself, and make it a @State variable (bypassing the viewModel completely), it works and doesn't produce a warning. But I need it work so I can pass in selection from the UIKit part of the app as well. I also can't migrate to @Observable because my main project has to support iOS15 and above.
Any clue why this is happening on macOS specifically, and what I can do to avoid it?
I ran into this with a more complex project, but was able to recreate with a very simple example below. I have a List with five items in it. I use a ForEach on the items with .onMove. The onMove function has a destination value.
If my list is: A, B, C, D, E, and I drag B so it is below C, I would expect to get A, C, B, D, E. Further, I would expect that destination would be 2, since I am moving B to the 3rd index, assuming that A is at index 0.
Weirdly, destination is always 3.
Conversely, if I do the opposite, and drag C so it is above B, and I get A, C, B, D, E, destination is now 1! This makes sense, since it's the second position.
So why is it that moving down the list results in what appears to be 1-based indexing, but moving it up the list seems to be 0-based indexing? How am I supposed to reliably get the correct destination value?
@State var items = ["A", "B", "C", "D", "E"]
var body: some View {
NavigationStack {
List {
ForEach(items, id: \.self) { item in
Text(item)
}
.onMove(perform: moveItem)
}
.toolbar {
ToolbarItem {
EditButton()
}
}
}
}
private func moveItem(from source: IndexSet, to destination: Int) {
print("destination is \(destination)")
}
}
Output from above. If I drag B below C:
destination is 3
If I drag C above B:
destination is 1
I'm unable to find sample code that demonstrates how to support a custom Live Activity layout for Apple Watch. I have read the documentation and have added the supplementalActivityFamilies with small and medium. However, I am not able to detect when the activityFamily is set to small.
This is what I'm trying to use without success:
struct MyWidgetLiveActivity: Widget {
@Environment(\.activityFamily) var activityFamily: ActivityFamily
var body: some WidgetConfiguration {
ActivityConfiguration(for: MyWidgetAttributes.self) { context in
if activityFamily == .small {
Text("Apple Watch! \(activityFamily.description)")
} else {
Text("Not small family: \(activityFamily.description)")
}
} dynamicIsland: { context in
return DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom")
}
} compactLeading: {
Text("CL")
} compactTrailing: {
Text("CT")
} minimal: {
Text("M")
}
}
.supplementalActivityFamilies([.small, .medium])
}
}
This code shows "Not small family: medium" on my Apple Watch. Could somebody provide some insight into why this doesn't work for me?
I have added a "welcome" tip to my SwiftUI app, which only appears on the main screen the first time the app is launched.
On macOS and iOS, the TipView has an X button that lets the user dismiss the tip.
However, on tvOS there is no such button, and I cannot figure out how to dismiss the tip at all. Using the remote, I am unable to navigate to the tip and highlight it so I can click it to dismiss. Pressing the Home remote button while the tip is displayed has no effect other than closing my app and going back to the tvOS launch screen.
Am I missing something?
struct ContentView: View {
@Environment(TempestDataProvider.self) private var dataProvider
@State private var welcomeTip = WelcomeTip()
var body: some View {
VStack {
Grid {
GridRow {
TemperatureMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true)
WindMetricBox(alignment: .trailing, backgroundStyle: nil, bottomPadding: true)
}
GridRow {
HumidityMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true)
PressureMetricBox(alignment: .trailing, backgroundStyle: nil, bottomPadding: true)
}
GridRow {
RainMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true)
SunMetricBox(alignment: .trailing, backgroundStyle: nil, bottomPadding: true)
}
GridRow {
LightningMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true)
MetricBox(alignment: .trailing, systemImageName: "sensor", backgroundStyle: nil, bottomPadding: true) {
IndicatorLightPanel()
}
}
}
.fixedSize(horizontal: false, vertical: true)
Spacer()
TipView(welcomeTip)
StatusBar()
}
}
}
Greetings,
i have been watched the video and looked for codes to apply live activity for watch.
problem is in preview always show my widget as medium size even if apply supporting widget family and changing the content of preview to dynamicIsland .compact
however preview still shows me the medium size everytime. what is wrong ?
I am trying to create Widget, which does the following.
User specifies his intent, triggering the Timeline.
the Timeline returns an entry, with isLoading Flag for the Widget View to render [IsLoading] View.
in Background ( somehow ), the timeLine fetchs the real data via an API Call, and on its response, the timeline reloads the Widget View with the real data.
Implemented the Number 3, list above, via some programing logic, and calling reloadAllTimelines. This works sometimes, and fails other times.
Any help ?
Hi there, I'm having an app developed in the last weeks, and recently I'm testing it on iOS 18. This following piece of UI code has diff between iOS 18 and iOS version lower than 18.
I have a NavigationStack in my homeView, and the display difference is for its toolbar in one destination. I've tried both approaches in code, with header variable or ToolbarItemGroup used directly in the toolbar modifier, both would result in there being a spacer between the body VStack and toolbar, which is unexpected. Here's the code and a demo screenshot.
var body: some View {
// header
VStack(alignment: .leading) {
notificationView(
iconKey: "ErrorCircle",
contentKey: "receivedFile.notification.noNetwork.content"
)
fileListView
}
.toolbar {
// header
ToolbarItemGroup(placement: .principal) {
Button {
dismiss()
} label: {
Image(systemName: "chevron.backward")
}
.background(Color.yellow)
Spacer()
Text(LocalizedStringKey("title"))
.font(
.system(size: 17)
.weight(.semibold)
)
.background(Color.yellow)
Spacer()
Button {
print("click")
} label: {
Text("Click")
}
.background(Color.yellow)
}
}
.navigationBarBackButtonHidden()
.onAppear {
refreshAllFiles()
}
}
@ToolbarContentBuilder private var header: some ToolbarContent {
ToolbarItem(placement: .topBarLeading) {
Button {
dismiss()
} label: {
Image(systemName: "chevron.backward")
}
.background(Color.yellow)
}
ToolbarItem(placement: .principal) {
Text(LocalizedStringKey("receivedFileList.title"))
.font(
.system(size: 17)
.weight(.semibold)
)
.background(Color.yellow)
}
ToolbarItem(placement: .topBarTrailing) {
Button {
print("click for jumping")
} label: {
Text("Click for jumping")
}
.background(Color.yellow)
}
}
Hope I can get some help from the forum on the usage. If not fixable, may I know if this is a known issue that would be fixed in the next upgrades.
To change the background of a TextField, I simply apply:
TextField("0.0", value: $p, format: .number)
.textFieldStyle(PlainTextFieldStyle())
.background(.red)
That does only work with plain style, but it works.
Trying something similar on a button changes the container view background.
The only solution I've found is to overlap with a Rectangle.
How is it ?
A SwiftUI bug ?
A current limit of Swift ?
A rationale for it ?
There a better solution I've not found ?
Hello community,
I'm an iOS developer from VW group, I was doing some proof of concepts around an automaker app, and I've found a big blocker. When I was trying to summon a keyboard, it wasn't displayed.
So is it even possible?
Do I have to do some workaround with Carplay templates in order to be able to have the native keyboard from Carplay, or is there a special component that I need to introduce in my code to summon the keyboard?
Thank you very much.
David Cosialls.
https://developer.apple.com/documentation/WidgetKit/Adding-interactivity-to-widgets-and-Live-Activities#Add-a-toggle
Before explaining the situation, I referred to this document.
I'm making a widget with a toggle that works with Intent. (To be precise, it's Live Activity, but Apple's literally toggling and button interaction are implemented in the same way)
If you press the toggle when the device is locked, the toggle represents the state.
However, as described at the top of the same document, the device must be unlocked to execute Intent, so I can't actually change the data.
That's fine, but how can I return the isOn of the toggle to false? I'm blocked here because no code is executed.
If it is a widget in the home screen, it will be basically unlocked, so I can proceed according to the method of the document, but if it is today's view, user can access it even if device is locked.
I tested it in today's view with the Reminders app, If I don't unlock the device after tapping the toggle it returns the toggle back to false state.
I wonder how I can achieve this.
Is there an API that I missed?
Summary.
Tap the toggle of the widget while the device is locked.
The device waits for unlocking, but the user cancels it without unlocking it. (Still locked)
Return the isOn of the toggle to false. <- The part I want.
I've been obsessed with this topic for the past couple of weeks and unfortunately there just isn't a good answer out there even from the community. Therefore I am hoping that I can summon Quinn to get an official Apple position (on what's seemingly a fairly fundamental part of using SwiftUI).
Consider this simple example:
import Foundation
@MainActor
@Observable
class UserViewModel {
var name: String = "John Doe"
var age: Int = 30
// other properties and logic
}
// NetworkManager does not need to update the UI but needs to read/write from UserViewModel.
class NetworkManager {
func updateUserInfo(viewModel: UserViewModel) {
Task {
// Read values from UserViewModel prior to making a network call
let userName: String
let userAge: Int
// Even for a simple read, we have to jump onto the main thread
await MainActor.run {
userName = viewModel.name
userAge = viewModel.age
}
// Now perform network call with the retrieved values
print("Making network call with userName: \(userName) and userAge: \(userAge)")
// Simulate network delay
try await Task.sleep(nanoseconds: 1_000_000_000)
// After the network call, we update the values, again on the main thread
await MainActor.run {
viewModel.name = "Jane Doe"
viewModel.age = 31
}
}
}
}
// Example usage
let viewModel = UserViewModel()
let networkManager = NetworkManager()
// Calling from some background thread or task
Task {
await networkManager.updateUserInfo(viewModel: viewModel)
}
In this example, we can see a few things
The ViewModel is a class that manages states centrally
It needs to be marked as MainActor to ensure that updating of the states is done on the main thread (this is similar to updating @Published in the old days). I know this isn't officially documented in Apple's documentation. But I've seen this mentioned many times to be recommended approach including www.youtub_.com/watch?v=4dQOnNYjO58 and here also I have observed crashes myself when I don't follow this practise
Now so far so good, IF we assume that ViewModel are only in service to Views. The problem comes when the states need to be accessed outside of Views.
in this example, NetworkManager is some random background code that also needs to read/write centralized states. In this case it becomes extremely cumbersome. You'd have to jump to mainthread for each write (which ok - maybe that's not often) but you'd also have to do that for every read.
Now. it gets even more cumbersome if the VM holds a state that is a model object, mentioned in this thread..
Consider this example (which I think is what @Stokestack is referring to)
import Foundation
// UserModel represents the user information
@MainActor // Ensuring the model's properties are accessed from the main thread
class UserModel {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
@MainActor
@Observable
class UserViewModel {
var userModel: UserModel
init(userModel: UserModel) {
self.userModel = userModel
}
}
// NetworkManager does not need to update the UI but needs to read/write UserModel inside UserViewModel.
class NetworkManager {
func updateUserInfo(viewModel: UserViewModel) {
Task {
// Read values from UserModel before making a network call
let userName: String
let userAge: Int
// Jumping to the main thread to safely read UserModel properties
await MainActor.run {
userName = viewModel.userModel.name
userAge = viewModel.userModel.age
}
// Simulate a network call
print("Making network call with userName: \(userName) and userAge: \(userAge)")
try await Task.sleep(nanoseconds: 1_000_000_000)
// After the network call, updating UserModel (again, on the main thread)
await MainActor.run {
viewModel.userModel.name = "Jane Doe"
viewModel.userModel.age = 31
}
}
}
}
// Example usage
let userModel = UserModel(name: "John Doe", age: 30)
let viewModel = UserViewModel(userModel: userModel)
let networkManager = NetworkManager()
// Calling from a background thread
Task {
await networkManager.updateUserInfo(viewModel: viewModel)
}
Now I'm not sure the problem he is referring still exists (because I've tried and indeed you can make codeable/decodables marked as @Mainactor) but it's really messy.
Also, I use SwiftData and I have to imagine that @Model basically marks the class as @MainActor for these reasons.
And finally, what is the official Apple's recommended approach? Clearly Apple created @Observable to hold states of some kind that drives UI. But how do you work with this state in the background?