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

SwiftUI Documentation

Post

Replies

Boosts

Views

Activity

Is it Possible to save AttributedString with image to RTF doc?
Hello, I am trying to implement an RTF document export feature on a small app. But after days of trying and failing with dozens of approaches I’m beginning to wonder if it is even possible to create an .rtf file with embedded text and images in Swift/SwiftUI. I’ve tried many variations of (A) building the text and image content with HTML, converting it to AttributedString, then exporting to RTF, and (B) building the text and image content directly with AttributedString attributes and attachments for the image — and in both cases, the images are not saved in the RTF file. I am able to create a preview of the AttributedString with formatted text and image, and able to create an RTF file with formatted text that opens with TextEdit, Pages and Word without issue; but cannot get the image to appear in the saved RTF file. I’m hoping someone here can shed some light on if this is possible and if yes, how to save the combined text and image data to an RTF file. Here is the latest variation of the code I’m using — any ideas/suggestions are appreciated 🙏🏽: import SwiftUI struct ContentView: View { @State private var showExportSheet = false @State private var rtfData: Data? @State private var isLoading = false @State private var previewAttributedString: NSAttributedString? var body: some View { VStack { Button("Export RTF with Image") { isLoading = true createRTFWithEmbeddedImage() } .disabled(isLoading) if isLoading { ProgressView() } if let previewAttributedString = previewAttributedString { VStack { Text("Preview:") .font(.headline) TextView(attributedString: previewAttributedString) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity) .background(Color.gray.opacity(0.1)) } .padding() } } .sheet(isPresented: $showExportSheet) { DocumentPicker(rtfData: $rtfData) } } func createRTFWithEmbeddedImage() { let text = "This is a sample RTF document with an embedded image:" // Load the image (star.fill as a fallback) guard let image = UIImage(systemName: "star.fill") else { print("Failed to load image") isLoading = false return } // Resize the image to 100x100 pixels let resizedImage = resizeImage(image: image, targetSize: CGSize(width: 100, height: 100)) // Convert image to NSTextAttachment let attachment = NSTextAttachment() attachment.image = resizedImage // Set bounds for the image attachment.bounds = CGRect(x: 0, y: 0, width: 100, height: 100) // Create attributed string with the attachment let attributedString = NSMutableAttributedString(string: text) let attachmentString = NSAttributedString(attachment: attachment) attributedString.append(attachmentString) // Add red border around the image attributedString.addAttribute(.strokeColor, value: UIColor.red, range: NSRange(location: attributedString.length - attachmentString.length, length: attachmentString.length)) attributedString.addAttribute(.strokeWidth, value: -2.0, range: NSRange(location: attributedString.length - attachmentString.length, length: attachmentString.length)) // Set previewAttributedString for preview self.previewAttributedString = attributedString // Convert attributed string to RTF data guard let rtfData = try? attributedString.data(from: NSRange(location: 0, length: attributedString.length), documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf]) else { print("Failed to create RTF data") isLoading = false return } self.rtfData = rtfData isLoading = false showExportSheet = true // Debug: Save RTF to a file in the Documents directory saveRTFToDocuments(rtfData) } func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage { let size = image.size let widthRatio = targetSize.width / size.width let heightRatio = targetSize.height / size.height let newSize: CGSize if widthRatio > heightRatio { newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) } else { newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) } let rect = CGRect(origin: .zero, size: newSize) UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) image.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage ?? UIImage() } func saveRTFToDocuments(_ data: Data) { let fileManager = FileManager.default guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { print("Unable to access Documents directory") return } let fileURL = documentsDirectory.appendingPathComponent("debug_output.rtf") do { try data.write(to: fileURL) print("Debug RTF file saved to: \(fileURL.path)") } catch { print("Error saving debug RTF file: \(error)") } } } struct DocumentPicker: UIViewControllerRepresentable { @Binding var rtfData: Data? func makeUIViewController(context: Context) -> UIDocumentPickerViewController { let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("document_with_image.rtf") do { try rtfData?.write(to: tempURL) } catch { print("Error writing RTF file: \(error)") } let picker = UIDocumentPickerViewController(forExporting: [tempURL], asCopy: true) return picker } func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} } struct TextView: UIViewRepresentable { let attributedString: NSAttributedString func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.isEditable = false textView.attributedText = attributedString return textView } func updateUIView(_ uiView: UITextView, context: Context) { uiView.attributedText = attributedString } } Thank in advance!
2
0
157
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
SwiftUI dropping state changes silently?
Hello all, I have an app that demonstrates how sorting algorithms work. It's written in SwiftUI and uses native geometry objects (Rectangle, etc.) to draw the bars that are demonstrating the sorting algorithm. The application runs just fine on macOS Sonoma and iPadOS 17, if not a little bit slowly because the algorithm and drawing are interlocked to stay in time. When I started working on the app in Xcode 16 and running it on macOS Sequoia, I notice a very unusual change: the algorithm is running significantly faster (104.5 "operations" per second in Sequoia vs. 78.5 in Sonoma), with the nebulous side effect of the visualization noticeably stuttering. The application sometimes looks like it's frozen because several underlying state changes are being reflected in the UI, and when the sorting process is completed, it's supposed to turn all of the rectangles green, but this doesn't happen on the Sequoia/Xcode 16 build unless I open the navigation pane after sorting is completed. I can't find any reference to behavior changes in SwiftUI like this. Is this expected behavior? I thought that if such a significant change was made to SwiftUI's behavior, then Apple would at least inform their developers that they need to adapt their code in an article or a WWDC video. I did file a bug, it's 14025421 in Radar (don't know the correct format for Radar links). I included examples of how the app is supposed to look, along with some source code, and a large number of Instruments traces. I attempted to debug from there, but nothing was out of the ordinary; the traces were almost identical between the release version that runs on Sonoma and the debug build version that runs on Sequoia. Here's the behavior between the two versions on display, for reference. You might have to copy and paste the links, because the editor told me I wasn't allowed to have links to YouTube for whatever reason. Expected behavior, where animations are smooth, but algorithm runs more slowly: https://www.youtube.com/watch?v=UWMXiju8EMg Unexpected behavior, where sort algorithm performance is higher, but animations are choppy, and state changes are skipped: https://www.youtube.com/watch?v=GB0VJo8W7_0
0
0
110
1w
Programatic Haptics
I'm developing an iPhone App that is using on-device speech recognition. I'd like to create a 'buzz' to confirm the word was recognised without having to look at the screen. I can get haptics working from a button; but when I try to attach sensoryfeedback to any view element, it does not fire despite my toggle from successful voice commands variable correctly flipping. Has anyone tried a similar thing with success, or have any ideas? Thanks very much.
0
0
113
1w
swiftui. need .isVisible() {}. or similar
Im using a lazyvgrid with a customImage View that supports lazy loading of a high res image (otherwise thumb is used) What I'm looking for is some way to initiate my loadFullImage() functionality when the CustomImage view is visible ( onAppear() is not what I need. its called when view is loaded in grid, not when visible ) I've tried variation of this, but they do not work reliably CustomImage(key: key) .background( GeometryReader { proxy in Color.clear .preference(key: ViewVisibleKey.self, value: true) } ) .onPreferenceChange(ViewVisibleKey.self) { isVisible in DispatchQueue.main.async { loadImage(key) } } } Anyone have any suggestions ? either why the code above doesn't work reliably (its like 10%) , or some other way to accomplish this. Thanks
0
0
93
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
Map behaves differently compared to MKMapView
Hey, I have a problem. I was using MKMapView in my app, and in the view where I had a background at the top of the screen, in the example it was Color.red, it extended all the way to the top of the screen. Now, I wanted to switch to the newer Map and I'm seeing an issue because I'm getting a navigation bar that cuts off my color as I indicated in the picture. Does anyone know why this is happening and if there's another way to achieve this? Steps to reproduce: Change MapView() to Map() to see difference import SwiftUI import MapKit @main struct TestAppApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { var body: some View { NavigationStack { ScrollView(.vertical) { Color.red .padding(.top, -200) .frame(height: 200) MapView().frame(minHeight: 300) // change this line to Map } .navigationTitle("Title") .navigationBarTitleDisplayMode(.large) } } } private typealias ViewControllerRepresentable = UIViewControllerRepresentable struct MapView: ViewControllerRepresentable { typealias ViewController = UIViewController class Controller: ViewController { var mapView: MKMapView { guard let tempView = view as? MKMapView else { fatalError("View could not be cast as MapView.") } return tempView } override func loadView() { let mapView = MKMapView() view = mapView } } func makeUIViewController(context: Context) -> Controller { Controller() } func updateUIViewController(_ controller: Controller, context: Context) { update(controller: controller) } func update(controller: Controller) { } } #Preview { ContentView() } I got: I want:
0
0
132
1w
iOS18 beta2: NavigationStack, Views Being Popped Automatically
This is a report of an issue that appears to be a regression regarding NavigationStack. I have been aware of an issue where views are being automatically popped within NavigationView / NavigationStack since iOS 15, and it seems to be reoccurring in iOS 18.0 beta2. Below is the reproducible code. Additionally, in my environment, this problem does not occur iOS 18 simulator, but it does happen on an iPhone XS Max(real device) with iOS 18 beta 2. Environment: Xcode: Version 16.0 beta (16A5171c) iOS: 18.0 (22A5297f) iPhone: XS Max (real device) import SwiftUI @main struct iOS16_4NavigationSample2App: App { var body: some Scene { WindowGroup { NavigationStack { NavigationLink { ContentView() } label: { Text("Content") } } } } } enum Kind { case none, a, b, c } struct Value: Hashable, Identifiable { let id: UUID = UUID() var num: Int } @MainActor class ContentModel: ObservableObject { @Published var kind: Kind = .a @Published var vals: [Value] = { return (1...5).map { Value(num: $0) } }() init() {} } struct ContentView: View { @StateObject private var model = ContentModel() @State private var selectedData: Value? @State private var isShowingSubView = false @Environment(\.dismiss) private var dismiss init() { } var body: some View { if #available(iOS 16.0, *) { List(selection: $selectedData) { ForEach(model.vals) { val in NavigationLink(value: val) { Text("\(val.num)") } } } .navigationDestination(isPresented: .init(get: { selectedData != nil }, set: { newValue in if !newValue && selectedData != nil { selectedData = nil } }), destination: { SubView(kind: model.kind) }) } } } struct SubView: View { init(kind: Kind) { print("init(kind:)") } init() { print("init") } var body: some View { Text("Content") } } This code was shared in a different issue [iOS 16.4 NavigationStack Behavior Unstable].
8
1
323
3w
iOS18 beta2 NavigationStack: Tapping Back from a lower-level View returns to the Root View / No transition animation
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)") } }
6
0
193
2w
TabSection always show sections actions.
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?
1
0
224
2w
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
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
Question about preview using code in SwiftUI
Hello, I have a view which have a few controls and preview macro. I replaced preview macro with following code: struct ContentView_Preview:PreviewProvider{ static var previews: some View{ ContentView() } } after replacement, preview doesn't work properly. plz tell me how can i fix it. thanks, c00012
2
0
133
1w
SwiftUI and SwiftData projects, textField gets focus, freezes, and takes 7000 milliseconds to focus
macOS 14.5、Xcode 15.4、iPhone OS 18.0 beta Open Xcode and create a new project. Select swiftUI for Interface, Swift for Language, and Swiftdata for Storage. After creating the project, open the ContentView.swift file,write the code @State var username: String = "" TextField("info", text: $username) .textFieldStyle(.roundedBorder) .disableAutocorrection(true) .textInputAutocapitalization(.never) Run the project on iPhone. textField gets focus, freezes, and takes 7000 milliseconds to focus
0
0
114
1w
How to integrate Pencil into my application? I have encountered a problem.
我是个新手。我是UIKit的新手。我真的不知道该怎么办。我的问题是,我希望用铅笔绘制的图纸在PencilCreate中显示,但我不知道如何控制它们的交付。我需要单击HB才能跳转到铅笔视图界面。然后,铅笔视图界面中绘制的内容可以显示在PencilCreate的.border中。 导入SwiftUI 导入 PencilKit struct GrPencil:查看{ var body:一些视图{ 导航堆栈{ 画布视图() } } } struct CanvasView:UIViewRepresentable { @State var canvasView:PKCanvasView = PKCanvasView() @State var toolPicker = PKToolPicker() func makeUIView(上下文:上下文)-> PKCanvasView { canvasView.drawingPolicy = .anyInput canvasView.becomeFirstResponder() toolPicker.setVisible(true,forFirstResponder:canvasView) toolPicker.addObserver(canvasView) 返回canvasView } func updateUIView(_ uiView: PKCanvasView, context: Context) { } } #预览 { GrPencil() } 导入SwiftUI 导入 PencilKit struct PencilCreate:查看{ @State var drawing = PKDrawing() var body:一些视图{ VStack { VStack { 图像(uiImage:drawing.image(来自:drawing.bounds,比例:1)) .resizable() .框架(宽度:200,高度:120) .border(颜色.黑色) 按钮{ }标签:{ 文本(“HB”) } } } } } #预览 { PencilCreate() }
0
0
89
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
151
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