I have created a minimum example to demonstrate an issue with observing SwiftData model and UndoManager. This project includes a simple NavigationSplitView, an Item SwiftData model that is being persisted and an enabled UndoManager.
Problem: The SwiftData model Item can be observed as expected. Changing the date in the DetailView works as expected and all related views (ListElementView + DetailView) are updated as expected. When pressing ⌘+Z to undo with the enabled UndoManager, deletions or inserts in the sidebar are visible immediately (and properly observed by ContentView). However, when changing the timestamp and pressing ⌘+Z to undo that change, it is not properly observed and immediately updated in the related views (ListElementView + DetailView).
Further comments:
Undo operation to the model value changes (here: timestamp) are visible in the DetailView when changing sidebar selections
Undo operation to the model value changes (here: timestamp) are visible in the ListElementView when restarting the app
Undo operation to the model value changes (here: timestamp) are are properly observed and immediately visible in the sidebar, when ommiting the ListElementView (no view encapsulation)
Relevant code base:
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
@State private var selectedItems: Set<Item> = []
var body: some View {
NavigationSplitView {
List(selection: $selectedItems) {
ForEach(items) { item in
ListElementView(item: item)
.tag(item)
}
.onDelete(perform: deleteItems)
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
} detail: {
if let item = selectedItems.first {
DetailView(item: item)
} else {
Text("Select an item")
}
}
.onDeleteCommand {
deleteSelectedItems()
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
private func deleteSelectedItems() {
for selectedItem in selectedItems {
modelContext.delete(selectedItem)
selectedItems.remove(selectedItem)
}
}
}
struct ListElementView: View {
@Bindable var item: Item
var body: some View {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
}
}
struct DetailView: View {
@Bindable var item: Item
var body: some View {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
DatePicker(selection: $item.timestamp, label: { Text("Change Date:") })
}
}
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
It seems that the UndoManager does not trigger a redraw of the ContentView through the items query? Is this a bug or a feature?
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Posts under SwiftUI tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hello,
I'm having some difficulties trying to customise a SwiftUI list-detail splitview using List and OutlineGroup:
The model used is provided in Apple documentation OutlineGroup.
The contentView is
struct ContentView: View {
@State var itemString = String()
var body: some View {
HSplitView {
MyOutLine(title: "MyOutLine", itemString: $itemString)
.frame(width: 200, height: 300
, alignment: Alignment(horizontal: .leading, vertical: .top))
.padding()
MyView(itemString: itemString)
}
}
}
The left view is
struct MyOutLine: View {
let title:String
@Binding var itemString:String
@State private var selection: FileItem?
var body: some View {
List(selection: $selection) {
OutlineGroup(data, children: \.children) { item in
Text ("\(item.description)")
.onTapGesture {
selection = item
itemString = item.description
}
.listRowBackground(
selection == item ?
Color.gray
:nil
)
}
}
.listStyle(.sidebar)
.onAppear { itemString = "No selection"}
}
}
The right view is:
struct MyView: View {
let itemString:String
var body: some View {
VStack{
Spacer()
HStack {
Spacer()
Text(itemString)
Spacer()
}
Spacer()
}
}
}
The result works but I have 2 problems:
The selection works but the detail view is updated only when I click the text but not when I click the rest of the row. This results in the detail view not being sync with the list selection.
I would like to customise the colour of the selected row but listRowBackground does not seem to work.
Is there a way to fix this
Thank you in advance
Jean Marie
After reading the documentation for TranslationSession and other API methods I got an impression that it will not be possible to use the Translation API with UI elements built with UIKit.
You don’t instantiate this class directly. Instead, you obtain an instance of it by adding a translationTask(_:action:) or translationTask(source:target:action:) function to the SwiftUI view containing the content you want to translate
So the main question I have to the engineers of mentioned framework is will there be any support for UIKit or app developers will have to rewrite their apps in SwiftUI just to be able to use the Translation API?
Hello,
I have a SwiftUI view with the following state variable:
@State private var startDate: Date = Date()
@State private var endDate: Date = Date()
@State private var client: Client? = nil
@State private var project: Project? = nil
@State private var service: Service? = nil
@State private var billable: Bool = false
Client, Project, and Service are all SwiftData models. I have some view content that binds to these values, including Pickers for the client/project/service and a DatePicker for the Dates.
I have an onAppear listener:
.onAppear {
switch state.mode {
case .editing(let tt):
Task {
await MainActor.run {
startDate = tt.startDate
endDate = tt.endDate
client = tt.client
project = tt.project
service = tt.service
billable = tt.billable
}
}
default:
return
}
}
This works as expected. However, if I remove the Task & MainActor.run, the values do not fully update. The DatePickers show the current date, the Pickers show a new value but tapping on them shows a nil default value.
What is also extremely strange is that if tt.billable is true, then the view does update as expected.
I am using Xcode 15.4 on iOS simulator 17.5. Any help would be appreciated.
I have a menu bar extra app that includes a sub-menu for lesser used functions. The sub menu implements .keyboardShortcut for a few of the menu items. When the app is first the active app, the keyboard shortcuts only produce a beep UNTIL the sub menu has been accessed. Once the sub-menu has loaded, the keyboard shortcuts work as expected.
I should note that the MenuBarExtra is using the .window display mode, if that's important. The submenu appears with a button press within the MenuBarExtra.
Is there a method to expose the keyboard shortcuts to the system before the view has loaded?
The SwiftData predicate documentation says that it supports the contains(where:) sequence operation in addition to the contains(_:) string comparison but when I put them together in a predicate to try and perform a tokenised search I get a runtime error.
Unsupported subquery collection expression type (NSInvalidArgumentException)
I need to be able to search for items that contain at least one of the search tokens, this functionality is critical to my app. Any suggestions are appreciated. Also does anyone with experience with CoreData know if this is possible to do in CoreData with NSPredicate?
import SwiftData
@Model
final class Item {
var textString: String = ""
init() {}
}
func search(tokens: Set<String>, context: ModelContext) throws -> [Item] {
let predicate: Predicate<Item> = #Predicate { item in
tokens.contains { token in
item.textString.contains(token)
}
}
let descriptor = FetchDescriptor(predicate: predicate)
return try context.fetch(descriptor)
}
When Swiftui Self._printChanges() prints StructName: @40 changed. what does the 40 represent ?
When Swiftui Self._printChanges() prints StructName: @self changed. How do I determine why self changed?
Please consider simple example below.
I am trying to put a timer in the upper right corner of a live activity. I am done, it works, but I'm trying to get the timer to look better.
If I take a regular text, I can get it to align properly by adjusting the .frame(), , but for a text with a timer inside it, alignment is ignored from what I can see.
Text("Hello").frame(width: 90, alignment: .trailing).border(.red)
/*Text(timerInterval: timeRange, countsDown: false)
.monospacedDigit().font(.subheadline).frame(width: 90, alignment: .trailing).border(.red)*/
}
Is there any way to fix this?
Right now, I have a fixed width so that HH:mm:ss will fit, but that doesn't look super great if it's just minutes and seconds for example, there's an empty block to the right
Hi y'all,
After getting mono recording working, I want to differentiate my app from the standard voice memos to allow for stereo recording. I followed this tutorial (https://developer.apple.com/documentation/avfaudio/capturing_stereo_audio_from_built-in_microphones) to get my voice recorder to record stereo audio. However, when I look at the waveform in Audacity, both channels are the same. If I look at the file info after sharing it, it says the file is in stereo. I don't exactly know what's going on here. What I suspect is happening is that the recorder is only using one microphone. Here is the relevant part of my recorder:
// MARK: - Initialization
override init() {
super.init()
do {
try configureAudioSession()
try enableBuiltInMicrophone()
try setupAudioRecorder()
} catch {
// If any errors occur during initialization,
// terminate the app with a fatalError.
fatalError("Error: \(error)")
}
}
// MARK: - Audio Session and Recorder Configuration
private func enableBuiltInMicrophone() throws {
let audioSession = AVAudioSession.sharedInstance()
let availableInputs = audioSession.availableInputs
guard let builtInMicInput = availableInputs?.first(where: { $0.portType == .builtInMic }) else {
throw Errors.NoBuiltInMic
}
do {
try audioSession.setPreferredInput(builtInMicInput)
} catch {
throw Errors.UnableToSetBuiltInMicrophone
}
}
private func configureAudioSession() throws {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.record, mode: .default, options: [.allowBluetooth])
try audioSession.setActive(true)
} catch {
throw Errors.FailedToInitSessionError
}
}
private func setupAudioRecorder() throws {
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd, HH:mm:ss"
let timestamp = dateFormatter.string(from: date)
self.recording = Recording(name: timestamp)
guard let fileURL = recording?.returnURL() else {
fatalError("Failed to create file URL")
}
self.currentURL = fileURL
print("Recording URL: \(fileURL)")
do {
let audioSettings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVLinearPCMIsNonInterleaved: false,
AVSampleRateKey: 44_100.0,
AVNumberOfChannelsKey: isStereoSupported ? 2 : 1,
AVLinearPCMBitDepthKey: 16,
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
]
audioRecorder = try AVAudioRecorder(url: fileURL, settings: audioSettings)
} catch {
throw Errors.UnableToCreateAudioRecorder
}
audioRecorder.delegate = self
audioRecorder.prepareToRecord()
}
//MARK: update orientation
public func updateOrientation(withDataSourceOrientation orientation: AVAudioSession.Orientation = .front, interfaceOrientation: UIInterfaceOrientation) async throws {
let session = AVAudioSession.sharedInstance()
guard let preferredInput = session.preferredInput,
let dataSources = preferredInput.dataSources,
let newDataSource = dataSources.first(where: { $0.orientation == orientation }),
let supportedPolarPatterns = newDataSource.supportedPolarPatterns else {
return
}
isStereoSupported = supportedPolarPatterns.contains(.stereo)
if isStereoSupported {
try newDataSource.setPreferredPolarPattern(.stereo)
}
try preferredInput.setPreferredDataSource(newDataSource)
try session.setPreferredInputOrientation(interfaceOrientation.inputOrientation)
}
Here is the relevant part of my SwiftUI view:
RecordView()
.onAppear {
Task {
if await AVAudioApplication.requestRecordPermission() {
// The user grants access. Present recording interface.
print("Permission granted")
} else {
// The user denies access. Present a message that indicates
// that they can change their permission settings in the
// Privacy & Security section of the Settings app.
model.showAlert.toggle()
}
try await recorder.updateOrientation(interfaceOrientation: deviceOrientation)
}
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let orientation = windowScene.windows.first?.windowScene?.interfaceOrientation {
deviceOrientation = orientation
Task {
do {
try await recorder.updateOrientation(interfaceOrientation: deviceOrientation)
} catch {
throw Errors.UnableToUpdateOrientation
}
}
}
}
Here is the full repo: https://github.com/aabagdi/MemoMan/tree/MemoManStereo
Thanks for any leads!
I’m seeing a crash in production for a small percentage of users, and have narrowed it down based on logging to happening as or very shortly after an alert is presented using SwiftUI.
This seems to be isolated to iOS 17.5.1, but since it’s a low-volume crash I can’t be sure there aren’t other affected versions. What can I understand from the crash report?
Here’s a simplified version of the code which presents the alert, which seems so simple I can’t understand why it would crash. And following that is the crash trace.
// View (simplified)
@MainActor public struct MyView: View {
@ObservedObject var model: MyViewModel
public init(model: MyViewModel) {
self.model = model
}
public var body: some View {
myViewContent
.overlay(clearAlert)
}
var clearAlert: some View {
EmptyView().alert(
"Are You Sure?",
isPresented: $model.isClearAlertVisible,
actions: {
Button("Keep", role: .cancel) { model.clearAlertKeepButtonWasPressed() }
Button("Delete", role: .destructive) { model.clearAlertDeleteButtonWasPressed() }
},
message: {
Text("This cannot be undone.")
}
)
}
}
// Model (simplified)
@MainActor public final class MyViewModel: ObservableObject {
@Published var isClearAlertVisible = false
func clearButtonWasPressed() {
isClearAlertVisible = true
}
func clearAlertKeepButtonWasPressed() {
// No-op.
}
func clearAlertDeleteButtonWasPressed() {
// Calls other code.
}
}
Incident Identifier: 36D05FF3-C64E-4327-8589-D8951C8BAFC4
Distributor ID: com.apple.AppStore
Hardware Model: iPhone13,2
Process: My App [379]
Path: /private/var/containers/Bundle/Application/B589E780-96B2-4A5F-8FCD-8B34F2024595/My App.app/My App
Identifier: com.me.MyApp
Version: 1.0 (1)
AppStoreTools: 15F31e
AppVariant: 1:iPhone13,2:15
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.me.MyApp [583]
Date/Time: 2024-06-21 20:09:20.9767 -0500
Launch Time: 2024-06-20 18:41:01.7542 -0500
OS Version: iPhone OS 17.5.1 (21F90)
Release Type: User
Baseband Version: 4.50.06
Report Version: 104
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a69998c0
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [379]
Triggered by Thread: 0
Kernel Triage:
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
Thread 0 name:
Thread 0 Crashed:
0 libswiftCore.dylib 0x00000001a69998c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144)
1 AttributeGraph 0x00000001d0cd61a4 Attribute.init<A>(body:value:flags:update:) + 352 (Attribute.swift:473)
2 SwiftUI 0x00000001ac034054 closure #1 in Attribute.init<A>(_:) + 128 (<compiler-generated>:0)
3 SwiftUI 0x00000001ac033cac partial apply for closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0)
4 libswiftCore.dylib 0x00000001a6ad0450 withUnsafePointer<A, B>(to:_:) + 28 (LifetimeManager.swift:128)
5 SwiftUI 0x00000001ad624d14 closure #2 in UIKitDialogBridge.startTrackingUpdates(actions:) + 268 (UIKitDialogBridge.swift:370)
6 SwiftUI 0x00000001ad624ae0 UIKitDialogBridge.startTrackingUpdates(actions:) + 248 (UIKitDialogBridge.swift:369)
7 SwiftUI 0x00000001ad6250cc closure #4 in UIKitDialogBridge.showNewAlert(_:id:) + 72 (UIKitDialogBridge.swift:471)
8 SwiftUI 0x00000001abfdd050 thunk for @escaping @callee_guaranteed () -> () + 36 (:-1)
9 UIKitCore 0x00000001aa5722e4 -[UIPresentationController transitionDidFinish:] + 1096 (UIPresentationController.m:651)
10 UIKitCore 0x00000001aa571d88 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.114 + 320 (UIPresentationController.m:1390)
11 UIKitCore 0x00000001aa5cb9ac -[_UIViewControllerTransitionContext completeTransition:] + 116 (UIViewControllerTransitioning.m:304)
12 UIKitCore 0x00000001aa34a91c __UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__ + 36 (UIView.m:16396)
13 UIKitCore 0x00000001aa34a800 -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 624 (UIView.m:16429)
14 UIKitCore 0x00000001aa349518 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 436 (UIView.m:0)
15 UIKitCore 0x00000001aa356b14 -[UIViewAnimationState animationDidStop:finished:] + 192 (UIView.m:2400)
16 UIKitCore 0x00000001aa356b84 -[UIViewAnimationState animationDidStop:finished:] + 304 (UIView.m:2422)
17 QuartzCore 0x00000001a96f8c50 run_animation_callbacks(void*) + 132 (CALayer.mm:7714)
18 libdispatch.dylib 0x00000001aff61dd4 _dispatch_client_callout + 20 (object.m:576)
19 libdispatch.dylib 0x00000001aff705a4 _dispatch_main_queue_drain + 988 (queue.c:7898)
20 libdispatch.dylib 0x00000001aff701b8 _dispatch_main_queue_callback_4CF + 44 (queue.c:8058)
21 CoreFoundation 0x00000001a808f710 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1780)
22 CoreFoundation 0x00000001a808c914 __CFRunLoopRun + 1996 (CFRunLoop.c:3149)
23 CoreFoundation 0x00000001a808bcd8 CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420)
24 GraphicsServices 0x00000001ecf3c1a8 GSEventRunModal + 164 (GSEvent.c:2196)
25 UIKitCore 0x00000001aa6c490c -[UIApplication _run] + 888 (UIApplication.m:3713)
26 UIKitCore 0x00000001aa7789d0 UIApplicationMain + 340 (UIApplication.m:5303)
27 SwiftUI 0x00000001ac27c148 closure #1 in KitRendererCommon(_:) + 168 (UIKitApp.swift:51)
28 SwiftUI 0x00000001ac228714 runApp<A>(_:) + 152 (UIKitApp.swift:14)
29 SwiftUI 0x00000001ac2344d0 static App.main() + 132 (App.swift:114)
30 My App 0x00000001001e7bfc static MyApp.$main() + 52 (MyApp.swift:0)
31 My App 0x00000001001e7bfc main + 64
32 dyld 0x00000001cb73de4c start + 2240 (dyldMain.cpp:1298)
Any one getting any issues with NavigaitonLink to seemingly innocuous views freezing when tapped on? 1 CPU at 100% memory steadily increasing until app gets killed by the system. Will freeze if any NavigationLink on the view is tapped if certain views are linked to using NavigaitonLink.
I note some people have been getting similar freezes if they use @AppStorage, but I'm not using @AppStorage. I do use CoreData tho. tho I have some views that use core data that don't freeze.
https://developer.apple.com/forums/thread/708592?page=1#736374022
has anyone experienced similar issues? or know the cause. it doesn't seem to be any of my code because if I pause the debugger it stops on system code.
Basically, in my widget/live activity, I want to extract reusable views into a separate file with an isolated view and preview. Dummy example below.
I cannot do it because it says "missing previewcontext".
The only way I've found is to add the view to my main app target, but I don't want to clutter my main app wiews that only exist in my widgets if I can avoid it.
Can this be done somehow? Thoughts appreciated.
Dummy example (tried with and without "previewLayout":
struct StatusActivityView: View {
let status: UserStatusData
var body: some View {
VStack(alignment: .center) {
Text("Dummy example")
}.background(.blue).padding(5)
}
}
@available(iOS 16.2, *)
struct StatusActivityView_Previews: PreviewProvider {
static var previews: some View {
let status = WidgetConstants.defaultEntry()
return StatusActivityView(status: status).previewLayout(.sizeThatFits)
}
}
I'm developing an app where a user can bring a video or content from a WKWebView into an immersive space using SwiftUI attachments on a RealityView.
This works just fine, but I'm having some trouble configuring how the audio from the web content should sound in an immersive space.
When in windowed mode, content playing sounds just fine and very natural. The spatial audio effect with head tracking is pronounced and adds depth to content with multichannel or Dolby Atmos audio.
When I move the same web view into an immersive space however, the audio becomes excessively echoey, as if a large amount of reverb has been put onto the audio. The spatial audio effect is also decreased, and while still there, is no where near as immersive.
I've tried the following:
Setting all entities in my space to use channel audio, including the web view attachment.
for entity in content.entities {
entity.channelAudio = ChannelAudioComponent()
entity.ambientAudio = nil
entity.spatialAudio = nil
}
Changing the AVAudioSessionSpatialExperience:
And I've also tried every soundstage size and anchoring strategy, large works the best, but doesn't remove that reverb.
let experience = AVAudioSessionSpatialExperience.headTracked(
soundStageSize: .large,
anchoringStrategy: .automatic
)
try? AVAudioSession.sharedInstance().setIntendedSpatialExperience(experience)
I'm also aware of ReverbComponent in visionOS 2 (which I haven't updated to just yet), but ideally I need a way to configure this for visionOS 1 users too.
Am I missing something? Surely there's a way for developers to stop the system messing with the audio and applying these effects? A few of my users have complained that the audio sounds considerably worse in my cinema immersive space compared to in a window.
I am new to SwiftUI, and I wrote a ReorderableForEach struct with onDrag and onDrop, which is working well on iOS17. However it's not wokring on iOS18 beta1 or beta2 on my iPhone or simulator. When I long press the item, nothing happens.
When I remove onDrop, I can perform a Drag animation, so I think something wrong with my onDrop code.
the whole code:
import UIKit
import SwiftUI
public typealias Reorderable = Identifiable & Equatable
struct GridData: Identifiable, Equatable {
let id: Int
}
public struct ReorderableForEach<Item: Reorderable, Content: View, Preview: View>: View {
@Binding
private var active: Item?
@State
private var hasChangedLocation = false
private let items: [Item]
private let content: (Item) -> Content
private let preview: ((Item) -> Preview)?
private let moveAction: (IndexSet, Int) -> Void
private var onDropAction: ((Item) -> Void)?
private var allowReorder: Bool
public init(
_ items: [Item],
active: Binding<Item?>,
allowReorder: Bool,
@ViewBuilder content: @escaping (Item) -> Content,
@ViewBuilder preview: @escaping (Item) -> Preview,
moveAction: @escaping (IndexSet, Int) -> Void,
onDropAction: ((Item) -> Void)? = nil
) {
self.items = items
self._active = active
self.allowReorder = allowReorder
self.content = content
self.preview = preview
self.moveAction = moveAction
self.onDropAction = onDropAction
}
public init(
_ items: [Item],
active: Binding<Item?>,
allowReorder: Bool,
@ViewBuilder content: @escaping (Item) -> Content,
moveAction: @escaping (IndexSet, Int) -> Void,
onDropAction: ((Item) -> Void)? = nil
) where Preview == EmptyView {
self.items = items
self._active = active
self.allowReorder = allowReorder
self.content = content
self.preview = nil
self.moveAction = moveAction
self.onDropAction = onDropAction
}
public var body: some View {
ForEach(items) { item in
if !allowReorder {
contentView(for: item)
}
else if let preview {
contentView(for: item)
.onDrag {
return dragData(for: item)
} preview: {
preview(item)
}
} else {
contentView(for: item)
.onDrag {
return dragData(for: item)
}
}
}
}
private func contentView(for item: Item) -> some View {
content(item)
.opacity(active == item && hasChangedLocation ? 0.5 : 1)
.onDrop(
of: [.text],
delegate: ReorderableDragRelocateDelegate(
item: item,
items: items,
active: $active,
hasChangedLocation: $hasChangedLocation
) { from, to in
withAnimation {
moveAction(from, to)
}
} onDropAction: { item in
onDropAction?(item)
}
)
}
private func dragData(for item: Item) -> NSItemProvider {
active = item
return NSItemProvider(object: "\(item.id)" as NSString)
}
}
struct ReorderableDragRelocateDelegate<Item: Reorderable>: DropDelegate {
let item: Item
var items: [Item]
@Binding var active: Item?
@Binding var hasChangedLocation: Bool
var moveAction: (IndexSet, Int) -> Void
var onDropAction: ((Item) -> Void)?
func dropEntered(info: DropInfo) {
guard item != active, let current = active else { return }
guard let from = items.firstIndex(of: current) else { return }
guard let to = items.firstIndex(of: item) else { return }
hasChangedLocation = true
if items[to] != current {
moveAction(IndexSet(integer: from), to > from ? to + 1 : to)
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
hasChangedLocation = false
active = nil
onDropAction?(item)
return true
}
}
struct ReorderableDropOutsideDelegate<Item: Reorderable>: DropDelegate {
@Binding
var active: Item?
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
active = nil
return true
}
}
public extension View {
func reorderableForEachContainer<Item: Reorderable>(
active: Binding<Item?>
) -> some View {
onDrop(of: [.text], delegate: ReorderableDropOutsideDelegate(active: active))
}
}
#Preview {
ReorderableForEach(
(1...10).map { GridData(id: $0) },
active: .constant(nil),
allowReorder: true
) { item in
Text("Item \(item.id)")
.padding()
.background(Color.blue)
.cornerRadius(8)
} preview: { item in
Text("Preview \(item.id)")
.padding()
.background(Color.red)
.cornerRadius(8)
} moveAction: { from, to in
print("Move from \(from) to \(to)")
}
}
Am I missing something or this is a bug on iOS18?
Thanks in advance.
For whatever reason SwiftUI sheets don't seem to be resizable anymore.
The exact same code/project produces resizable Sheets in XCode 15.4 but unresizable ones with Swift included in Xcode 16 beta 2.
Tried explicitly providing .fixedSize(horizontal false, vertical: false) everywhere humanly possible hoping for a fix but sheets are still stuck at an awkward size (turns out be the minWidth/minHeight if I provide in .frame).
I'm working on creating a tiny app that copies a random sampling of files from a source folder to a destination folder: https://github.com/belovachap/Select-Random-Files-Mac
I choose folders in my user's Documents folder, am able to get the random sampling of files but then run into permission errors when trying to copy to the destination folder.
I've tried adding a call to startAccessingSecurityScopedResource on the destination URL but it doesn't seem to help.
Is this having the same problems on anyone else's computer??
Having a traditional 'NavigationSplitView' setup, I am looking for a way to animate it the same as the sidebarView, where there is a button to toggle and it animates by sliding out from the right side of the view, however the closest I have gotten was manipulating the 'navigationSplitViewColumnWidth' but that always results in the view instantly appearing / disappearing.
I am using SwiftUI for a MacOS specific app.
Here is just a general idea of what I am currently doing, it is by no means a reflection of my real code but serves the purpose of this example.
struct ContentView: View {
@State private var columnWidth: CGFloat = 300
var body: some View {
NavigationSplitView {
List {
NavigationLink(destination: DetailView(item: "Item 1")) {
Text("Item 1")
}
NavigationLink(destination: DetailView(item: "Item 2")) {
Text("Item 2")
}
NavigationLink(destination: DetailView(item: "Item 3")) {
Text("Item 3")
}
}
.navigationTitle("Items")
} detail: {
VStack {
DetailView(item: "Select an item")
Button(action: toggleColumnWidth) {
Text(columnWidth == 300 ? "Collapse" : "Expand")
}
.padding()
}
}
.navigationSplitViewColumnWidth(columnWidth)
}
private func toggleColumnWidth() {
withAnimation {
columnWidth = columnWidth == 300 ? 0 : 300
}
}
}
struct DetailView: View {
var item: String
var body: some View {
Text("Detail view for \(item)")
.navigationTitle(item)
.padding()
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
I have a SwiftUI page that I want to simplify by showing basic information by default, and putting the additional info behind a "Details" DisclosureGroup for advanced users.
I started by laying out all the components and breaking things into individual Views. These all are laid out and look fine.
Then I took several of them and added them inside a DisclosureGroupView.
But all of a sudden, the views inside started getting crunched together and the contents of the DisclosureGroup got clipped about 2/3 of the way down the page. The problem I'm trying to solve is how to show everything inside the DIsclosureGroup.
The top-level View looks like this:
VStack {
FirstItemView()
SecondView()
DetailView() // <- Shows disclosure arrow
}
Where DetailView is:
struct DetailView: View {
@State var isExpanded = true
var body: some View {
GeometryReader { geometry in
DisclosureGroup("Details", isExpanded: $isExpanded) {
ThirdRowView()
Spacer()
FourthRowView()
VStack {
FifthRowWithChartView()
CaptionLabelView(label: "Third", iconName: "chart.bar.xaxis")
}
}
}
}
}
The FifthRowWithChartView is half-clipped. One thing that might contribute is that there is a Chart view at the bottom of this page.
I've tried setting the width and height of the DisclosureGroup based on the height returned by the GeometryReader, but that didn't do anything.
This is all on iOS 17.6, testing on an iPhone 15ProMax. Any tips or tricks are most appreciated.
Hi, I learned swift only a few weeks ago, and im trying to make a TabView in Xcode. But, it shows only one item! I've already made this work in other apps, but I can't get it working here!
import SwiftUI
struct tabs: View {
var body: some View {
TabView {
ContentView()
.tabItem {
Image(systemName: "house.circle.fill")
Text("Home")
Settngs()
.tabItem {
Image(systemName:"gear.circle.fill")
Text("Settings")
}
}
}
}
}
#Preview {
tabs()
}
and also, I checked "settngs()" and its my name