Post

Replies

Boosts

Views

Activity

Reply to Weird @Binding behavior, doesn't update the value until I set it into different one
I remember seeing this somewhere before. The issue is that the contents of the sheet are initially created with the initial value (0) before the sheet is first shown. Subsequent sheet presentations then use the new changed value. The solution to this is to capture the current variable's value as the sheet is about to be shown. Here is how you would do that: .sheet(isPresented: $bindingValueBool, content: { [bindingValueInt] in // use the captured value which will be correct Text("This is the selected number \(bindingValueInt)") }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to How to retrieve values set in Hashable paths when using a router to navigate through views
You will need to extract the id property from the Form case value using Swift's pattern matching capabilities. Here is an example of how you could achieve this: // Add a computed property to the view var formID: String { if case .Form(let id) = router.paths.last { id } else { "No ID" // handle the case where the ID couldn't be extracted } } // Use this id property as a string Text(formID)
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to How to change the background behind a presented view
You can use the presentationBackground(_:) modifier. Here is an example of how you can use it: .sheet(...) { SheetView() .presentationBackground(.red.opacity(0.5)) // pass in a colour or even a custom view } Note: the translucency of the sheet background means content behind the sheet presentation will show through. If you don't want this behaviour use the regular background(_:ignoresSafeAreaEdges:) modifier with a maximum frame.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to SwiftUI Map Annotations
Since you already have the visible region shown on the map and all annotations, with their coordinates, you can work out which annotations are located in that region. MKMapRect has a useful method contains(_:) which indicates whether the specified map point lies within the rectangle. There are two (small?) problems though: Map expects annotations to have location coordinates of type CLLocationCoordinate2D but MKMapRect uses MKMapPoint. Luckily, an MKMapPoint can be formed from a CLLocationCoordinate2D. I'm assuming you are storing your map's region as an MKCoordinationRegion object inside of an @State variable. If so, this needs to be converted to an MKMapRect object and that isn't an easy process. If you don't require using MKCoordinationRegion, then store an MKMapRect instead which will make things easier. If you do need it, you will need to manually convert (a quick search online will yield some results), and don't forget about the 180th meridian. [Note: iOS 17 doesn't make Problem 2 an issue, with the new APIs handling things differently.] Here's a demo app I made to showcase how you can achieve this: // A single map annotation as an object struct AnnotationItem: Identifiable { let id = UUID() let title: String let coordinates: CLLocationCoordinate2D } struct MapAnnotations: View { // Store the map's currently visible rect @State private var visibleRect = MKMapRect(x: 125_000_000, y: 75_000_000, width: 15_000_000, height: 25_000_000) // Dummy data let items: [AnnotationItem] = [ .init(title: "San Francisco", coordinates: .init(latitude: 37.77938, longitude: -122.41843)), .init(title: "New York", coordinates: .init(latitude: 40.71298, longitude: -74.00720)), .init(title: "São Paulo", coordinates: .init(latitude: -23.57964, longitude: -46.65506)), .init(title: "London", coordinates: .init(latitude: 51.50335, longitude: -0.07940)), .init(title: "Rome", coordinates: .init(latitude: 41.88929, longitude: 12.49355)), .init(title: "Johannesburg", coordinates: .init(latitude: -26.20227, longitude: 28.04363)), .init(title: "Mumbai", coordinates: .init(latitude: 18.94010, longitude: 72.83466)), .init(title: "Tokyo", coordinates: .init(latitude: 35.68951, longitude: 139.69170)), .init(title: "Melbourne", coordinates: .init(latitude: -37.81503, longitude: 144.96634)) ] var body: some View { Map(mapRect: $visibleRect, annotationItems: items) { item in MapMarker(coordinate: item.coordinates) } .ignoresSafeArea() .overlay(alignment: .bottom) { let annotations = visibleAnnotations() // Show the list of visible annotations if there are any if !annotations.isEmpty { VStack { ForEach(annotations) { item in Text(item.title) .font(.title3.bold()) } } .padding(10) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 10)) .padding() } } } func visibleAnnotations() -> [AnnotationItem] { items.filter { item in // Check if the item's location is in the map's currently visible rect visibleRect.contains(.init(item.coordinates)) } } } Any questions about this please let me know and I'll be happy to answer.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to SwifUI - Set frame on Group of Buttons not working as expected
The issue is not with the Group but with the order of the modifiers. The order is very important in SwiftUI as it dictates how views are rendered and can produce different outcomes by just flipping two modifiers around. In your case, you are applying the frame modifier after the background modifier which is why you are seeing this result: To explain, these two snippets are effectively the same and demonstrates what Group is doing: Group { Button { } label: { Text("One") .padding() } .background(.black) .tint(.white) Button { } label: { Text("Two") .padding() } .background(.white) .tint(.black) } .frame(minWidth: 0, maxWidth: .infinity) // <- Button { } label: { Text("One") .padding() } .background(.black) .tint(.white) .frame(minWidth: 0, maxWidth: .infinity) // <- Button { } label: { Text("Two") .padding() } .background(.white) .tint(.black) .frame(minWidth: 0, maxWidth: .infinity) // <- What you really want is this: which can be achieved by applying the frame modifier before the background modifier, like this: Button { } label: { Text("One") .padding() } .frame(minWidth: 0, maxWidth: .infinity) // <- .background(.black) .tint(.white) Button { } label: { Text("Two") .padding() } .frame(minWidth: 0, maxWidth: .infinity) // <- .background(.white) .tint(.black) This has to be done individually as using Group can't fulfil that. What you can do instead is create a custom button style with this max frame (and anything else you want) and then apply that to the Group or HStack.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to How to create window without border between title bar and content view?
This style of window is without the title bar. You would need to remove/hide it to get this effect. In SwiftUI, you would add this modifier to your main App: WindowGroup { ContentView() } .windowStyle(.hiddenTitleBar) // add this modifier here However, it seems like you are using the AppKit lifecycle which means you will have to do this: myWindow = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 200, height: 200), styleMask: [.closable, .titled, .fullSizeContentView], // can make the contentView consume the full size of the window backing: .buffered, defer: false) // Modify these two properties myWindow.titleVisibility = .hidden myWindow.titlebarAppearsTransparent = true
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to Disabled button in SwiftUI .alert not working
I encountered a similar issue at the release of iOS 16 which is when I filed a feedback report. At that time, buttons that were disabled didn't show up in the alert at all. Then, sometime during the early iOS 17 betas (beta 4 maybe?), the issue was addressed and this was added to the SwiftUI release notes: Resolved Issues Fixed an issue where dynamically enabled buttons (e.g. with a TextField) were not updated in alerts. (95917673) (FB10463211) I then had to test this to see if things were fixed, and the disabled buttons did show up…but the action closure wasn't called at all. I then filed another feedback report (FB12857555) and nothing has happened yet. I suggest also filing feedback as that will help elevate the problem with Apple and hopefully get it fixed soon.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to SwiftUI inspector full height
I believe you need to place the inspector modifier outside of a navigation structure (NavigationStack or NavigationSplitView), which I think was mentioned in the session video. Although you wouldn't necessarily use one for macOS, it's probably needed to have the full-height behaviour. This works for me: struct ContentView: View { var body: some View { NavigationStack { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } .padding() .toolbar { Text("test") } } .inspector(isPresented: .constant(true)) { Text("this is a test") } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Oct ’23
Reply to SwiftUI: Cannot find '$lektion' in scope
The toggle requires a Binding to a boolean value but your lektion variable cannot provide one. The solution is to use the Bindable property wrapper like this: ForEach(lektionen) { lektion in @Bindable var lektion = lektion // add this line NavigationLink { VokabelnView(lektion: lektion).navigationTitle("Vokabeln") } label: { Toggle("", isOn: $lektion.isOn) } } Now you can access $lektion which can create bindings to its properties.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Nov ’23
Reply to SwiftUI Orientation Change
The issue you have is that the if statement is placed inline inside the class and Swift doesn't know what to do with it. This is where variable and function declarations go and not code blocks or expressions. Instead, I think you want to put it in an initialiser, like this: init() { if myHorizClass == .compact && myVertClass == .regular { self.myOrient = "Portrait" } else if myHorizClass == .regular && myVertClass == .compact { self.myOrient = "Landscape" } else { self.myOrient = "Something Else" } } Also, I don't know if it was a typo or not, but in the if statement you had elseif instead of else if. Does this solve your problem?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
May ’24
Reply to SwiftUI Trap change of orientation
I am unsure of what you're trying to accomplish. Which do you want to do? Detect when the user rotates their device – in terms of portrait and landscape orientation. Detect when the physical size of your app changes – in terms of size classes. Option 1 isn't recommended in cases such as Split View. For example, an iPad may be in landscape orientation but the width of your app isn't the full width of the device, only a portion of it. Option 2 uses predefined values that tells you whether your app is horizontally (width) or vertically (height) "compact", so you can configure your UI accordingly. I suggest you have a look at the documentation, especially for SwiftUI. You have some concepts about views, data models and communicating between them incorrect. Also check out this guide on layout; the "Device size classes" section might give you a better understanding into what "compact" and "regular" size classes mean.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
May ’24
Reply to SwiftUI Trap change of orientation
In that case, don't give up so easily. When working with background images you don't really need to worry about device orientation. SwiftUI can let you resize the image and scale it to fill the available space. Something like this should work: struct BackgroundImageView: View { var body: some View { ZStack { // Background image Image(.background) .resizable() // make sure image can be resized .scaledToFill() // scale the image so it fills all available space .ignoresSafeArea() // [optional] Makes the image fill the whole screen including going behind the clock and battery (top) and the home bar (bottom) // Other content goes here Text("Main Content") } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
May ’24
Reply to Weird @Binding behavior, doesn't update the value until I set it into different one
I remember seeing this somewhere before. The issue is that the contents of the sheet are initially created with the initial value (0) before the sheet is first shown. Subsequent sheet presentations then use the new changed value. The solution to this is to capture the current variable's value as the sheet is about to be shown. Here is how you would do that: .sheet(isPresented: $bindingValueBool, content: { [bindingValueInt] in // use the captured value which will be correct Text("This is the selected number \(bindingValueInt)") }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to MapProxy conversion from screen to coords is wrong on macOS
Can you try using a custom coordinate space like this and see if it works. I can't test it because I'm on Ventura. Map { ... } .onTapGesture(perform: { screenCoord in pinLocation = reader.convert(screenCoord, from: .named("map")) ... }) .coordinateSpace(.named("map"))
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to How to retrieve values set in Hashable paths when using a router to navigate through views
You will need to extract the id property from the Form case value using Swift's pattern matching capabilities. Here is an example of how you could achieve this: // Add a computed property to the view var formID: String { if case .Form(let id) = router.paths.last { id } else { "No ID" // handle the case where the ID couldn't be extracted } } // Use this id property as a string Text(formID)
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to How to change the background behind a presented view
You can use the presentationBackground(_:) modifier. Here is an example of how you can use it: .sheet(...) { SheetView() .presentationBackground(.red.opacity(0.5)) // pass in a colour or even a custom view } Note: the translucency of the sheet background means content behind the sheet presentation will show through. If you don't want this behaviour use the regular background(_:ignoresSafeAreaEdges:) modifier with a maximum frame.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to WatchOS Battery Level in SwiftUI
WKInterfaceDevice.current().batteryLevel
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to SwiftUI Map Annotations
Since you already have the visible region shown on the map and all annotations, with their coordinates, you can work out which annotations are located in that region. MKMapRect has a useful method contains(_:) which indicates whether the specified map point lies within the rectangle. There are two (small?) problems though: Map expects annotations to have location coordinates of type CLLocationCoordinate2D but MKMapRect uses MKMapPoint. Luckily, an MKMapPoint can be formed from a CLLocationCoordinate2D. I'm assuming you are storing your map's region as an MKCoordinationRegion object inside of an @State variable. If so, this needs to be converted to an MKMapRect object and that isn't an easy process. If you don't require using MKCoordinationRegion, then store an MKMapRect instead which will make things easier. If you do need it, you will need to manually convert (a quick search online will yield some results), and don't forget about the 180th meridian. [Note: iOS 17 doesn't make Problem 2 an issue, with the new APIs handling things differently.] Here's a demo app I made to showcase how you can achieve this: // A single map annotation as an object struct AnnotationItem: Identifiable { let id = UUID() let title: String let coordinates: CLLocationCoordinate2D } struct MapAnnotations: View { // Store the map's currently visible rect @State private var visibleRect = MKMapRect(x: 125_000_000, y: 75_000_000, width: 15_000_000, height: 25_000_000) // Dummy data let items: [AnnotationItem] = [ .init(title: "San Francisco", coordinates: .init(latitude: 37.77938, longitude: -122.41843)), .init(title: "New York", coordinates: .init(latitude: 40.71298, longitude: -74.00720)), .init(title: "São Paulo", coordinates: .init(latitude: -23.57964, longitude: -46.65506)), .init(title: "London", coordinates: .init(latitude: 51.50335, longitude: -0.07940)), .init(title: "Rome", coordinates: .init(latitude: 41.88929, longitude: 12.49355)), .init(title: "Johannesburg", coordinates: .init(latitude: -26.20227, longitude: 28.04363)), .init(title: "Mumbai", coordinates: .init(latitude: 18.94010, longitude: 72.83466)), .init(title: "Tokyo", coordinates: .init(latitude: 35.68951, longitude: 139.69170)), .init(title: "Melbourne", coordinates: .init(latitude: -37.81503, longitude: 144.96634)) ] var body: some View { Map(mapRect: $visibleRect, annotationItems: items) { item in MapMarker(coordinate: item.coordinates) } .ignoresSafeArea() .overlay(alignment: .bottom) { let annotations = visibleAnnotations() // Show the list of visible annotations if there are any if !annotations.isEmpty { VStack { ForEach(annotations) { item in Text(item.title) .font(.title3.bold()) } } .padding(10) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 10)) .padding() } } } func visibleAnnotations() -> [AnnotationItem] { items.filter { item in // Check if the item's location is in the map's currently visible rect visibleRect.contains(.init(item.coordinates)) } } } Any questions about this please let me know and I'll be happy to answer.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to SwifUI - Set frame on Group of Buttons not working as expected
The issue is not with the Group but with the order of the modifiers. The order is very important in SwiftUI as it dictates how views are rendered and can produce different outcomes by just flipping two modifiers around. In your case, you are applying the frame modifier after the background modifier which is why you are seeing this result: To explain, these two snippets are effectively the same and demonstrates what Group is doing: Group { Button { } label: { Text("One") .padding() } .background(.black) .tint(.white) Button { } label: { Text("Two") .padding() } .background(.white) .tint(.black) } .frame(minWidth: 0, maxWidth: .infinity) // <- Button { } label: { Text("One") .padding() } .background(.black) .tint(.white) .frame(minWidth: 0, maxWidth: .infinity) // <- Button { } label: { Text("Two") .padding() } .background(.white) .tint(.black) .frame(minWidth: 0, maxWidth: .infinity) // <- What you really want is this: which can be achieved by applying the frame modifier before the background modifier, like this: Button { } label: { Text("One") .padding() } .frame(minWidth: 0, maxWidth: .infinity) // <- .background(.black) .tint(.white) Button { } label: { Text("Two") .padding() } .frame(minWidth: 0, maxWidth: .infinity) // <- .background(.white) .tint(.black) This has to be done individually as using Group can't fulfil that. What you can do instead is create a custom button style with this max frame (and anything else you want) and then apply that to the Group or HStack.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to How to create window without border between title bar and content view?
This style of window is without the title bar. You would need to remove/hide it to get this effect. In SwiftUI, you would add this modifier to your main App: WindowGroup { ContentView() } .windowStyle(.hiddenTitleBar) // add this modifier here However, it seems like you are using the AppKit lifecycle which means you will have to do this: myWindow = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 200, height: 200), styleMask: [.closable, .titled, .fullSizeContentView], // can make the contentView consume the full size of the window backing: .buffered, defer: false) // Modify these two properties myWindow.titleVisibility = .hidden myWindow.titlebarAppearsTransparent = true
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to Disabled button in SwiftUI .alert not working
I encountered a similar issue at the release of iOS 16 which is when I filed a feedback report. At that time, buttons that were disabled didn't show up in the alert at all. Then, sometime during the early iOS 17 betas (beta 4 maybe?), the issue was addressed and this was added to the SwiftUI release notes: Resolved Issues Fixed an issue where dynamically enabled buttons (e.g. with a TextField) were not updated in alerts. (95917673) (FB10463211) I then had to test this to see if things were fixed, and the disabled buttons did show up…but the action closure wasn't called at all. I then filed another feedback report (FB12857555) and nothing has happened yet. I suggest also filing feedback as that will help elevate the problem with Apple and hopefully get it fixed soon.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to Why is there padding in the Watch Simulator & on a device?
If you're asking about removing the grey areas, then you can adjust the padding of the list items' contents with the listRowInsets(_:) modifier, like so: NavigationLink { ... } .listRowInsets(.init())
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Sep ’23
Reply to SwiftUI inspector full height
I believe you need to place the inspector modifier outside of a navigation structure (NavigationStack or NavigationSplitView), which I think was mentioned in the session video. Although you wouldn't necessarily use one for macOS, it's probably needed to have the full-height behaviour. This works for me: struct ContentView: View { var body: some View { NavigationStack { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } .padding() .toolbar { Text("test") } } .inspector(isPresented: .constant(true)) { Text("this is a test") } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Oct ’23
Reply to SwiftUI: Cannot find '$lektion' in scope
The toggle requires a Binding to a boolean value but your lektion variable cannot provide one. The solution is to use the Bindable property wrapper like this: ForEach(lektionen) { lektion in @Bindable var lektion = lektion // add this line NavigationLink { VokabelnView(lektion: lektion).navigationTitle("Vokabeln") } label: { Toggle("", isOn: $lektion.isOn) } } Now you can access $lektion which can create bindings to its properties.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Nov ’23
Reply to SwiftUI Orientation Change
The issue you have is that the if statement is placed inline inside the class and Swift doesn't know what to do with it. This is where variable and function declarations go and not code blocks or expressions. Instead, I think you want to put it in an initialiser, like this: init() { if myHorizClass == .compact && myVertClass == .regular { self.myOrient = "Portrait" } else if myHorizClass == .regular && myVertClass == .compact { self.myOrient = "Landscape" } else { self.myOrient = "Something Else" } } Also, I don't know if it was a typo or not, but in the if statement you had elseif instead of else if. Does this solve your problem?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
May ’24
Reply to SwiftUI Trap change of orientation
I am unsure of what you're trying to accomplish. Which do you want to do? Detect when the user rotates their device – in terms of portrait and landscape orientation. Detect when the physical size of your app changes – in terms of size classes. Option 1 isn't recommended in cases such as Split View. For example, an iPad may be in landscape orientation but the width of your app isn't the full width of the device, only a portion of it. Option 2 uses predefined values that tells you whether your app is horizontally (width) or vertically (height) "compact", so you can configure your UI accordingly. I suggest you have a look at the documentation, especially for SwiftUI. You have some concepts about views, data models and communicating between them incorrect. Also check out this guide on layout; the "Device size classes" section might give you a better understanding into what "compact" and "regular" size classes mean.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
May ’24
Reply to SwiftUI Trap change of orientation
In that case, don't give up so easily. When working with background images you don't really need to worry about device orientation. SwiftUI can let you resize the image and scale it to fill the available space. Something like this should work: struct BackgroundImageView: View { var body: some View { ZStack { // Background image Image(.background) .resizable() // make sure image can be resized .scaledToFill() // scale the image so it fills all available space .ignoresSafeArea() // [optional] Makes the image fill the whole screen including going behind the clock and battery (top) and the home bar (bottom) // Other content goes here Text("Main Content") } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
May ’24