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
406
Jul ’24
Is "CarPlay.app" malware or a bug?
Why is "CarPlay.app" text shown in Location Services? All other app listings display an icon instead of text - so this looks suspicious. It may be a bug though. Has anyone else seen this? I’ve filed this via Apple Feedback app but no response from Apple yet. Device is running iOS 16.2beta.
2
0
1.9k
Nov ’22
SwiftUI Card List Swipe Action Buttons - Height and Width Issue on iPhone
My app is iOS 17 targeted and uses a card-based list view with Delete and Duplicate trailing and leading swipe actions. The Delete and Duplicate buttons display appropriately on iPad with rounded corners, height equal to card height and width expanding to fill the leading or trailing space created by the swipe action (with padding between the edge of the button and the edge of the card). See screenshot: However, on iPhone (a) the button heights extend from the top of the first card to the bottom of the last card in the scrolled list, (b) the width expands to completely fill the space without padding between the button and the cards, and (c) only the leading (or trailing) top and bottom corners are rounded (respectively). See screenshots: The swipe action modifiers are standard and applied on a CardView in the following view chain (NavigationSplitView -> ZStack -> Group -> ZStack -> List -> ForEach -> ZStack -> CardView | NavigationLink with empty view as workaround for a toolbar bug. Here are the swipe actions : .swipeActions(edge: .trailing, allowsFullSwipe: false) { Button(role: .destructive, action: { // Perform "Delete" action }) { Label("Delete", systemImage: "trash") } } .swipeActions(edge: .leading, allowsFullSwipe: false) { Button(action: { // Perform "Duplicate" action }) { Label("Duplicate", systemImage: "doc.on.doc") } } I’ve tried embedding the buttons within a Geometry reader to explicitly contain the height and width but this results in a compiler error and I suspect that the solution might be a lot simpler. Any thoughts on a fix or an explanation of why the buttons display properly on iPad device and simulator but not on iPhone device or simulator is appreciated. Thanks in advance!
0
0
615
Apr ’24
macOS Ventura Default Web Browser Setting Missing
The "Default Web Browser" setting appears to be missing on MacOS Ventura beta 1 - beta 3. Steps to reproduce: Click Apple Menu Select System Settings > General Observe that Default Browser setting is missing Search Settings for “Default Browser” and notice that the setting is missing Is anyone else experiencing this issue? Any suggestions to resolve or work around this? Thanks!
1
0
2.3k
Jul ’22