We have separated much of our UI into different packages to reduce complexity and compile time. When we recently tested using new .xcstrings string catalogs, we hit an unexpected problem.
Strings extracted from SwiftUI components like Text or Button are extracted into the Localizable.xcstrings in the same package, but the default behaviour of Text(_ key:tableName:bundle:comment:) is to use Bundle.main.
When the default behaviour of the string extraction isn't to extract to the main app target, this introduces a very fragile system where it's easy to add code that looks localised, but ends up failing lookup at runtime.
I don't feel comfortable that we will always remember to define the correct module every time we create a Text. Also, other components like Button doesn't have an init that takes a Bundle, so we would also have to remember that Button(_ titleKey:action:) can now only be used in a package if we make sure that the main bundle contains a matching key.
Is there a way for us to make sure that strings are always extracted to the same place as they are resolved against by default? Either by having strings in packages extracted to an xcstrings file in the main app or having Text default to resolving against the module bundle by default?
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.
Post
Replies
Boosts
Views
Activity
In my Watch app on watchOS 9 I was using .foregroundColor(myColour) to colour the text in a widgetLabel on a corner complication like this:
let myColour: Color = functionThatReturnsAColorObjectConstructedLike Color.init(...) // green
.widgetLabel {
Text(myText)
.foregroundColor(myColour)
}
It worked fine; the widget label was green.
Now, in watchOS 10, I see that foregroundColor() is being deprecated in favour of foregroundStyle(), and I can use .foregroundStyle(.green), and - importantly - foregroundStyle() is only available on watchOS 10 and newer.
myColour is calculated depending on some other info, so I can't just write .green, and when I use .foregroundStyle(myColour) the widget label comes out as white every time, even if I set myColour = .green.
I think I have to use some sort of extension to pick the right combination, something like:
extension View {
func foregroundType(colour: Colour, style: any ShapeStyle) -> some THING? {
if #available(watchOS 10.0, *) {
return foregroundStyle(style)
} else {
return foregroundColor(colour)
}
}
}
// Usage
let myStyle: any ShapeStyle = SOMETHING?
...
.widgetLabel {
Text(myText)
.foregroundType(colour: myColour, style: myStyle)
It doesn't work. I just can't figure out what should be returned, nor how to return it. Any ideas?
I'm manually placing a subclass of NSView into the parent view using addSubview:positioned:relativeTo. The dirtyRect passed to drawRect: is wildly incorrect. Can folks attempt to reproduce and file bugs? This is awfully close to the release of Sonoma, and I feel like folks with bezier curves (or maybe other drawing code?) in their NSView subclasses are going to experience problems.
To reproduce, place a view (I'm using an NSImageView) as a subview within a view.
Then, create a subclass of NSView and draw a bezier curve in the drawRect method. Add an instance of this subclass as a subview of your original view. I'm offsetting the x value for clarity. When I build with Xcode 15 and run on Ventura or earlier, I get the correct result. Or, if I build with Xcode 14.3 and run on Sonoma I get the correct result. However, when I build in Xcode 15 and run on the RC build of Sonoma, I get a whacky result.
I get something like (origin = (x = -264, y = -146), size = (width = 480, height = 388)) for the dirtyRect in the error case, while the rect is supposed to be (origin = (x = 0, y = 0), size = (width = 48, height = 48)) (I'm basing the frame of the new view on the original image.)
Thanks!
I'm running into an issue with using .topBarTrailing placement for a toolbar item. The app fails to launch (crashes) with this placement. The following view works fine with any other placement (other than .topBarLeading).
What am I doing wrong?
var body: some View {
NavigationStack {
Text("Overview")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
// noop
} label: {
Label("Add", systemImage: "plus")
}
}
}
.navigationTitle("Overview")
.navigationBarTitleDisplayMode(.inline)
}
}
I've opted to use .confirmationAction as a workaround, which works fine. It also positions the toolbar item in the same place on the view as the .topBarTrailing placement would.
I'm using Xcode version 15.0 and targeting WatchOS 10.
Verbose error output when trying to run the app in the simulator:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Layout requested for visible navigation bar, <PUICStackedNavigationBar: 0x100e1e8d0; baseClass = UINavigationBar; frame = (0 0; 198 60); opaque = NO; autoresize = W; layer = <CALayer: 0x60000027c280>> delegate=0x101877800 standardAppearance=0x60000261cc60, when the top item belongs to a different navigation bar. topItem = <UINavigationItem: 0x100f11230> title='Overview' style=navigator, navigation bar = <PUICStackedNavigationBar: 0x100f22a80; baseClass = UINavigationBar; frame = (0 0; 198 60); opaque = NO; autoresize = W; layer = <CALayer: 0x6000002887c0>> delegate=0x101069600 standardAppearance=0x60000261f3c0, possibly from a client attempt to nest wrapped navigation controllers.'
I found an issue when implementing an alert with a TextField to input a name. I want the action button to be disabled until a name has been entered, but the action block is never executed when the button has become enabled and pressed. The problem seems to appear only when name is initially an empty string. Tested with iOS 17.0.
struct MyView: View {
@State private var name = ""
var body: some View {
SomeView()
.alert(...) {
TextField("Name", text: $name)
Button("Action") {
// Action
}.disabled(name.isEmpty)
Button("Cancel", role: .cancel) {}
}
}
}
Our MacOS application has a single window which is occupied by an NSView-derived view. It's been working for the last ten years or so, but when using the Sonoma beta, window updates are badly broken.
We rely on using setNeedsDisplayInRect to redisplay any portions of the view that need to be redisplayed, but no matter how small a rectangle we specify, the entire window is repainted with the background colour before our drawRect implementation is called. We already provide an overload of isOpaque in our view which returns true, and in the past this was effective for suppressing the background fill, but it no longer seems to work (although I can confirm that it is still called along the way).
I've attached an image that shows an example of how a sample window looks after resizing (which is correct) and then what it looks like after using setNeedsDisplayInRect to invalidate the region occupied by the button in the centre. I've explicitly set the NSWindow background colour to blue to make it more obvious :
Is it still possible to inhibit the background fill? Repainting the entire view for every update is not really an option for us, for performance reasons
Hi.
I am very new to SwiftUI and still trying to learn.
I just encountered a very weird problem that I can't figure out.
I have an iPad application that runs on Apple Silicon Macs as "Designed for iPad"; for some reason, this issue only comes up when running it on a Mac, even if the code is exactly the same.
This is the view that's causing issues:
struct ProfileEditView: View {
@EnvironmentObject var mainModel: MainViewModel
@EnvironmentObject var authModel: AuthViewModel
[some other stuff]
var body: some View {
GeometryReader { geo in
VStack(spacing: 20) {
navigationBar()
.padding(.horizontal, 3)
if authModel.user != nil {
VStack(alignment: .leading, spacing: 30) {
PhotosPicker(selection: self.$imageSelection,
matching: .images,
preferredItemEncoding: .compatible) {
ProfilePicView(editIconShown: true)
}
.disabled(authModel.profilePicIsLoading)
.padding(.horizontal, geo.size.width * 0.3)
.padding(.bottom)
VStack(spacing: 10) {
if let error = self.error {
Text(error)
.foregroundStyle(Color.red)
.font(.footnote)
.italic()
}
SettingsTextFieldView(String(localized: "Your name:"),
value: self.$name) {
if nameIsValid {
authModel.updateName(self.name)
}
}
}
Spacer()
actions()
}
.padding(.horizontal, 5)
.padding(.top)
.onAppear {
self.name = authModel.user!.name
}
}
}
.padding()
}
}
}
Obviously, I am injecting the ViewModels instances at the app entry point:
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@StateObject var authViewModel: AuthViewModel = AuthViewModel()
@StateObject var mainViewModel: MainViewModel = MainViewModel()
@StateObject var statsViewModel: StatsViewModel = StatsViewModel()
@StateObject var purchaseViewModel: PurchaseViewModel = PurchaseViewModel()
@State var revenueCatIsConfigured: Bool = false
init() {
// MARK: Firebase configuration
FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
// MARK: RevenueCat configuration
if let uid = AuthViewModel.getLoggedInUserID() {
Purchases.logLevel = .error
Purchases.configure(withAPIKey: "redacted", appUserID: uid)
self.revenueCatIsConfigured = true
}
}
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(authViewModel)
.environmentObject(mainViewModel)
.environmentObject(statsViewModel)
.environmentObject(purchaseViewModel)
}
}
}
And lastly, ProfileEditView that is causing issues, is a subview of MainView:
MainView has a switch statement, based on the selected tab; one of the possible views is SettingsView, which can show ProfileEditView as a sheet modifier.
For some reason, only when running on mac, I get the error
Thread 1: Fatal error: No ObservableObject of type AuthViewModel found. A View.environmentObject(_:) for AuthViewModel may be missing as an ancestor of this view.
This will come up every time I reference authModel in this view alone.
Both the iPad and iPhone versions work just fine, and again, the code is exactly the same, since it's a "Designed for iPad".
I don't even know how to troubleshoot the issue.
Using the screencapture CLI on macOS Sonoma 14.0 (23A344) results in a 72dpi image file, no matter if it was captured on a retina display or not.
For example, using
screencapture -i ~/Desktop/test.png in Terminal lets me create a selective screenshot, but the resulting file does not contain any DPI metadata (checked using mdls ~/Desktop/test.png), nor does the image itself have the correct DPI information (should be 144, but it's always 72; checked using Preview.app).
I noticed a (new?) flag option, -r, for which the documentation states:
-r Do not add screen dpi meta data to captured file.
Is that flag somehow automatically set? Setting it myself makes no difference and obviously results in a no-dpi-in-metadata and wrong-dpi-in-image file.
The only two ways I got the correct DPI information in a resulting image file was using the default options (forced by -p): screencapture -i -p, and by making the capture go to the clipboard screencapture -i -c. Sadly, I can't use those in my case.
Feedback filed: FB13208235
I'd appreciate any pointers,
Matthias
I have enabled “StrictConcurrency” warnings in my project that uses SwiftUI. I have a Commands struct. It has a Button, whose action is calling an async method via Task{}. This builds without warnings within Views, but not Commands. There the compiler reports “Main actor-isolated property 'body' cannot be used to satisfy nonisolated protocol requirement”.
Looking at SwiftUI:
In View, body is declared @MainActor:
@ViewBuilder @MainActor var body: Self.Body { get }
In Commands, body is not declared @MainActor:
@CommandsBuilder var body: Self.Body { get }
So the common practice of making a Button action asynchronous:
Button {
Task { await model.load() }
} label:{
Text("Async Button")
}
will succeed without warnings in Views, but not in Commands. Is this intentional? I've filed FB13212559. Thank you.
This function on NSTextLayoutManager has the following signature
func enumerateTextSegments(
in textRange: NSTextRange,
type: NSTextLayoutManager.SegmentType,
options: NSTextLayoutManager.SegmentOptions = [],
using block: (NSTextRange?, CGRect, CGFloat, NSTextContainer) -> Bool
)
The documentation here doesn't define what the CGRect and CGFloat passed to block are. However, looking at sample code Using TextKit2 To Interact With Text, they seem to be the frame for the textsegment and baselineposition respectively.
But, the textSegmentFrame seems to start at origin.x = 5.0 when text is empty. Is this some starting offset for text segments? I don't seem to be able to find mention of this anywhere.
I can set color of the SF symbol image in the application window but cannot do the same in the menu bar. I wonder how I can change the color in the menu?
import SwiftUI
@main
struct ipmenuApp: App {
var body: some Scene {
MenuBarExtra {
Image(systemName: "bookmark.circle.fill")
.renderingMode(.original)
.foregroundStyle(.red)
} label: {
Image(systemName: "bookmark.circle.fill")
.renderingMode(.original)
.foregroundStyle(.red)
}
}
}
xcodebuild -version
Xcode 15.0
Build version 15A240d
I'm experiencing a peculiar issue with SwiftUI's TextField. Whenever I long-press on the TextField, the console outputs an error about passing an invalid numeric value (NaN, or not-a-number) to the CoreGraphics API. This issue persists even in a new Xcode project with minimal code.
Code Snippet:
import SwiftUI
struct ContentView: View {
@State private var text: String = ""
var body: some View {
TextField("Placeholder", text: $text)
}
}
Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
Steps to Reproduce:
Create a new SwiftUI project in Xcode.
Add a TextField to the ContentView.
Run the app on a device or simulator.
Long-press inside the TextField.
What I've Tried:
Updating to the latest version of Xcode and iOS.
Using UIViewRepresentable to wrap a UIKit UITextField.
Creating a new Xcode project to isolate the issue.
None of these steps have resolved the issue.
Questions:
Has anyone else encountered this problem?
Are there any known workarounds for this issue?
Is this a known bug, and if so, has it been addressed in any updates?
Hi! I find this code does not work as expected on iOS 17 simulator and device. It was working correctly with iOS 16:
struct ContentView: View {
@State private var numberText = ""
var color: Color {
let result = (Int(numberText) ?? 0) <= 0
if result {
return .black
}
return .red
}
var body: some View {
VStack {
TextField("Enter a number", text: $numberText)
.font(.title)
.foregroundStyle(
color
)
Text("Font color will turn red if number > 0")
.foregroundColor(.gray)
}
.padding()
}
}
I tried a workaround and it works:
struct ContentView: View {
@State private var numberText = ""
@State private var color = Color.black
func updateColor() ->Color {
let result = (Int(numberText) ?? 0) <= 0
if result {
return .black
}
return .red
}
var body: some View {
VStack {
TextField("Enter a number", text: $numberText)
.font(.title)
.foregroundStyle(
color
)
.onChange(of:numberText) {
color = updateColor()
}
Text("Font color will turn red if number > 0")
.foregroundColor(.gray)
}
.padding()
}
}
I have two main models, House and Expense. On the home page I show a list of the three most recent expenses using the following query:
var descriptor = FetchDescriptor<Expense>(predicate: #Predicate { $0.houseID == house.id }, sortBy: [SortDescriptor(\.date, order: .reverse)])
descriptor.fetchLimit = 3
_recentExpenses = Query(descriptor)
I have a NavigationLink that takes you to the full list of expenses:
NavigationLink {
ExpenseListView(house: house)
} label: {
Text("See all")
}
On this page I have a query similar to the previous one, without the fetch limit:
let descriptor = FetchDescriptor<Expense>(
predicate: #Predicate { $0.houseID == house.id}
)
_expenses = Query(descriptor)
The problem is that when I click on the navigation link to go to the expenses page, the app freezes.
I tried passing the expenses array to the second page instead of invoking another query, but this way the view doesn't update when I update the expenses.
Is there a way to solve this problem? Or a way to pass a Binding of the fetched results?
When using the Navigation Stack with paths, I've faced an issue where tapping the 'back' button on the custom navigation bar results in a blank screen. With light theme the screen is white, when using dark theme the screen is black. I've found a decision with using an environment value to dismiss a single screen when needed. However, the problem becomes more prominent when I want to pop back to the root view.
This issue has become since updating to iOS 17.
I want to recreate an user experience like in the settings app in tvOS. Therefore I have a HStack with some content on the left side and a List of NavigationLinks on the right side.
However a focused link in the list gets clipped on the left side. I tried paddings and spacers and what not, but nothing helped. Is this is a bug or am I missing something?
Here is some example code to show the problem:
struct ContentView: View {
var body: some View {
NavigationStack {
HStack(spacing: 20) {
VStack(alignment: .center) {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
List {
ForEach(SomeViewEnum.allCases) { someView in
NavigationLink(someView.rawValue, value: someView)
}
}
.navigationDestination(for: SomeViewEnum.self) { someView in
Text(someView.rawValue)
}
}
}
}
}
And a screenshot to show the problem:
I have some visualisation-heavy AUv3's, and the goal is not to perform graphics-intensive tasks if the plugin window is not opened inside the host app (such as Logic Pro).
On iOS, this is easily accomplished by the viewWillAppear, etc overrides. But on macOS, it seems these overrides are not called every time the user opens / closes the plugin windows in the host application.
I did try some alternate methods, like trying to traverse the view / controller hierarchy, or make use of the window property, to no avail.
What substitute mechanism could I use to determine visibility status of an AUv3 on macOS?
Thanks in advance,
Zoltan
Hi, I adopted in my app the new default style for the status bar where it switches between light and dark themes depending on the color of the content beneath. It works great until I hide the status bar and show it again. From that point onwards, the status bar will be stuck on the dark or light theme, depending on the app's theme.
This happens as well if the status bar is initially hidden on startup. In that case, the default behavior never works.
I filed an issue (FB13222217), and as of now I didn't find a workaround except never hiding the status bar on my app. I attached a super minimalist example project there, just a scroll view with black and white views and nothing more.
This happens on SwiftUI and UIKit as well.
I hope the issue can be addressed and will appreciate if anyone has a workaround for this to let me know.
Thanks!
After upgrading to iOS 17 I am struggling to get decimals working in my app - here is a sample code: for clarity I provide a complete "working" sample (I am pulling data from a back-end server using json, etc - all of that is working ok, it's the UI issue - see below):
Model
import Foundation
struct TestFloat: Encodable {
var testFloatNumber: Float = 0
}
Observable Class
import Foundation
class TestFloatViewModel: ObservableObject {
@Published var testFloat = TestFloat()
}
View
struct TestFloatView: View {
@ObservedObject var testFloatVM: TestFloatViewModel
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
var body: some View {
VStack {
HStack {
Text("Enter Float Number")
Spacer()
}
Spacer()
.frame(height: 2.0)
HStack {
TextField("Enter Float Number", value: $testFloatVM.testFloat.testFloatNumber, formatter: formatter)
.keyboardType(.decimalPad)
.textFieldStyle(.roundedBorder)
}
}
}
}
This is working on a device with iOS16; however when run on device with iOS17:
you can enter maximum four digits?
using backspace you can delete all numbers apart of the first one
you cannot enter the decimal (.) at all even though decimal keyboard is provided
Any help is greatly appreciated.
Activity image for custom activity is blank ever since iOS 17 release ,compiled in Xcode 15. The image being sent is 180x180 px. Works fine in iOS 16 running earlier Xcodes.
Attached a image of the issue showing blank icons (mail and messages are the custom activities)
Anyone facing the issue?