App Purchase Validation and In-App Purchase Persistence with the older StoreKit APIs is complicated. I’m curious to learn whether the new StoreKit SwiftUI views and .subscriptionStatusTask modifier simplify the process — or ideally eliminates the need to implement complex Validation and persistence logic using AppStorage, Keychain or App Receipts. Any thoughts on this?
Post
Replies
Boosts
Views
Activity
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!
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.
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!
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!