Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.

All subtopics

Post

Replies

Boosts

Views

Activity

Large Title Navigation Bar Problems with Two Views
As has been posted a number of times, the large title navigation bar only collapses on scrolling for the UIView listed first in an .xib or storyboard file. In my case, I have an enclosing view containing 2 Views, a TableView and a CollectionView in an .XIB file. This is connected to a UIKit class. Tapping a button switches between the two views. If the TableView is the first view listed, the navigationbar collapses with scrolling but with the CollectionView listed second, the large title navigation bar doesn't collapse with scrolling. Alternatively, if the CollectionView is the first view listed, the navigation bar collapses with scrolling but with the TableView listed second, the large title navigation bar doesn't collapse with scrolling. I have not been able to figure out a way to enable the collapsable large title navigation bar to work in such a scenario as mine for both views within a container view. Is there a way to do this? I have tested this capability through iOS 17.
2
0
210
1w
SwiftUI Previews not working with Firebase in Xcode 16.0 beta
The SwiftUI previews have been working fine in Xcode 16.0 beta, but ever since I added Firebase into my app, I've been getting error with previews. CrashReportError: IshaanCord crashed IshaanCord crashed. Check ~/Library/Logs/DiagnosticReports for crash logs from your application. Process: IshaanCord[5651] Date/Time: 2024-07-04 19:29:51 +0000 Log File: <none> "Cannot preview in this file" Does anyone know how to fix this? Thank you.
2
0
233
1w
Is it possible to use a list-style UICollectionView but have multiple columns?
I'm working on a UICollectionView that has a custom compositional layout with multiple columns. I wanted to add swipe actions to the cells (specifically, a delete action). I knew this was possible because of the existence of trailingSwipeActionsConfigurationProvider but I didn't realize that this is only for list layouts, created with UICollectionViewCompositionalLayout.list. But if I use a list layout, I don't think I have any opportunity to add multiple columns. Do I really have to choose between multiple columns and trailing swipe actions? Or is there some way to get both?
0
0
135
1w
Vertical ScrollView Height and Paging Offset Issues in SwiftUI
Hi everyone, I'm currently working on an iOS app using SwiftUI, and I'm facing an issue with a vertical ScrollView. My goal is to have the ScrollView take up all the safe area space plus the top inset (with the bottom inset being an ultra-thin material) and enable paging behavior. However, I'm encountering two problems: The initial height of the ScrollView is too high (dragging the view (even without changing the page) adjusts the size). The paging offset of the ScrollView is incorrect (page views are not aligned). I’ve tried many things and combinations in desperation, including padding, safeAreaPadding, contentMargins, frame, fixedSize, containerRelativeFrame with callback, custom layout, and others, but nothing seems to work. If by any chance someone can help me find a solution, I’d greatly appreciate it. I suspect there are issues with the height defined by the ScrollView when some safe areas are ignored, leading to strange behavior when trying to set the height. It's challenging to explain everything in a simple post, so if someone from the SwiftUI team could look into this potential bug, that would be incredibly helpful. Thank you! import SwiftUI struct ScrollViewIssue: View { @State private var items: [(String, Color)] = [ ("One", .blue), ("Two", .green), ("Three", .red), ("Four", .purple) ] var body: some View { ZStack(alignment: .top) { ScrollView(.vertical) { VStack(spacing: 0) { ForEach(items, id: \.0) { item in ItemView(text: item.0, color: item.1) .containerRelativeFrame([.horizontal, .vertical]) } } } .printViewSize(id: "ScrollView") .scrollTargetBehavior(.paging) .ignoresSafeArea(edges: .top) VStack { Text("Title") .foregroundStyle(Color.white) .padding() .frame(maxWidth: .infinity) .background(Color.black.opacity(0.2)) Spacer() } } .printViewSize(id: "ZStack") .safeAreaInset(edge: .bottom) { Rectangle() .frame(width: .infinity, height: 0) .overlay(.ultraThinMaterial) } } } struct ItemView: View { let text: String let color: Color var body: some View { ZStack { RoundedRectangle(cornerRadius: 20) .fill(color.opacity(0.2)) .overlay( color, in: RoundedRectangle(cornerRadius: 20) .inset(by: 10 / 2) .stroke(lineWidth: 10) ) Text(text) } .printViewSize(id: "item") } } struct ViewSizeKey: PreferenceKey { static var defaultValue = CGSize() static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() } } extension View { func printViewSize(id: String) -> some View { background( GeometryReader { proxy in Color.clear .preference(key: ViewSizeKey.self, value: proxy.size) } .onPreferenceChange(ViewSizeKey.self) { value in print("\(id) size:\(value)") } ) } } #Preview { ScrollViewIssue() }
1
0
153
1w
NavigationStack with NavigationPath triggers multiple init/deinit of views in stack
Hi all 👋 I've stumbled upon what I think is a bug in SwiftUI, but there might be an explanation for it, which I cannot to figure out. So here's the "bug". Perhaps any of you know it. In short I found out that .navigationDestination(for: ...) runs multiple times when you abstract the logic away from the scope of the closure, into a function that should return the desired view from the type of destination. But if you keep the logic inside the scope of the closure it only runs once per destination. The longer explanation I've setup a small example of a setup, which I'm planning on building an entire app on. Basically it's MVVVM with a coordinator pattern. I've ditched the coordinator in this example to minimize code, but the router is present and is key to how the app should work: navigation happens from a central place at the root of the app. You should be able to copy all the code into a single file and run the app, to see what happens. It's a simple app with a rootview that can navigate to either "Cookies" or "Milk". You can set an amount (to have some sort of state, that you can test is still present, when navigating back to the view) + navigate on to another view. The bug in question happens in the RootView: .navigationDestination(for: Destination.self) { destination in let _ = print("||| Destination: \(destination.rawValue)") // Method #1 // anyViewFor(destination: destination) // Method #2 // itemViewFor(destination: destination) // Method #3 // cookieViewFor(destination: destination) // Method #4 switch destination { case .cookie: let vm = CookieViewModel() CookieView(vm: vm) case .milk: let vm = MilkViewModel() MilkView(vm: vm) } } If you comment out Method 4 and comment in any of Method 1, 2, 3 you will see the issue in the console. Say you navigate from RootView -> CookieView (Set 2 cookies) -> MilkView (Set 1 glass of milk) -> CookieView, and then navigate back to RootView. Method 4 produces the following prints: ||| Router: add to navPath: 1 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 960, num: 0 ||| Router: add to navPath: 2 ||| Destination: Milk 🥛 ||| Init ☀️: MilkViewModel, id: 254, num: 0 ||| Router: add to navPath: 3 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 348, num: 0 ||| Router: remove from navPath: 2 ||| Deinit 🔥: CookieViewModel, id: 348, num: 0 ||| Router: remove from navPath: 1 ||| Deinit 🔥: MilkViewModel, id: 254, num: 1 ||| Router: remove from navPath: 0 ||| Deinit 🔥: CookieViewModel, id: 960, num: 2 This makes sense. The desired Views+ViewModels (we only have prints from VMs) are init'ed and deinit'ed. Method 1, 2, 3 produces the following prints: ||| Router: add to navPath: 1 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 893, num: 0 ||| Router: add to navPath: 2 ||| Destination: Milk 🥛 ||| Init ☀️: MilkViewModel, id: 747, num: 0 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 384, num: 0 ||| Router: add to navPath: 3 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 578, num: 0 ||| Destination: Milk 🥛 ||| Init ☀️: MilkViewModel, id: 409, num: 0 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 468, num: 0 ||| Deinit 🔥: CookieViewModel, id: 384, num: 0 ||| Router: remove from navPath: 2 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 859, num: 0 ||| Deinit 🔥: CookieViewModel, id: 468, num: 0 ||| Destination: Milk 🥛 ||| Init ☀️: MilkViewModel, id: 250, num: 0 ||| Deinit 🔥: MilkViewModel, id: 409, num: 0 ||| Deinit 🔥: CookieViewModel, id: 578, num: 0 ||| Router: remove from navPath: 1 ||| Destination: Cookie 🍪 ||| Init ☀️: CookieViewModel, id: 211, num: 0 ||| Deinit 🔥: CookieViewModel, id: 859, num: 0 ||| Deinit 🔥: MilkViewModel, id: 250, num: 0 ||| Deinit 🔥: MilkViewModel, id: 747, num: 1 ||| Router: remove from navPath: 0 ||| Deinit 🔥: CookieViewModel, id: 211, num: 0 ||| Deinit 🔥: CookieViewModel, id: 893, num: 2 This is where it gets weird. When it's a function returning the desired view for the given destination to .navigationDestination(for: ...) then it appears to be running n * number of items in the NavigationPath-object. You can see on the num: x in the deinit-prints, that instances are inited and deinted that we're never in touch with. Do any of you have a qualified guess why this is happening? To me it seems like a bug. I'll provide the code for mig example in a comment...
2
0
203
1w
swiftUI animation And ID attribute
struct ContentIDView: View { @State var testID = UUID() @State var b = 0 var body: some View { VStack { Image("video_pic_diy_000") .resizable() .scaledToFit() .contentTransition(.interpolate) .id(testID) .offset(x:CGFloat(b>0 ? 100*b:0)) .animation(.easeInOut(duration: 0.01).delay(0.2), value: b) .onAppear(perform: { print("\(testID)") }) Text("\(testID)") Spacer() Button("Move") { withAnimation(Animation.easeInOut(duration: 0.2)) { b = b+1 testID = UUID() } } } } } In the above code, there is an image and a button, and each time the button is clicked, the image moves 100* the distance of the number of clicks in the x direction from 0, but now each time it moves 100 from where the last move stopped instead of 0. What causes this? In addition, I set the id for Image. When the id changes every time the button is clicked, shouldn't the initial state be restored? Since it is the initial state, why didn't it move from 0?
0
0
67
1w
SwiftUI, numeric keypad, and missing keyboard toolbar
Have a SwiftUI TextField with .numberPad keypad. Want to allow the user to make their changes, then hit 'Done' or 'Cancel' to dismiss the keyboard (there are other choices on the page) before hitting 'Submit'. The view containing the field is brought up as a modal, using .FullScreenCover. Here's the code snippet: @State var currentBid: Float = 0 @FocusState var isInputActive: Bool ... TextField("Enter Amount", text: Binding( get: { String(Int(currentBid)) }, set: { currentBid = Float(Int($0) ?? 0) }) ).keyboardType(.numberPad) .focused($isInputActive) .toolbar(content: { ToolbarItemGroup(placement: .keyboard, content: { Button("Cancel") { print("CANCELLED") isInputActive = false } Spacer() Button("Done") { print("DONE") isInputActive = false } }) }) When you tap the text field, the numeric keyboard comes up, but the button bar does not show. This is on iOS 17.6. tested on-device. The alternative is to use a regular alphanumeric keyboard with an "ENTER" button. But the field is to enter a number and I have to either filter the value or discard it in case they enter bad data. I checked online, and others indicated wrapping the whole View inside a NavigationStack might be needed. Tried it and it didn't work. Any suggestions? Thanks!
2
0
151
1w
Issue with observing SwiftData model and UndoManager
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?
0
0
119
1w
Markdown Parsing Adding Unexpected Paragraph Attributes
I am looking to support rendering markdown text while adding corporate branding styles (typography and colors for headers specified by the design team, etc). All was going well until I encountered Ordered List and began seeing some unexpected behavior. Lets take for example some sample markdown as shown below. let sampleMarkdown: String = """ # Ordered List Test This is some sample markdown used to test ordered lists. 1. Line item one of a sample markdown ordered list. 2. This is the second line item of a sample markdown ordered list. 3. And finally, the third line item of a sample markdown ordered list. """ Now, let's say for each item in the ordered list, I would like to assign a random foreground color to the text. This is straightforward as I can loop over the runs of an attributed string and should I encounter an intentType.kind of .orderedList I can apply the random foreground color as so with clode along the lines of such: for run in pulseAttributedString.runs { if let presentationIntent = run.presentationIntent { for intentType in presentationIntent.components { switch intentType.kind { case .orderedList: let colors: [UIColor] = [.green, .systemPink, ...] container[AttributeScopes.UIKitAttributes.ForegroundColorAttribute.self] = colors.randomElement() } } } } Upon setting the random foreground color to the attribute container, and running in the Simulator you can see the following output. This is very much close to what I'd expect although frustratingly the parser seemed to have stripped out the 1. 2. etc. I'm not sure what the reasoning for that is, or why the .orderedList does not have an associated value representing the index of the line item in the list, similar to how case header(level: Int) includes an associated value for header level. Upon closer inspection, I also see that there is another presentation intent kind of case listItem(ordinal: Int). When I add a case in my switch statement for listItem I see it too is included within the presentation intent components. With that said, I need to restore the 1., 2. etc, would I use case listItem(ordinal: Int) or .orderedList. And what would be the difference between the two? Would someone be able to explain that to me since the documentation for listemItem seems to make no effort to do so. This now leads me to the next issue I was encountering where if I then add emphasis to some words within the ordered list, essentially the same sample markdown above but with emphais on the word one and second: let sampleMarkdown: String = """ # Ordered List Test This is some sample markdown used to test ordered lists. 1. Line item *one* of a sample markdown ordered list. 2. This is the *second* line item of a sample markdown ordered list. 3. And finally, the third line item of a sample markdown ordered list. """ I now encounter the unexpected behavior where the words that received the emphasis also have a paragraph attribute associated with them, as shown in the screenshot below. I can log the presentationIntent and confirm that in my console that there are now presentation intent components for listItem, orderedList and now a paragraph. [paragraph (id 5), listItem 1 (id 4), orderedList (id 3)] So now it appears that in addition to having to restore and insert the 1., 2. etc, to the line items, but now I also need to make sure I strip out the added paragraph intent that gets added to a piece of text has emphaisis within an orderedList line item? This doesn't seem to make sense to me, am I doing something wrong?
0
0
117
1w
XC Test - Xamarin- Mobile Automation - Tiles missing on list view
Unable to see the first element in a Xamarin forms List view while running the automated UI iOS tests using xctest Element with property in Xamarin forms "ListViewCachingStrategy.RetainElement" , the first element is missing from the list view while running the tests in iOS . Inbetween the tests if we remove the WDA and tried the same scenario, it works as expected. The first element was displayed when working with xamarin forms (v4.7.0.1351) and it is observed with only the current version. Manually trying the same scenario does not have any issues , Elements are displayed as expected, issue is observed only through automation
2
0
132
1w
NavigationDestination, NavigationLink with List and ForEach Does Not Work
Dear community, I am a new developer and I am building a view (called Root) that has a list of rows where clicking each row navigates to a completely different view. I have a CaseIteratable enum and I list each enum type using ForEach and each enum case navigates to a different view using NavigationLink and NavigationDestination. But the problem is that clicking any of the rows for the first time navigates correctly to the corresponding view. But when I go back to the root view and chose another row, it navigates me to a blank view for less than a sec and automatically navigates back to the root view. Below is the code for reference. I would really appreciate some help and advice here. Thank you very much! struct RootViewNavigationStack: View { @AppStorage("items") private var items = Item.allCases @State private var enableMove = false @State private var rootStackPath: NavigationPath = .init() var body: some View { NavigationStack(path: $rootStackPath) { VStack { List { ForEach(items) { item in HStack { NavigationLink(value: item) { ListCell( icon: item.icon, title: item.title) } .disabled(enableMove) if enableMove { withAnimation { Image(systemName: "line.3.horizontal") .foregroundStyle(.secondary) } } } } .onMove(perform: enableMove == true ? moveItems : nil) } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { enableMove.toggle() } label: { if enableMove { Text("Done") .bold() } else { Text("Edit") } } } } .navigationDestination(for: Item.self) { item in item.destinationView } .navigationTitle("Root") } } } and this is the Item enum for more info Just kindly ignore the var iconName since it doesnt represent any actual SF Symbol name enum Item: Identifiable, Codable, Hashable, CaseIterable { case view1 case view2 case view3 case view4 case view5 var id: Item { self } } extension Item { var title: String { switch self { case .view1: "View1" case .view2: "View2" case .view3: "View3" case .view4: "View4" case .view5: "View5" } } var iconName: String { switch self { case .view1: "View1" case .view2: "View2" case .view3: "View3" case .view4: "View4" case .view5: "View5" } } var icon: Image { Image(systemName: self.iconName) } @ViewBuilder var destinationView: some View { switch self { case .view1: CarView() case .view2: HouseView() case .view3: MusicView() case .view4: ShoesView() case .view5: BooksView() } } } Once again, would really appreciate someone to help and many thanks 🙏!
4
0
198
1w
SwiftUI.List.scrollDismissesKeyboard(.immediately) causes scroll glitch on List
When the scrollDismissesKeyboard(.immediately) is used on a SwiftUI.List, the scroll is very glitchy when keyboard is open both on simulator and on device. Glitch is visible when content height is smaller than screen height. A sample code to reproduce the issue: struct ContentView: View { @State private var searchText = "" var body: some View { NavigationStack { List { ForEach(0..<1) { index in Rectangle() .fill(Color.blue) .frame(height: 100) .cornerRadius(10) .padding(.vertical, 5) .padding(.horizontal) } } .searchable(text: $searchText) .scrollDismissesKeyboard(.immediately) } } } Steps to Reproduce: Run the code above. Tap on the search bar so the keyboard appears. Scroll the list down. Glitch should be visible. Along with the glitch, there are many warnings being thrown when I interact with the search bar: -[RTIInputSystemClient remoteTextInputSessionWithID:performInputOperation:] perform input operation requires a valid sessionID. inputModality = Keyboard, inputOperation = <null selector>, customInfoType = UIEmojiSearchOperations -[UIApplication getKeyboardDevicePropertiesForSenderID:shouldUpdate:usingSyntheticEvent:], failed to fetch device property for senderID (***) use primary keyboard info instead. This API seems very unstable. Is this a known issue? If so, are there plans on fixing it?
2
1
183
2w
Spatial Event
Hello. I am building an AVP app with a C++ engine in Metal. From Swift, I get a spatial event from the layerRenderer, and I want to send the event data to my engine. But I have a problem: I need the event ID value (SpatialEventCollection.Event.ID), which I can see in the debugger (it has a value of 1 or 2 depending on the hand), but I need that value as an Int to pass it to the engine. Any ideas on how to do this? Thanks.
0
0
113
2w
iOS 18 Beta 2 XCTest: Unable to record and play contacts permission system dialog
Unable to record and play the new contacts permission system dialog. App: https://developer.apple.com/documentation/contacts/accessing-a-person-s-contact-data-using-contacts-and-contactsui func handleContactsAccessAlert() { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let allowButton = springboard.buttons["Allow Full Access"] allowButton.tap() let access6ContactsButton = springboard.alerts["Allow full access to 6 contacts?"].scrollViews.otherElements.buttons["Allow"] access6ContactsButton.tap() } func handleContactsPermissionAlert() { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let allowButton = springboard.buttons["Continue"] if allowButton.exists { allowButton.tap() sleep(5) handleContactsAccessAlert() } } func testContactPermissions() { let app = XCUIApplication() app.launch() app.buttons["Request Access"].tap() sleep(5) handleContactsPermissionAlert() sleep(5) app.collectionViews/*@START_MENU_TOKEN@*/.staticTexts["Kate Bell"]/*[[".cells.staticTexts[\"Kate Bell\"]",".staticTexts[\"Kate Bell\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() }
0
0
122
2w