Provide views, controls, and layout structures for declaring your app's user interface using SwiftUI.

SwiftUI Documentation

Posts under SwiftUI tag

2,337 Posts
Sort by:
Post not yet marked as solved
1 Replies
22 Views
Hi everyone, I'm trying to make use of a background actor in my SwiftUI project. Inserting data works with the ModelContainer's mainContext. Another context in a ModelActor, however, fails to write into the same database. I verify the results by opening the SQLite file on the file system. While the mainContext.insert call does indeed insert a row into the table, the ModelActor's context fails to do so. There is no error or message received in the ModelActor. The property autosaveEnabled is set to true. I wrote a sample project to reproduce the issue in isolation. It consists of a single source file that introduces the Model ToDo, the ToDoView, initializes the ModelContainer and ModelActor. Please find the source code below. Is there any mistake in my approach? import SwiftUI import SwiftData @Model final class ToDo { let title: String init(title: String) { self.title = title } } @main struct testSwiftDataApp: App { @State var modelContainer: ModelContainer @State var backgroundData: BackgroundDataActor init() { let modelContainer: ModelContainer = try! ModelContainer(for: ToDo.self) self.modelContainer = modelContainer self.backgroundData = BackgroundDataActor( modelContainer: modelContainer ) } var body: some Scene { WindowGroup { ToDoView(backgroundData: self.backgroundData) .modelContainer(modelContainer) } } } struct ToDoView: View { @Environment(\.modelContext) var modelContext @Query var todos: [ToDo] let backgroundData: BackgroundDataActor var modelContainer: ModelContainer { self.modelContext.container } var body: some View { VStack { Text("Add ToDo") TextField( "", text: .constant( "URL to database: " + "\(self.modelContainer.configurations.first!.url)" ) ) // This action will be invoked on the ModelActor's context Button { Task { let todo = ToDo(title: "Step 1") await self.backgroundData.store( id: todo.persistentModelID ) } } label: { Text("Create ToDo in background") } // This action will be invoked on the mainContext Button { Task { let todo = ToDo(title: "Step 2") self.modelContainer.mainContext.insert(todo) } } label: { Text("Create ToDo in foreground") } // Show the query results VStack { Text("Available ToDos") ForEach(self.todos) { Text($0.title) } } } } } @ModelActor actor BackgroundDataActor: ModelActor { func store(id: PersistentIdentifier) { print("Trying to save") print("Is auto save enabled: \(self.modelContext.autosaveEnabled)") if let dbo = self[id, as: ToDo.self] { self.modelContext.insert(dbo) try! self.modelContext.save() print("Saved into database") } } }
Posted
by anmarb.
Last updated
.
Post not yet marked as solved
1 Replies
59 Views
I prepare an app to migrate from ObservableObject to Observable, from EnvironmentObject to Environment(MyClass.self) and so so forth. That works OK, very simple. But, that forces to run on macOS 14 or more recent. So I would like to have it conditionally, such as: if macOS 14 available @Environment(ActiveConfiguration.self) var activeConfiguration: ActiveConfiguration otherwise @EnvironmentObject var activeConfiguration: ActiveConfiguration The same for the class declaration: if macOS 14 available @Observable class ActiveConfiguration { var config = Configuration() } otherwise class ActiveConfiguration : ObservableObject { @Published var config = Configuration() } Is there a way to achieve this (I understand it is not possible through extensions of Macros, as we can do for modifiers) ? Could I define 2 classes for ActiveConfiguration, but then what about @Environment ?
Posted
by Claude31.
Last updated
.
Post not yet marked as solved
2 Replies
74 Views
On my shop and content views of my app, I have a shopping cart SF symbol that I've modified with a conditional to show the number of items in the cart if the number of items is above zero. However, whenever I change tabs and back again, that icon disappears even though there should be an item in the cart. I have a video of the error, but I have no idea how to post it. Here is some of the code, let me know if you need to see more of it: CartManager.swift import Foundation import SwiftUI @Observable class CartManager { /*private(set)*/ var products: [Product] = [] private(set) var total: Int = 0 private(set) var numberofproducts: Int = 0 func count() -> Int { numberofproducts = products.count return numberofproducts } func addToCart(product: Product) { products.append(product) total += product.price numberofproducts = products.count } func removeFromCart(product: Product) { products = products.filter { $0.id != product.id } total -= product.price numberofproducts = products.count } } ShopPage.swift import SwiftUI struct ShopPage: View { @Environment(CartManager.self) private var cartManager var columns = [GridItem(.adaptive(minimum: 135), spacing: 0)] @State private var searchText = "" let items = ["LazyHeadphoneBean", "ProperBean", "BabyBean", "RoyalBean", "SpringBean", "beanbunny", "CapBean"] var filteredItems: [Bean] { guard searchText.isEmpty else { return beans } return beans.filter { $0.imageName.localizedCaseInsensitiveContains(searchText) } } var body: some View { NavigationStack { ZStack(alignment: .top) { Color.white .ignoresSafeArea(edges: .all) VStack { AppBar() .environment(cartManager) ScrollView() { LazyVGrid(columns: columns, spacing: 20) { ForEach(productList, id: \.id) { product in NavigationLink { beanDetail(product: product) .environment(cartManager) } label: { ProductCardView(product: product) .environment(cartManager) } } } } } .navigationBarDrawer(displayMode: .always)) } } .environment(cartManager) } var searchResults: [String] { if searchText.isEmpty { return items } else { return items.filter { $0.contains(searchText)} } } } #Preview { ShopPage() .environment(CartManager()) } struct AppBar: View { @Environment(CartManager.self) private var cartManager var body: some View { NavigationStack { VStack (alignment: .leading){ HStack { Spacer() NavigationLink(destination: CartView() .environment(cartManager) ) { CartButton(numberOfProducts: cartManager.products.count) } } Text("Shop for Beans") .font(.largeTitle .bold()) } } .padding() .environment(CartManager()) } } CartButton.swift import SwiftUI struct CartButton: View { var numberOfProducts: Int var body: some View { ZStack(alignment: .topTrailing) { Image(systemName: "cart.fill") .foregroundStyle(.black) .padding(5) if numberOfProducts > 0 { Text("\(numberOfProducts)") .font(.caption2).bold() .foregroundStyle(.white) .frame(width: 15, height: 15) .background(Color(hue: 1.0, saturation: 0.89, brightness: 0.835)) .clipShape(RoundedRectangle(cornerRadius: 50)) } } } } #Preview { CartButton(/*numberOfProducts: 1*/numberOfProducts: 1) }
Posted
by KittyCat.
Last updated
.
Post not yet marked as solved
0 Replies
32 Views
Hello, I have been working on SwiftData since a month now and i found very weird that every time i update a data inside a SwiftData model my app became very slow. I used the instrument if something was wrong, and i see memory increasing without releasing. My app have a list with check and unchecked elements (screeshot below). I just press check and unchecked 15 times and my memory start from 149mb to 361mb (screenshots below). For the code i have this model. final class Milestone: Identifiable { // ********************************************************************* // MARK: - Properties @Transient var id = UUID() @Attribute(.unique) var uuid: UUID var text: String var goalId: UUID var isFinish: Bool var createdAt: Date var updatedAt: Date var goal: Goal? init(from todoTaskResponse: TodoTaskResponse) { self.uuid = todoTaskResponse.id self.text = todoTaskResponse.text self.goalId = todoTaskResponse.goalId self.isFinish = todoTaskResponse.isFinish self.createdAt = todoTaskResponse.createdAt self.updatedAt = todoTaskResponse.updatedAt } init(uuid: UUID, text: String, goalId: UUID, isFinish: Bool, createdAt: Date, updatedAt: Date, goal: Goal? = nil) { self.uuid = uuid self.text = text self.goalId = goalId self.isFinish = isFinish self.createdAt = createdAt self.updatedAt = updatedAt self.goal = goal } } For the list i have var milestonesView: some View { ForEach(milestones) { milestone in MilestoneRowView(task: milestone) { milestone.isFinish.toggle() } .listRowBackground(Color.backgroundComponent) } .onDelete(perform: deleteMilestone) } And this is the cell struct MilestoneRowView: View { // ********************************************************************* // MARK: - Properties var task: Milestone var action: () -> Void init(task: Milestone, action: @escaping () -> Void) { self.task = task self.action = action } var body: some View { Button { action() } label: { HStack(alignment: .center, spacing: 8) { Image(systemName: task.isFinish ? "checkmark.circle.fill" : "circle") .font(.title2) .padding(3) .contentShape(.rect) .foregroundStyle(task.isFinish ? .gray : .primary) .contentTransition(.symbolEffect(.replace)) Text(task.text) .strikethrough(task.isFinish) .foregroundStyle(task.isFinish ? .gray : .primary) } .foregroundStyle(Color.backgroundComponent) } .animation(.snappy, value: task.isFinish) } } Does someone have a idea? Thanks
Posted
by agalpin.
Last updated
.
Post not yet marked as solved
0 Replies
31 Views
Hello. I have the following question. I call the nextBatch() method on MusicItemCollection to get the next collection of artist albums. Here is my method: func allAlbums2(artist: Artist) async throws -> [Album] { var allAlbums: [Album] = [] artist.albums?.forEach { allAlbums.append($0) } guard let albums = artist.albums else { return [] } var albumsCollection = albums while albumsCollection.hasNextBatch { let response = try await albumsCollection.nextBatch() if let response { albumsCollection = response var albums = [Album]() albumsCollection.forEach({ albums.append($0)}) allAlbums.append(contentsOf: albums) } } return allAlbums } The problem is as follows. Sometimes it happens that some albums not in nextBatch are not returned (I noticed one, I didn't check the others). This happens once every 50-100 requests. The number of batches is returned the same when the album is skipped. The same albums are returned in the batch where the album was missed. I have a question, do I have a problem with multi-threading or something else or is it a problem in the API. The file contains prints of the returned batches. One with a missing album, the other without a missing one. There are about 3000 lines in the file. I will be grateful for a hint or an answer. Thank you! Here link to file: https://files.fm/u/nj4r5wgyg3
Posted
by 20a.
Last updated
.
Post not yet marked as solved
1 Replies
29 Views
Hello Apple Developer Forum, I'm reaching out because I've encountered an issue with my app's implementation involving the ScrollView and the digitalCrown, and I'm hoping to find some guidance or assistance from the community. Currently, I'm developing an app where users can navigate through dates using the digitalCrown to change the "$scrollAmount", which then dynamically updates the ScrollView with events corresponding to that date. However, I've run into a problem where the ScrollView is being inadvertently scrolled by the digitalCrown every time it's initiated. Ideally, I would like to disable the digitalCrown's interaction with the ScrollView altogether, so that the ScrollView is only scrolled using touch inputs and not by the digitalCrown. I've tried several approaches to achieve this, but haven't had much success so far. Could anyone please provide some guidance or suggestions on how I can effectively disable the digitalCrown's interaction with the ScrollView while still allowing touch-based scrolling? Any help or insights into this matter would be greatly appreciated. Thank you very much in advance for your time and assistance. Best regards, Example code: var body: some View { NavigationView { VStack { HStack{ Text("\(formattedDate(for: scrollAmount, format: lineOne))") .onTapGesture { scrollAmount = 0.0 } } ScrollView{ ForEach(viewModel.events, id: \.event) { viewModelItem in let event = viewModelItem.event VStack { HStack { Text(event.title) } } } } .scrollDisabled(true) } } .focusable(isFocused) .digitalCrownRotation( detent: $scrollAmount, from: -365.0, through: 365.0, by: 1.0, sensitivity: .low, isContinuous: false, isHapticFeedbackEnabled: true) .onChange(of: scrollAmount) { let roundedValue = round(scrollAmount) scrollAmount = roundedValue viewModel.fetchEvents(for: scrollAmount) } } }
Posted
by wurx.
Last updated
.
Post not yet marked as solved
0 Replies
23 Views
Hi, I'm an indie developer trying to make a 2D prototype of a simple game where I have to drag and drop items from one box to another. I have so far implemented a working prototype with the .draggable (https://developer.apple.com/documentation/swiftui/view/draggable(_:)) function which works well in the simulator, but as soon as I use my vision pro, the finger pinch action doesn't register half the time. I can only select the object around 30% of the time. My code is as follows. DiskView(size: diskSize, rod: rodIndex) .draggable(DiskView(size: diskSize, rod: rodIndex)) .hoverEffect() I have also registered DiskView as a UTType and have it able to be transferred around. The business logic works, just the pinch gesture does not work half the time.
Posted Last updated
.
Post not yet marked as solved
2 Replies
1.1k Views
Since Xcode 15 beta 5, making a class with the @Observable macro no longer requires all properties to have an initialization value, as seen in the video. Just put an init that collects the properties and everything works correctly. @Observable final class Score: Identifiable { let id: Int var title: String var composer: String var year: Int var length: Int var cover: String var tracks: [String] init(id: Int, title: String, composer: String, year: Int, length: Int, cover: String, tracks: [String]) { self.id = id self.title = title self.composer = composer self.year = year self.length = length self.cover = cover self.tracks = tracks } } But there is a problem: the @Observable macro makes each property to integrate the @ObservationTracked macro that seems not to conform the types to Equatable, and in addition, to Hashable. Obviously, being a feature of each property, it is not useful to conform the class in a forced way with the static func == or with the hash(into:Hasher) function that conforms both protocols. That any class we want to be @Observable does not conform to Hashable, prevents any instance with the new pattern to be usable within a NavigationStack using the data driven navigation bindings and the navigationDestination(for:) modifier. I understand that no one has found a solution to this. If you have found it it would be great if you could share it but mainly I am making this post to invoke the mighty developers at Apple to fix this bug. Thank you very much. P.S. - I also posted a Feedback (FB12535713), but no one replies. At least that I see.
Posted
by jcfmunoz.
Last updated
.
Post not yet marked as solved
3 Replies
169 Views
hi I have been using WKWebView embedded in a UIViewRepresentable for displaying inside a SwiftUI View hierarchy, but when I try the same code on 17,5 beta (simulator) the code fails. In fact, the code runs (no exceptions raised or anything) but the web view does not render. In the console logs I see: Warning: -[BETextInput attributedMarkedText] is unimplemented Error launching process, description 'The operation couldn’t be completed. (OSStatus error -10814.)', reason '' The code I am using to present the view is: struct MyWebView: UIViewRepresentable { let content: String func makeUIView(context: Context) -> WKWebView { // Javascript that disables pinch-to-zoom by inserting the HTML viewport meta tag into <head> let source: String = """ var meta = document.createElement('meta'); meta.name = 'viewport'; meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = '*:focus{outline:none}body{margin:0;padding:0}'; var head = document.getElementsByTagName('head')[0]; head.appendChild(meta); head.appendChild(style); """ let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true) let userContentController: WKUserContentController = WKUserContentController() let conf = WKWebViewConfiguration() conf.userContentController = userContentController userContentController.addUserScript(script) let webView = WKWebView(frame: CGRect.zero /*CGRect(x: 0, y: 0, width: 1000, height: 1000)*/, configuration: conf) webView.isOpaque = false webView.backgroundColor = UIColor.clear webView.scrollView.backgroundColor = UIColor.clear webView.scrollView.isScrollEnabled = false webView.scrollView.isMultipleTouchEnabled = false if #available(iOS 16.4, *) { webView.isInspectable = true } return webView } func updateUIView(_ webView: WKWebView, context: Context) { webView.loadHTMLString(content, baseURL: nil) } } This has been working for ages and ages (back to at least ios 15) - something changed. Maybe it is just a problem with the beta 17.5 release?
Posted Last updated
.
Post not yet marked as solved
3 Replies
67 Views
Hi everyone ... i am pretty new to IOS dev, so any help would be great. I am trying to send a string "apple" from UIviewcontroller one to the UIviewcontroller sixth. I am able send the string "apple" from one UIviewcontroller to the next. But then i will have to repeat it again and again in all the views. Is there anyway i can send it directly to the sixth UIviewcontroller ?
Posted
by sonam93.
Last updated
.
Post not yet marked as solved
1 Replies
236 Views
Using SwiftData for a MacOS app I have a working Table but I'd like to add recursion to the rows so instead of stopping at the first child level I can get to grandchildren, etc. Tried creating a separate func that calls itself but cannot get working syntax, possibly because this is happening inside a DisclosureTableRow or I just have bad syntax. Instead of calling TableRow(childAccount) I'd like to be able to call a recursive function which determines if each child is treated as a parent or not until there are no more grandchildren. Each Account class object has .parent: Account, .children: [Account], and .isExpanded:Bool (required by the DisclosureTableRow but not changed here). This is the working non-recursive code: ForEach(theAccounts.sorted(using: sortOrder)) { account in // Make this account bindable so can use .isExpanded directly @Bindable var account = account // Check if the account is not a child of any other account to avoid duplcates, if !theAssetAccounts.contains(where: { $0.children?.contains(account) ?? false }) { // If the account has children, display them in a DisclosureTableRow… if let children = account.children, !children.isEmpty { DisclosureTableRow(account, isExpanded: $account.isExpanded) { ForEach(children) { childAccount in TableRow(childAccount) } } } else { // …or if the account has no children, display it in a simple TableRow TableRow(account) } } } } First the singleton theMotherAccount is at the top level then we iterate over an array of other accounts, theAccounts, showing only those that are not themselves children. Any children are then surfaced as part of another DisclosureTableRow. I thought I could just create a recursive func to return either a DisclosureTableRow or a TableRow but have not been able to find acceptable syntax. This is what I thought ought to work: if let children = account.children, !children.isEmpty { return DisclosureTableRow(account, isExpanded: Bindable(account).isExpanded) { ForEach(children) { child in recursiveAccountRow(account: child) } } } else { return TableRow(account) } }
Posted
by pikes.
Last updated
.
Post not yet marked as solved
0 Replies
55 Views
Hi, Per Apple the isStationary value is supposed to set to true when the device is stationary. I am trying to get a better understanding of when and how this status is changed. When and how does Apple decide when to set this to true and what is the threshold by which it is set to false. Right now when I start my app and use it is set from true to false instantly. let updates = CLLocationUpdate.liveUpdates() for try await update in updates { self.isStationary = update.isStationary I would love to know by what criteria it sets it to true otherwise I'm collecting a lot of zero speed very slight to no movements in latitude and longitude that I have to make some assumptions about filtering out of what I capture. I can't seem to find any mention of this or use case examples in any of the usual sources for examples despite having been introduced at the last WWDC 2023. Any help here would be appreciated.
Posted Last updated
.
Post not yet marked as solved
1 Replies
73 Views
I am trying add Sign in with Apple but when I attempt to capability in my app nothing happens in the list does apple not able to provide this feature yet in Vision OS or is there any bug or may be ami missing something which does not seems?
Posted Last updated
.
Post marked as solved
3 Replies
139 Views
I have a driving tracking app I'm working on that properly starts tracking and logging location when the app is in the foreground or background. I use a buffer/queue to keep recent locations so when a trip ramps up to driving speed I can record that to work back to the start location just before the trip starts. This works great, however, in background mode when the user does not have the app open it will record locations but not until a significant location change is detected. The buffering I do is lost and the location only starts tracking several hundred yards or more after the trip has started. Does anyone have any suggestions or strategies to handle this chicken and the egg scenario?
Posted Last updated
.
Post not yet marked as solved
0 Replies
56 Views
Adding environment value openURL or dismiss to a View in a NavigationStack, without even using it, causes an infinite refresh loop. What doesn't work: a) struct ViewA: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { ViewB() } } } struct ViewB: View { @Environment(\.openURL) var openURL var body: some View { NavigationLink("Next", value: 1) .navigationDestination(for: Int.self, destination: itemView) } func itemView(_ item: Int) -> some View { Text("Item \(item)") } } Prints ViewB: _openURL changed. infinitely. b) Passing the path to ViewB and appending the value with a Button What works: a) .navigationDestination(for: Int.self) { Text("Item \($0)") } Prints ViewB: @self, @identity, _openURL changed. ViewB: @self, _openURL changed. ViewB: _openURL changed. (3 times) b) Handling the destination on ViewA, which is not ideal for my use case. Prints ViewB: @self, @identity, _openURL changed. ViewB: _openURL changed. (5 times) While the workaround would work, it is still unclear how the environment value can cause the freeze (and eventual crash). Also that passing a function as parameter fails, while providing the destination in place does not. The code is stripped down to the minimal reproducible version. Any thoughts?
Posted
by takadeivi.
Last updated
.
Post marked as solved
2 Replies
91 Views
It is driving me crazy, because I really don't know what I am doing wrong. I have a observable class: @Observable class Variables: Identifiable { var id: UUID() var xValue: Int = 1 var yValue: Int = 1 } And a main view that calls a Subview to set the variables, using environment to set variables to the Subview, because this is a very simplified code example and in the final code lot more subview are gonna use this data. struct MainView: View { @State var vars = Variables() var body: Some View { VStack { Subview() .environment(vars) .padding() Text("Value X = \($vars.xValue)") Text("Value Y = \($vars.yValue)") } } struct Subview: View { @Environment(Variables.self) private var vars var body: Some View { VStack { TextField("xValue", value $vars.xValue, format: .number) { Text("X") } TextField("yValue", value $vars.yValue, format: .number) { Text("Y") } } } I Get this error for the TextFields: Cannot convert value of type 'Int' to expected argument type 'Binding' I just don't get it. Am I mixing up different kind of bindings, is something wrong in the definition of TextField.......................................... Please help me.
Posted
by SilkSound.
Last updated
.
Post not yet marked as solved
0 Replies
52 Views
In SwiftUI, a link is identified as both a button and link, this is during when running with VoiceOver. I know you can remove the button trait using .accessibilityRemoveTraits. However, I am sure there is a reason to it. Can somebody explain if it is genuinely a bug.
Posted
by Mayur123.
Last updated
.
Post not yet marked as solved
0 Replies
49 Views
Hello, I have a custom iOS app that takes a picture every second and uploads them to an S3 bucket. However, I was wondering if there is a way to send these images to a Mac instead via USB. Basically the app would capture a photo and send it directly to a location in Mac via USB. I couldn't find any good info online so I am not sure how realistic this is. Thanks a lot!
Posted
by gonn94.
Last updated
.
Post not yet marked as solved
1 Replies
132 Views
I need to create a carousel component with the following requirements (sorted by relevance): Objectives Every image is 16:9 aspect ratio and resizes to fit the screen. Needs a zoom and pan functionality, possibly the same way as iOS Photos app. Needs to work both in landscape and portrait mode, with a smooth transition between orientations. When orientation changes, the image needs to be rotated to preserve the center of the image (like Photos app and hyperoslo/Lightbox) The component should only take the minimum necessary space. In most use cases, such component should have other subviews both above and below. Circularity. I would like the carousel to wrap around. What I tried: Using a TabView with .tabViewStyle(PageTabViewStyle()).indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) modifiers. This didn't work: rotating the interface caused the view to get stuck between pages (it looks like it's a well known [bug]).(https://stackoverflow.com/questions/72435939/swiftui-tabview-is-not-working-properly-on-orientation-change). Implementing a single page (that is, an image view) using an UIScrollView and an UIViewRepresentable, then collecting them into an HStack. Unfortunately I need to use zoomScale and contentOffset properties of the UIScrollView outside of the UIViewRepresentable itself. The net result was that .init() was invoked for every image in the carousel at every rotation, causing severe stutters and an horrible rotation animation. Implementing the whole carousel using UIKit, and an UICollectionView, whose cells were an instance of UIScrollView. The problem is, the UIScrollView needs to recompute its constraints upon rotation but a cell is an instance of UIView, so it can't respond to rotations via viewWillTransition(to:CGSize, with: any UIViewControllerTransitionCoordinator). In the UICollectionView itself, you can only access the visible cells (one at a time is visible), and cells are cached, so even after rotating, some occasionally are presented on screen with the same appearance as before the rotation (some do, some don't, in the same UICollectionView). Also when rotating, it looks like the UIScrollView of the visible cell is being recreated, making it impossible to preserve the image center (I use this subclass of UIScrollView for this purpose). And the UICollectionView is taking the full window size, not just the bare minimum necessary space. Help: With all of this in mind, what options do I realistically have? If necessary I can raise the minimum iOS version to 16.0, even though I guess it doesn't make any significative difference since SwiftUI introduced MagnifyGesture only from iOS 17.0.
Posted Last updated
.
Post not yet marked as solved
1 Replies
158 Views
Hey folks, I need to implement a view for a live activity (so using. WidgetKit), so any UIKit/SceneKit is a non-starter. The thing to implement is a horizontal representation of a 360 degree protractor ('degree triangle'). I would like this view to be inifintely scrollable as if the user has a circular shape around their head and they can rotate their head to look at all the numbers, and when they reach 360/0, they simply continue with new round. In both directions. I've managed to get this working for the positive values by using a LazyHStack and which is populated using ForEach, using a custom class that implements RandomAccessCollection where the endIndex is Int.max (not truly infinite, but good enough). However, I can't get the scrollview/ForEach to start in the 'center' of the Int.max range (I'm sure this would cause some performance issue as well). So my question is: how to get a SwiftUI scrollview to behave like a infinite scrollview, pretty much like Josh Shaffer explained in WWDC 2011 in the session with Eliza Block called 'Advanced ScrollView Techniques'?
Posted
by Joride.
Last updated
.