Hi, I keep trying to use transformable to store an array of strings with SwiftData, and I can see that it is activating the transformer, but it keeps saying that I am still using NSArray instead of NSData.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "category"; desired type = NSData; given type = Swift.__SwiftDeferredNSArray; value = (
yo,
gurt
).'
terminating due to uncaught exception of type NSException
CoreSimulator 1010.10 - Device: iPhone 16 18.0 (6879535B-3174-4025-AD37-ED06E60291AD) - Runtime: iOS 18.0 (22A3351) - DeviceType: iPhone 16
Message from debugger: killed
@Model
class MyModel: Identifiable, Equatable {
@Attribute(.transformable(by: StringArrayTransformer.self)) var category: [String]?
@Attribute(.transformable(by: StringArrayTransformer.self)) var amenities: [String]?
var image: String?
var parentChunck: HenricoPostDataChunk_V1?
init(category: [String]?, amenities: [String]?) {
self.category = category
self.amenities = amenities
}
}
class StringArrayTransformer: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
print(value)
guard let array = value as? [String] else { return nil }
let data = try? JSONSerialization.data(withJSONObject: array, options: [])
print(data)
return data
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
let string = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String]
print(string)
return string
}
override class func transformedValueClass() -> AnyClass {
return NSData.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
static func register() {
print("regitsering")
ValueTransformer.setValueTransformer(StringArrayTransformer(), forName: .stringArrayTransformerName)
}
}
extension NSValueTransformerName {
static let stringArrayTransformerName = NSValueTransformerName("StringArrayTransformer")
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Whenever I load a resized image into a 70 by 70 frame, when I run the loading screen on the simulator, it looks like the image is flying from the top left corner of the screen on load, however, when I load it in the previews, it starts where its supposed to be in the center. Both are scaled properly, however, the first ones position is acting like I put a transition on it when I did not
import SwiftUI
struct LoadingView: View {
@Environment(\.colorScheme) private var colorScheme
var text: String? = nil
@State private var isSpinning = false
var body: some View {
VStack{
Image("Jeromes_Logo")
.resizable()
.frame(width: 70, height: 70)
.rotationEffect(.degrees(isSpinning ? 360 : 0))
.animation(
.bouncy(duration: 0.4, extraBounce: 0.2)
.repeatForever(autoreverses: false),
value: isSpinning
)
.onAppear {
isSpinning = true
}
if let text = text {
Text(text)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
#Preview {
LoadingView(text: "loading")
}
Hi, I keep trying to use transformable to store an array of strings with SwiftData, and I can see that it is activating the transformer, but it keeps saying that I am still using NSArray instead of NSData.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "category"; desired type = NSData; given type = Swift.__SwiftDeferredNSArray; value = ( yo, gurt ).' terminating due to uncaught exception of type NSException CoreSimulator 1010.10 - Device: iPhone 16 18.0 (6879535B-3174-4025-AD37-ED06E60291AD) - Runtime: iOS 18.0 (22A3351) - DeviceType: iPhone 16 Message from debugger: killed
@Model
class MyModel: Identifiable, Equatable {
@Attribute(.transformable(by: StringArrayTransformer.self)) var category: [String]?
@Attribute(.transformable(by: StringArrayTransformer.self)) var amenities: [String]?
var image: String?
var parentChunck: MyModelDataChunk_V1?
init(category: [String]?, amenities: [String]?) {
self.category = category
self.amenities = amenities
}
}
class StringArrayTransformer: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
print(value)
guard let array = value as? [String] else { return nil }
let data = try? JSONSerialization.data(withJSONObject: array, options: [])
print(data)
return data
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
let string = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String]
print(string)
return string
}
override class func transformedValueClass() -> AnyClass {
return NSData.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
static func register() {
print("regitsering")
ValueTransformer.setValueTransformer(StringArrayTransformer(), forName: .stringArrayTransformerName)
}
}
extension NSValueTransformerName {
static let stringArrayTransformerName = NSValueTransformerName("StringArrayTransformer")
}
Hello everyone, I am having an issue where the attributed text that I have in my UITextView is not scaling dynamically with phone text size, whenever I remove the attributed text logic, it scales fine, however, with it, it stays at a set font size.
struct AutoDetectedClickableDataView: UIViewRepresentable {
let text: String
@Binding var height: CGFloat
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.dataDetectorTypes = [.phoneNumber, .address, .link]
textView.isEditable = false
textView.isScrollEnabled = false
textView.backgroundColor = .clear
textView.font = UIFont.preferredFont(forTextStyle: .body) /*UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.systemFont(ofSize: 16.0)) */
textView.adjustsFontForContentSizeCategory = true
textView.textContainer.lineBreakMode = .byWordWrapping
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.translatesAutoresizingMaskIntoConstraints = false
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
let attributed = NSMutableAttributedString(string: text, attributes: [
.font: UIFont.preferredFont(forTextStyle: .body)
])
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.address.rawValue |
NSTextCheckingResult.CheckingType.link.rawValue |
NSTextCheckingResult.CheckingType.phoneNumber.rawValue)
detector?.enumerateMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)) { match, _, _ in
guard let match = match else { return }
attributed.addAttributes([
.foregroundColor: UIColor.systemBlue,
.underlineStyle: NSUnderlineStyle.single.rawValue,
], range: match.range)
}
uiView.attributedText = attributed
// uiView.text = text
DispatchQueue.main.async {
uiView.layoutIfNeeded()
let fittingSize = CGSize(width: uiView.bounds.width, height: .greatestFiniteMagnitude)
let size = uiView.sizeThatFits(fittingSize)
height = size.height
}
}
}
I am currently having an issue in where whenever I place a UITextView with text that's long, it simply pushes past the width of the container it is in, and when I do try and set a width, it does auto wrap and ignores that. I do not come from UIKit and come from SwiftUI, through a mix of ChatGPT and Stack overflow, I have come up with this solution, however, are there any better/simpler ones, I dont want to have to use expensive GeoReaders just to get a width.
struct AutoDetectedPhoneNumberView: UIViewRepresentable {
let text: String
var width: CGFloat
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.dataDetectorTypes = [.phoneNumber]
textView.isEditable = false
textView.isScrollEnabled = false
textView.backgroundColor = .clear
textView.font = UIFont.systemFont(ofSize: 16)
textView.textContainer.lineBreakMode = .byWordWrapping
textView.translatesAutoresizingMaskIntoConstraints = false
print(width)
NSLayoutConstraint.activate([
textView.widthAnchor.constraint(equalToConstant: width)
])
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
GeometryReader { geo in
AutoDetectedPhoneNumberView(text: phone, width: geo.size.width)
}
I currently have a custom UITextView that properly changes the color of a link to the custom color I have set, however, when I click and hold the link, and the animation plays that makes the link bigger and creates a boarder around the link before previewing the website, the link changes briefly back to the system default color, and when letting go, it changes back to my custom color. Is there anyway to have the link always be my custom color, even when clicking and holding?
struct AutoDetectedClickableDataView: UIViewRepresentable {
let text: String
let dataDetectors: UIDataDetectorTypes
@Binding var height: CGFloat
private func dataDectectorValue(_ dataDectecor: UIDataDetectorTypes) -> UInt64 {
var checkingTypes: [NSTextCheckingResult.CheckingType] = []
if dataDetectors.contains(.phoneNumber) {
checkingTypes.append(.phoneNumber)
}
if dataDetectors.contains(.link) {
checkingTypes.append(.link)
}
if dataDetectors.contains(.address) {
checkingTypes.append(.address)
}
if dataDetectors.contains(.calendarEvent) {
checkingTypes.append(.date)
}
if checkingTypes.isEmpty {
return 0
}
let intCheckingTypes: UInt64 = checkingTypes.reduce(0) { result, checkingType in
UInt64(result) | UInt64(checkingType.rawValue)
}
return intCheckingTypes
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.dataDetectorTypes = dataDetectors
textView.isEditable = false
textView.isScrollEnabled = false
textView.backgroundColor = .clear
textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.systemFont(ofSize: 16.0), compatibleWith: textView.traitCollection)
textView.textColor = UIColor.label
textView.adjustsFontForContentSizeCategory = true
textView.textContainer.lineBreakMode = .byWordWrapping
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.translatesAutoresizingMaskIntoConstraints = false
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
textView.linkTextAttributes = [.foregroundColor: JeromesColors.UILinkColor]
textView.tintColor = JeromesColors.UILinkColor
for gesture in textView.gestureRecognizers ?? [] {
if gesture is UITapGestureRecognizer {
gesture.view?.tintColor = JeromesColors.UILinkColor
}
}
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
let font = UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.systemFont(ofSize: 16.0), compatibleWith: uiView.traitCollection)
let color = UIColor.label
let attributed = NSMutableAttributedString(string: text, attributes: [
.font: font,
.foregroundColor: color
])
let detectorValue = dataDectectorValue(dataDetectors)
var matchCount = 0
if !dataDetectors.isEmpty, detectorValue != 0 {
let detector = try? NSDataDetector(types: detectorValue)
detector?.enumerateMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)) { match, _, _ in
guard let match = match else { return }
if match.resultType == .date, let date = match.date, date < Date() {
return
} else {
attributed.addAttributes([
.foregroundColor: JeromesColors.UILinkColor,
.underlineStyle: NSUnderlineStyle.single.rawValue,
], range: match.range)
matchCount += 1
}
}
}
uiView.attributedText = attributed
if matchCount == 0 {
uiView.isSelectable = false
} else if matchCount > 0 {
uiView.isSelectable = true
}
DispatchQueue.main.async {
uiView.layoutIfNeeded()
let fittingSize = CGSize(width: uiView.bounds.width, height: .greatestFiniteMagnitude)
let size = uiView.sizeThatFits(fittingSize)
height = size.height
}
}
}