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.
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
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?
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!
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
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??
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
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
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 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.
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()
}
}
}
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).
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: @self changed. How do I determine why self changed?
When Swiftui Self._printChanges() prints StructName: @40 changed. what does the 40 represent ?
Overview
I've found inconsistency on view lifecycle events between UIKit and SwiftUI as the following shows when using UIVPageViewController and UIHostingController as one of its pages.
SwiftUI View
onAppear is only called at the first time to display and never called in the other cases.
UIViewController
viewDidAppear is not called at the first time to display, but it's called when the page view controller changes its page displayed.
The whole view structure is as follows:
UIViewController (root)
UIPageViewController (as its container view)
UIHostingController (as its page)
SwiftUI View (as its content view)
UIViewControllerRepresentable (as a part of its body)
UIViewController (as its content)
Environment
Xcode Version 15.4 (15F31d)
iPhone 15 Pro (iOS 17.5) (Simulator)
iPhone 8 (iOS 15.0) (Simulator)
Sample code
import UIKit
import SwiftUI
class ViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
private var pageViewController: UIPageViewController!
private var viewControllers: [UIViewController] = []
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
pageViewController.delegate = self
pageViewController.dataSource = self
let page1 = UIHostingController(rootView: MainPageView())
let page2 = UIViewController()
page2.view.backgroundColor = .systemBlue
let page3 = UIViewController()
page3.view.backgroundColor = .systemGreen
viewControllers = [page1, page2, page3]
pageViewController.setViewControllers([page1], direction: .forward, animated: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let pageViewController = segue.destination as? UIPageViewController else { return }
self.pageViewController = pageViewController
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
print("debug: \(#function)")
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
print("debug: \(#function)")
guard let viewControllerIndex = viewControllers.firstIndex(of: viewController) else { return nil }
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0, viewControllers.count > previousIndex else { return nil }
return viewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
print("debug: \(#function)")
guard let viewControllerIndex = viewControllers.firstIndex(of: viewController) else { return nil }
let nextIndex = viewControllerIndex + 1
guard viewControllers.count != nextIndex, viewControllers.count > nextIndex else { return nil }
return viewControllers[nextIndex]
}
}
struct MainPageView: View {
var body: some View {
VStack(spacing: 0) {
PageContentView()
PageFooterView()
}
.onAppear { print("debug: \(type(of: Self.self)) onAppear") }
.onDisappear { print("debug: \(type(of: Self.self)) onDisappear") }
}
}
struct PageFooterView: View {
var body: some View {
Text("PageFooterView")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.onAppear { print("debug: \(type(of: Self.self)) onAppear") }
.onDisappear { print("debug: \(type(of: Self.self)) onDisappear") }
}
}
struct PageContentView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
PageContentViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
class PageContentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
view.backgroundColor = .systemYellow
let label = UILabel()
label.text = "PageContentViewController"
label.font = .preferredFont(forTextStyle: .title1)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("debug: \(type(of: Self.self)) \(#function)")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("debug: \(type(of: Self.self)) \(#function)")
}
}
Logs
// Display the views
debug: MainPageView.Type onAppear
debug: PageFooterView.Type onAppear
// Swipe to the next page
debug: pageViewController(_:viewControllerAfter:)
debug: pageViewController(_:viewControllerBefore:)
debug: PageContentViewController.Type viewDidDisappear(_:)
debug: pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
debug: pageViewController(_:viewControllerAfter:)
// Swipe to the previous page
debug: PageContentViewController.Type viewDidAppear(_:)
debug: pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
debug: pageViewController(_:viewControllerBefore:)
As you can see here, onAppear is only called at the first time to display but never called in the other cases while viewDidAppear is the other way around.
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.
What is the info property of SwiftUI::Layer?
I couldn't find any document or resource about it.
It appears in SwiftUI::Layer's definition:
struct Layer {
metal::texture2d<half> tex;
float2 info[5];
/// Samples the layer at `p`, in user-space coordinates,
/// interpolating linearly between pixel values. Returns an RGBA
/// pixel value, with color components premultipled by alpha (i.e.
/// [R*A, G*A, B*A, A]), in the layer's working color space.
half4 sample(float2 p) const {
p = metal::fma(p.x, info[0], metal::fma(p.y, info[1], info[2]));
p = metal::clamp(p, info[3], info[4]);
return tex.sample(metal::sampler(metal::filter::linear), p);
}
};
I'm giving a go to the new TabSection with iOS 18 but I'm facing an issue with sections actions. I have the following section inside a TabView:
TabSection {
ForEach(accounts) { account in
Tab(account.name , systemImage: account.icon, value: SelectedTab.accounts(account: account)) {
Text(account.name)
}
}
} header: {
Text("Accounts")
}
.sectionActions {
AccountsTabSectionAddAccount()
}
I'm showing a Tab for each account and an action to create new accounts. The issue I'm facing is that when there are no accounts the entire section doesn't appear in the side bar including the action to create new accounts. To make matters worse the action doesn't show at all in macOS even when there are already accounts and the section is present in side bar. Is there some way to make the section actions always visible?
Anyone else get these warnings when using UI Tab Bar in visionOS? Are these detrimental to pushing my visionOS app to the App Review Team?
import SwiftUI
import UIKit
struct HomeScreenWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UITabBarController {
let tabBarController = UITabBarController()
// Home View Controller
let homeVC = UIHostingController(rootView: HomeScreen())
homeVC.tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), tag: 0)
// Brands View Controller
let brandsVC = UIHostingController(rootView: BrandSelection())
brandsVC.tabBarItem = UITabBarItem(title: "Brands", image: UIImage(systemName: "bag"), tag: 1)
tabBarController.viewControllers = [homeVC, brandsVC]
return tabBarController
}
func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {
// Update the UI if needed
}
}
struct HomeScreenWrapper_Previews: PreviewProvider {
static var previews: some View {
HomeScreenWrapper()
}
}
This is a report of an issue that appears to be a regression regarding NavigationStack.
While investigating another issue [iOS18 beta2: NavigationStack, Views Being Popped Automatically] , I encountered this separate issue and wanted to share it.
In a NavigationStack with three levels: RootView - ContentView - SubView,
tapping the Back button from the SubView returned to the RootView instead of the ContentView.
This issue is similar to one that I previously posted regarding iOS16.0 beta.
https://developer.apple.com/forums/thread/715970
Additionally, there is no transition animation when moving from ContentView to SubView.
The reproduction code is as follows:
import SwiftUI
struct RootView2: View {
@State var kind: Kind = .a
@State var vals: [Selection] = {
return (1...5).map { Selection(num: $0) }
}()
@State var selection: Selection?
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
NavigationLink {
ContentView2(vals: $vals, selection: $selection)
} label: {
Text("album")
}
.navigationDestination(isPresented: .init(get: {
return selection != nil
}, set: { newValue in
if !newValue {
selection = nil
}
}), destination: {
if let selection {
SubView2(kind: .a, selection: selection)
}
})
}
} else {
EmptyView()
}
}
}
struct ContentView2: View {
@Binding var vals: [Selection]
@Binding var selection: Selection?
@Environment(\.dismiss) private var dismiss
var body: some View {
list
.onChange(of: self.selection) { newValue in
print("changed: \(String(describing: newValue?.num))")
}
}
@ViewBuilder
private var list: some View {
if #available(iOS 16.0, *) {
List(selection: $selection) {
ForEach(self.vals) { val in
NavigationLink(value: val) {
Text("\(String(describing: val))")
}
}
}
}
}
}
//
struct SubView2: View {
let kind: Kind
let selection: Selection
var body: some View {
Text("Content. \(kind): \(selection)")
}
}