I'm recreating the sleep timer from the Podcasts app. How can I display an icon for the picker instead of the current selection?
This doesn't work:
Picker("Sleep Timer", systemImage: "moon.zzz.fill", selection: $sleepTimerDuration) {
Text("Off").tag(0)
Text("5 Minutes").tag(5)
Text("10 Minutes").tag(10)
Text("15 Minutes").tag(15)
Text("30 Minutes").tag(30)
Text("45 Minutes").tag(45)
Text("1 Hour").tag(60)
}
Do I need to drop down to UIKit for this?
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Post
Replies
Boosts
Views
Activity
Hello! I am making an app in SwiftUI and find it hard to make good-looking UIs with SwiftUI when using the List. When having a big navigation bar, the list appears misaligned with the title.
This is a preview within Xcode. Don't know if that changes anything 🤷. Below is the code. I should note I am using Xcode 16 beta 3.
//
// SwiftUIView.swift
//
import SwiftUI
struct SwiftUIView: View {
struct D {
var id: Int
var name: String
var identifier: String
}
let items: [(Int, D)] = [
(0, D(id: 0, name: "Test One", identifier: "one.example.test")),
(1, D(id: 1, name: "Test One", identifier: "two.example.test")),
(2, D(id: 2, name: "Test One", identifier: "three.example.test")),
(3, D(id: 3, name: "Test One", identifier: "four.example.test"))
]
var body: some View {
NavigationStack {
List(items, id: \.1.id) { idx, d in
VStack(alignment: .leading) {
Text(d.name)
.bold()
Text(d.identifier)
}
}
.navigationTitle("Hello Title")
}
}
}
#Preview {
SwiftUIView()
}
Hi folks,
I've used a NavigationSplitView within one of the tabs of my app since iOS 16, but with the new styling in iOS 18 the toolbar region looks odd. In other tabs using e.g. simple stacks, the toolbar buttons are horizontally in line with the new tab picker, but with NavigationSplitView, the toolbar leaves a lot of empty space at the top (see below). Is there anything I can do to adjust this, or alternatively, continue to use the old style?
Thanks!
I have a TabView with individual tabs containing NavigationSplitViews. On iPadOS 18, when moving the new UI into a sidebar, the title of the first column in the current split view randomly spins its way in and out of view. Is this just a beta bug, or am I doing something wrong? Hard to convey without a recording, but hopefully the screenshots will show what I mean. Thanks.
I'm pursuing a design that necessitates a dual app architecture — using a NavigationStack for compact-sized screens, and a NavigationSplitView for regular-sized screens.
I've encountered what might be a bug in NavigationSplitView on iPadOS 17.x. Or perhaps it's a mistake in my code.
When navigating into nested views (relying on NavigationPath), everything works fine... until I background the app on iPad.
At this point, the navigation stack appears to collapse in the NavigationSplitView detail area — merging the parent and child views.
Adding to the mystery... I can reproduce this bug on iPadOS 17.x. But the problem goes away when running on iPadOS 18.x beta.
Key questions:
Is there a problem in my code?
Should I file a feedback with the Apple SwiftUI team?
If iPadOS 18.x fixes this bug, can I expect a SwiftUI fix to be back-ported to 17.x or earlier?
Steps to reproduce:
Run my sample code on iPadOS simulator.
Navigate to select a color, and then a shape.
Put the app into the background (e.g., go to Home Screen).
Return to app.
What you can expect:
When running on iPadOS 17.x, you'll see that the two most recent views in the stack have merged (meaning the title has changed, and the "back" button has disappeared).
On iPadOS 18.x, you'll see that everything is working fine (title isn't changed, and back button remains available).
Confused as to why the Chart flips with each user input. The console is also output unique id for each slice which was not my intention. Not sure if the unique .id is the culprit behind the flip.
selectedCount changed to: Optional(3)
Selected slice: Optional(App.EmojiUsage(id: 69090646-0D0A-4FE8-86EC-4103608DC3F7, emojiTab: App.emojiTab.sad, count: 1))
Scheduling reset task to run in 2 seconds
Resetting selected slice and count
selectedCount changed to: Optional(1)
Selected slice: Optional(App.EmojiUsage(id: DE4A76D1-CC57-4FA0-A261-9AD1A6E28F95, emojiTab: App.emojiTab.happy, count: 2))
Scheduling reset task to run in 2 seconds
Resetting selected slice and count
selectedCount changed to: Optional(3)
Selected slice: Optional(App.EmojiUsage(id: 5052F8EA-2582-4E72-A61D-01FCCDF3DB03, emojiTab: App.emojiTab.sad, count: 1))
Scheduling reset task to run in 2 seconds
Resetting selected slice and count
selectedCount changed to: Optional(0)
Selected slice: Optional(App.EmojiUsage(id: 5C1AB577-6CFC-4BA8-A9DF-30822EF79B91, emojiTab: App.emojiTab.happy, count: 2))
Scheduling reset task to run in 2 seconds
@Model
class AppModel {
var id: String
var journalEntry: String
var date: Date
var emojiTab: emojiTab
init(journalEntry: String, date: Date, emojiTab: emojiTab) {
self.id = UUID().uuidString
self.journalEntry = journalEntry
self.date = date
self.emojiTab = emojiTab
}
}
struct EmojiPrompt: Identifiable {
var id = UUID()
var icon: RiveViewModel
var emojitab: emojiTab
var title: String
}
enum emojiTab: String, Codable, Plottable {
case happy
case sad
case sleep
var primitivePlottable: Double {
switch self {
case .sleep:
return 0.0
case .happy:
return 1.0
case .sad:
return 2.0
}
}
}
var emojiPrompt = [
EmojiPrompt(
icon: RiveViewModel(
fileName: "app",
stateMachineName: "happyBtnSM",
artboardName: "happyBtn"
),
emojitab: .happy,
title: "Happy 1"
),
EmojiPrompt(
icon: RiveViewModel(
fileName: "app",
stateMachineName: "sadBtnSM",
artboardName: "sadBtn"
),
emojitab: .sad,
title: "Sad 2"
),
EmojiPrompt(
icon: RiveViewModel(
fileName: "app",
stateMachineName: "happyBtnSM",
artboardName: "happyBtn"
),
emojitab: .sleep,
title: "Sleep"
)
]
import SwiftUI
import SwiftData
import RiveRuntime
import Charts
struct SectorChartView: View {
@Environment(\.modelContext) private var context: ModelContext
@Binding var selectedEmojiUsage: EmojiUsage?
@State private var selectedCount: Int?
@Binding var selectedSlice: EmojiUsage?
@State private var resetTask: DispatchWorkItem? // State variable for the reset task
var emojiUsageData: [EmojiUsage]
var resetDelay: TimeInterval = 2.0 // Adjustable delay for reset
var body: some View {
ZStack {
Chart {
ForEach(emojiUsageData) { data in
SectorMark(
angle: .value("Count", data.count),
innerRadius: .ratio(0.70),
outerRadius: selectedSlice?.emojiTab == data.emojiTab ? .ratio(1.0) : .ratio(0.75),
angularInset: 1.5
)
.cornerRadius(4)
.foregroundStyle(by: .value("Emoji", data.emojiTab.rawValue.capitalized))
}
}
.chartAngleSelection(value: $selectedCount)
.chartBackground { chartProxy in
GeometryReader { geo in
let frame = geo[chartProxy.plotFrame!]
VStack {
if let selectedEmojiUsage = selectedEmojiUsage {
RiveViewModel(fileName: "app", stateMachineName: "\(selectedEmojiUsage.emojiTab.rawValue)BtnSM", artboardName: "\(selectedEmojiUsage.emojiTab.rawValue)Btn")
.view()
.frame(width: 120, height: 120)
.id(selectedEmojiUsage.emojiTab.rawValue) // Force re-render when the emojiTab changes
} else {
RiveViewModel(fileName: "app", stateMachineName: "sleepBtnSM", artboardName: "sleepBtn")
.view()
.frame(width: 120, height: 120)
.id("sleep") // Force re-render when default state
}
}
.position(x: frame.midX, y: frame.midY)
}
}
}
.onChange(of: selectedCount) { oldValue, newValue in
// Ensure reset task is only scheduled if there is a valid new value
guard newValue != nil else { return }
resetTask?.cancel() // Cancel any existing reset task
print("selectedCount changed to: \(String(describing: newValue))")
if let newValue = newValue {
withAnimation {
getSelectedSlice(value: newValue)
}
let task = DispatchWorkItem {
withAnimation(.easeIn) {
print("Resetting selected slice and count")
self.selectedSlice = nil
self.selectedCount = nil
self.selectedEmojiUsage = nil
}
}
resetTask = task
print("Scheduling reset task to run in 2 seconds")
DispatchQueue.main.asyncAfter(deadline: .now() + resetDelay, execute: task) // Schedule reset after specified delay
}
}
.frame(width: 250, height: 250)
}
private func getSelectedSlice(value: Int) {
var cumulativeTotal = 0
_ = emojiUsageData.first { emojiRange in
cumulativeTotal += emojiRange.count
if value <= cumulativeTotal {
selectedSlice = emojiRange
selectedEmojiUsage = emojiRange
print("Selected slice: \(String(describing: selectedSlice))")
return true
}
return false
}
}
}
var emojiUsageData: [EmojiUsage] {
let groupedEntries = Dictionary(grouping: entries, by: { $0.emojiTab })
return groupedEntries.map { (key, value) in
EmojiUsage(emojiTab: key, count: value.count)
}
}
struct EmojiUsage: Identifiable {
var id = UUID()
var emojiTab: emojiTab
var count: Int
}
I would like to create a master-detail view inside the Settings screen for my Mac app. I am trying to use a NavigationSplitView nested inside the top-level Settings TabView. (Reduced code below).
However, the sidebar of the NavigationSplitView interferes with the TabView - the TabView appears to be in the detail of the NavigationSplitView even though it is its parent. I have seen others having the reverse issues with TabViews in NavigationSplitViews. Is this a known bug please?
struct SettingsView: View {
var body: some View {
TabView {
GeneralSettingsView()
.tabItem {
Label("General", systemImage: "gear") }
.tag(SettingsSection.general)
TypesView()
.tabItem {
Label("Types", systemImage: "star") }
.tag(SettingsSection.types)
}
.frame(width: 375, height: 150)
}
}
struct TypesView: View {
@Environment(\.modelContext) private var modelContext
@Query private var types: [ItemType]
@State private var selectedType: ItemType?
var body: some View {
NavigationSplitView {
List {
ForEach(types) { type in
NavigationLink {
TypeView(type: type)
} label: {
Text(type.name)
}
}
}
} detail: {
Text("Select a Type")
}
}
}
I am curious about the fact that the Text above the VStack is shown as a title to the view. I didn't know one could place Text above a VStack without another VStack. Here's the partial code showing the situation.
@State private var results = [Result]()
var body: some View {
Text("Songs of Al Di Meola on iTunes")
.font(.title3)
.fontWeight(.bold)
List(results, id: \.trackId) { item in
VStack(alignment: .leading) {
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
}
}
.task {
await loadData()
}
}
See picture for details.
(I'm using macOS 14.5 and Xcode 15.4)
I have a Swift Chart on macOS that needs to scroll horizontally. Simplified version:
Chart(dataPoints) { data in
LineMark(x: .value("X Axis", data.x),
y: .value("Y Axis", data.y))
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 10)
The above code works fine, except that it does not show scroll bars.
On a Mac with no trackpad, this means there's no mechanism to scroll the chart.
On my MacBook Pro with a trackpad, I can scroll the chart with a 2-finger swipe gesture, but there are no transient scroll bars to show the relative size of the visible part of the chart.
How do I add visible scroll bars to the chart so that I can scroll on Macs with no trackpad?
I am working on building Control widgets for our app and have noticed that openAppWhenRun doesn't seem to work for any ControlConfigurationIntent. When attaching the debugger to the widget extension in a sample project, I see the following error:
Unknown NSError The operation couldn’t be completed. (LNActionExecutorErrorDomain error 2018.)
This is reproducible as of Xcode 16.0 Beta 2 and Beta 3.
I have noted that using an OpenIntent, with a parameter called target that conforms to AppEnum seems to open the app properly, but if I use that workaround, adding any additional parameters to the OpenIntent seems to break things again.
Are others seeing this issue? I have feedback FB14357691.
Here's some sample code below to reproduce:
var body: some ControlWidgetConfiguration {
AppIntentControlConfiguration(kind: "Open Any Screen", intent: OpenAppScreenIntent.self) { template in
ControlWidgetButton(action: template) {
Label {
Text("Open App")
} icon: {
Image(systemName: "calendar")
}
}.tint(.red)
}
}
}
enum AppScreen: CaseIterable {
case calendar
case campus
case search
var title: String {
switch self {
case .calendar:
"Calendar"
case .campus:
"Campus"
case .search:
"Search"
}
}
}
struct OpenAppScreenIntent: ControlConfigurationIntent {
static var title: LocalizedStringResource = "Open app to a screen"
static var description = IntentDescription("Opens the app.")
/// The app should open regardless of what happens here
static let openAppWhenRun: Bool = true
@Parameter(title: "Screen", optionsProvider: OsuScreenOptionsProvider())
var screen: String?
struct OsuScreenOptionsProvider: DynamicOptionsProvider {
func results() async throws -> ItemCollection<String> {
var screenTitles: [String] = []
for screen in AppScreen.allCases {
async let title = screen.title
await screenTitles.append(title)
}
return ItemCollection {
ItemSection(items: screenTitles.map { IntentItem($0)})
}
}
func defaultResult() async -> String? {
return "Campus"
}
}
@MainActor
func perform() async throws -> some IntentResult {
#warning("The app should open regardless of what happens in this method, but it doesn't")
return .result()
}
}
I have a SwiftUI macOS hierarchical table with 'children' initializer argument. Everything is working fine but I'd like to have expander arrows in different column. TableColumn definition order doesn't seem to have any effect nor has the order of properties in the model. Is there a way how to define which column should contain expander arrows?
I have an extremely simple SwiftUI View with two buttons. One displays a sheet, the other one a popover.
struct ContentView: View {
@State private var showPopover = false
@State private var showSheet = false
var body: some View {
VStack {
Button("Popover") {
showPopover = true
}
.popover(isPresented: $showPopover, content: {
Text("Popover content")
})
Spacer()
Button("Sheet") {
showSheet = true
}
}
.padding()
.sheet(isPresented: $showSheet) {
Text("Lorem Ipsum")
}
}
}
On iPadOS, when the Popover is visible and the "Sheet" Button (that looks disabled but is not) is pressed, nothing happens and I get the following log:
Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x14508c600> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x14501fe00> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x14501fe00>) which is already presenting <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x140023e00>.
What's even worse is, the "Sheet" Button is now in a broken state and no longer works at all. Is there a way to fix this or am I right in assuming this is a bug?
Greetings,
Context
Let's imagine 2 possible root Views: House and Castle.
They can both present a Room view and this room view can show a Bed view.
Let's also assume, for the sake of the example, that I want to pass the "$sheet" (Identifiable enum) from either House or Castle - which are 2 different enums - all the way to Bed, so that Bed can dismiss back to rootView. So I use a Binding.
Visually, the UI flow is
House --> \
Room --> Bed
Castle --> /
In RoomView and BedView, I expected to receive either a HouseSheet or a CastleSheet. So by receiving one at init, I set to a constant nil the other. For example, if I open a RoomView (or BedView) from a HouseView, the code would be
init(parentHouseSheet: Binding<HouseSheet?>) {
self._parentHouseSheet = parentHouseSheet
self._parentCastleSheet = .constant(nil)
self.from = .house
}
init(parentCastleSheet: Binding<CastleSheet?>) {
self._parentHouseSheet = .constant(nil)
self._parentCastleSheet = parentCastleSheet
self.from = .castle
}
Problem
My problem now is that the .constant(nil) appears to change and triggers a refresh!
By using a simple Self._printChanges() I get the following when I navigate to a BedView (from a House origin!).
RoomView: _parentCastleSheet changed. // Why? It's a binded constant and I'm not even touching RoomView at all.
BedView: @self, @identity, _parentCastleSheet changed.
BedView: @self, _parentCastleSheet changed. // Same why?
Full code
// House & Castle
enum BuildingOrigin: String {
case house
case castle
}
enum HouseSheet: String, Hashable, Identifiable {
case room
var id: String { return self.rawValue }
}
enum CastleSheet: String, Hashable, Identifiable {
case room
var id: String { return self.rawValue }
}
// I only show HouseView for this example.
struct HouseView: View {
@State private var sheet: HouseSheet?
var body: some View {
let _ = Self._printChanges()
VStack {
Button("Open Room (Sheet)") {
self.sheet = .room
}
}
.padding()
.sheet(item: $sheet) { sheet in
switch sheet {
case .room: RoomView(houseSheet: $sheet)
}
}
}
}
// Room
enum RoomViewDestination: String, Hashable {
case bed
}
struct RoomView: View {
@State private var path = NavigationPath()
@Binding var parentHouseSheet: HouseSheet?
@Binding var parentCastleSheet: CastleSheet?
let from: BuildingOrigin
init(houseSheet: Binding<HouseSheet?>) {
self._parentHouseSheet = houseSheet
self._parentCastleSheet = .constant(nil)
self.from = .house
}
init(castleSheet: Binding<CastleSheet?>) {
self._parentHouseSheet = .constant(nil)
self._parentCastleSheet = castleSheet
self.from = .castle
}
var body: some View {
let _ = Self._printChanges()
NavigationStack(path: $path) {
List {
NavigationLink(value: RoomViewDestination.bed) {
Text("Open Bed (Navigation)")
}
}
.navigationDestination(for: RoomViewDestination.self) { destination in
switch destination {
case .bed:
switch self.from {
case .house: BedView(parentHouseSheet: $parentHouseSheet)
case .castle: BedView(parentCastleSheet: $parentCastleSheet)
}
}
}
}
}
}
// Bed
struct BedView: View {
@Binding var parentHouseSheet: HouseSheet?
@Binding var parentCastleSheet: CastleSheet?
let from: BuildingOrigin
init(parentHouseSheet: Binding<HouseSheet?>) {
self._parentHouseSheet = parentHouseSheet
self._parentCastleSheet = .constant(nil)
self.from = .house
}
init(parentCastleSheet: Binding<CastleSheet?>) {
self._parentHouseSheet = .constant(nil)
self._parentCastleSheet = parentCastleSheet
self.from = .castle
}
var body: some View {
let _ = Self._printChanges()
Text("The \(from.rawValue) has a nice bed.")
}
}
Hello, I'm a new programmer here, so this may be an error on my part, however I have tried my best to research the issue and believe I may have discover a bug. I have a set of tabItems inside of a TabView using a variable to track the selected tab called selectedIndex. I have added in a text view to watch the selected tab. This works correctly in the canvas and in a simulator, however whenever I build this on an iOS Device or for my Mac the selectedIndex does not change when selecting tabs like it does in the canvas and simulator. Instead it just stays at the default 0. Any assistance would be great. :-)
import SwiftUI
struct ContentView: View {
@State private var selectedIndex = 0
var body: some View {
Text("\(selectedIndex)")
TabView(selection: $selectedIndex) {
FilteredApplicantListView()
.tabItem { Label("Applicant Processor", systemImage: "person.3.sequence.fill") }
.tag(0)
QuickFinancials()
.tabItem { Label("Quick Financials", systemImage: "dollarsign.gauge.chart.leftthird.topthird.rightthird") }
.tag(1)
MoveInView()
.tabItem { Label("Move Ins", systemImage: "figure.walk.arrival") }
.tag(2)
MoveOutView()
.tabItem { Label("Move Outs", systemImage: "figure.walk.departure") }
.tag(3)
}
}
}
I am observing infinite loop of view creation, deletion, and recreation when I move my app to background and bring back to foreground. I am clueless what is causing this repeated invocation of view body, so I tried Self._printChanges() inside the view body . But all I get is the following in console.
ChildView: @ self , _dismiss changed. //<--- This is the problematic view
ParentView: unchanged. //<--- This is parent view
The issue has also been reported on Apple developer forums but no solution found.
What are other options to debug this issue?
I am trying to recreate the apple calendar in swiftui but I have issues creating scroll layout.
So I want to have:
When I scroll horizontally the hours on the side have to stay fixed but the header with the day number day String and the full day events have to scroll
When I scroll vertically the hours on the side have to move but the header don't
For the moment I have :
HStack(alignment: .top, spacing:0) {
// The hours on the side
LateralHours(height: geometry.size.height * 24/10) . offset(y: -offset)
ScrollViewReader { proxy in
ScrollView (.horizontal, showsIndicators: false) {
VStack {
// The day number and the full day events
Header(width: width)
ScrollView {
LazyHStack (spacing: 0) {
ForEach($loadedDays, id: \.self) { day in
// The day grid with the events
DayContentView(
cellHeight: geometry.size.height / 10,
width: width,
selectedEvent: $selectedEvent,
day: day,
store: $store,
modifiedEvent: $modifiedEvent
)
}
}
.frame(height: 24*geometry.size.height / 10)
// This code block is used to track the position of the scrollview position
.background( GeometryReader {
Color.clear.preference(key: ScrollOffsetPreferenceKey.self,
value: -$0.frame(in: .named("scroll")).origin.y)
})
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
offset = value
}
}
.coordinateSpace(name: "scroll")
}
}
.scrollTargetBehavior(.viewAligned)
.defaultScrollAnchor(.leading)
}
.scrollPosition(id: $position)
.frame(alignment: .topLeading)
}
So the hours on the side are in none of the scrollViews they are only modified through the .offset with the vertical scrollView position. My problem is that the .offset seems to be kind of slow and my app is slowed down is there a better modifed than .offset or do you know a more efficient way to do this ?
I'm trying to add a ControlWidget to my WidgetBundle like this:
struct MyWidgets: WidgetBundle {
var body: some Widget {
if #available(iOSApplicationExtension 18.0, *) {
LauncherControl()
}
MyLiveActivityWidget()
HomeScreenWidget()
LockScreenWidget()
}
This works exactly as expected on iOS 18. However on iOS 17 my app seems to have no widgets at all.
The workaround described here (https://www.avanderlee.com/swiftui/variable-widgetbundle-configuration/) does not work either since WidgetBundleBuilder.buildBlock does not accept ControlWidget as an argument.
What is the correct way to include a Control widget conditionally on iOS 18?
I have a problem in dealing with RTL language (Arabic) in SwiftUI with List and Searchable flow, also when long pressed on the search bar to prompt the options menu it is also flipped.
I added the environment parameter to support right to left layout direction for the list, but when I clicks on the search bar to do the search flow the list is flipped again, Thanks in advance.
var body: some View {
NavigationStack {
List {
Section {
Text("مرحبا")
}
}
.environment(\.layoutDirection, .rightToLeft)
.flipsForRightToLeftLayoutDirection(true)
.navigationTitle(" جديد")
.toolbar(content: {
Button("اغلاق") {
self.dismiss()
}
})
}
.tint(Color.blue)
.searchable(text: self.$searchText, placement: .navigationBarDrawer(displayMode: .always))
.interactiveDismissDisabled()
}
I recently released my first app. I noticed that on my login screen, the keyboard on both my email TextField and my password SecureField was showing an autofill for passwords. I was setting their keyboard type, but looking into this, I noticed that there is a text content type that I should also be setting. Upon setting this, nothing changed. The only thing that does seem to change this functionality is by hiding the SecureField.
I ran into a problem when I tried adding a dismiss button to the toolbar of these keyboards. I was able to find people talking about this being an iOS 17 bug. However, I have not found anyone talking about this autofill issue. Is this also a bug in iOS 17? Back when I was creating the login page for my app, I believe it was still iOS 16, and that would explain why I didn't notice it before.
One more note I just ran across while testing. If I add a second identical TextField, it won't have this autofill.
Here is what I have discovered:
When I have window A pushes window B, and then B's onAppear dismisses A by its id. In this case, A will not appear if B later dismisses itself unless B calls open/pushWindow(id: A)
However, if I then open an immersive space by A and dismiss it, there will be several B appearing depending on how many times the process I mentioned above was repeated
It does make no sense using onAppear to dismiss A while we later want to reuse it, but is this feature expected?