在 CoreData 数据库中存储包含 CGPoints 的数组 (Swift)
store Array containing CGPoints in CoreData database (Swift)
所以正如标题已经指出的那样,我正在尝试将一个数组保存到数据库中。如果这是可能的,我该怎么做?如果没有,我希望你能帮我解决其他问题。
我正在制作一个 iOS 应用程序,如果用户触摸(并移动)屏幕,我会将此数据存储在一个数组中。因为它需要 multi-touch 将某一时刻所有触摸(touchesBegan 或 touchesMoved)的 CGPoint 存储在一个数组中,该数组再次存储在主数组中。导致var everyLocation = [[CGPoint]]()
。我已经发现无法将 CGPoint 直接存储在数据库中,因此我可以使用 NSStringFromCGPoint(pointVariable)
将它们转换为字符串。然而,只要我不能存储数组,这就不是很有用......
我也想存储它发生的日期,所以在我的数据库中我创建了具有两个属性的实体 'Locations':'locations' 和 'date'。在最终的应用程序中,实体名称将是用户正在进行的练习的名称(我有大约四个练习,所以有四个实体)。
我见过的大多数示例代码都将 CGPoint 存储在单独的 x 和 y 中或存储在一个字符串中。我也许也可以这样做,所以我不必存储数组。要做到这一点,我想我必须使属性成为触摸的坐标,实体名称是日期,数据库名称是练习的名称。如果这是唯一的解决方案,我如何在 run-time?
创建一个实体(带有属性)
提前致谢
1) 添加一个 "Transformable" 类型属性。
2) Event.h
@interface Event : NSManagedObject
@property (nonatomic, retain) NSArray * absentArray;
@interface AbsentArray : NSValueTransformer
@end
Event.m
@implementation AbsentArray
+ (Class)transformedValueClass
{
return [NSArray class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
return [NSKeyedArchiver archivedDataWithRootObject:value];
}
- (id)reverseTransformedValue:(id)value
{
return [NSKeyedUnarchiver unarchiveObjectWithData:value];
}
@end
3) 把它当作普通数组使用即可
Event *event = //init
event.absentArray = @[1,2,3];
[context save:nil]
只需更改 swift 中的这些代码。
你可以理解为.swfit合并.h/.m文件。 Objective C 有 .h 作为头文件,里面有很多属性。 .m 是暗示文件应该有哪些方法。
例如:
.swift
import Foundation
import CoreData
class Event: NSManagedObject {
@NSManaged var absentArray: AnyObject
}
3) 保存:
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
在 William 向我指出变形的方向后,我终于设法将各个部分拼凑起来。我使用本教程来了解如何使用它:http://jamesonquave.com/blog/core-data-in-swift-tutorial-part-1/
Swift3 使其变得无缝,
随便写
typealias Point = CGPoint
并将属性类型设置为 Transformable 并将其自定义 class 设置为
Array<Point>
无需执行任何操作即可为我工作。
以下是我从警告消息提示的练习中学到的东西:
在某些时候,当指定 nil 时,Core Data 将默认使用“NSSecureUnarchiveFromData”,并且包含不支持 NSSecureCoding 的 类 的可转换属性将变得不可读。
我的应用程序收集了一系列点 [CGPoint],这些点是用 Apple Pencil 或手指在屏幕上绘图创建的,并将其存储在 CoreData 中——基本上是我称之为 Scribble 的东西的核心。为了存储在 CoreData 中,我创建了一个名为“points”的属性并将类型设置为 Transformable。自定义 Class 设置为 [CGPoint]。此外,我将 CodeGen 设置为手动,而不是自动的“Class 定义”选项。当我生成 CoreData 托管对象子类文件时,它会生成一个 +CoreDataClass.swift 文件,关键行是:
@NSManaged public 变量点:[CGPoint]?
需要注意的是,如果您使用自动选项实际上会出现问题,因为生成的文件不知道 CGPoint 是什么,也无法编辑以添加 UIKit 的导入以供其查找定义。
在 Apple 开始鼓励安全编码之前,这一直很有效。在下面的代码文件中,我开发了一个 ScribblePoints 对象来处理编码及其关联的数据转换器。
//
// ScribblePoints.swift
//
import Foundation
import UIKit
public class ScribblePoints: NSObject, NSCoding {
var points: [CGPoint] = []
enum Key: String {
case points = "points"
}
init(points: [CGPoint]) {
self.points = points
}
public func encode(with coder: NSCoder) {
coder.encode(points, forKey: Key.points.rawValue)
}
public required convenience init?(coder: NSCoder) {
if let sPts = coder.decodeObject(of: ScribblePoints.self, forKey: Key.points.rawValue) {
self.init(points: sPts.points)
} else {
return nil
}
}
}
extension ScribblePoints : NSSecureCoding {
public static var supportsSecureCoding = true
}
@available(iOS 12.0, *)
@objc(ScribblePointsValueTransformer)
final class ScribblePointsValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: ScribblePointsValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [ScribblePoints.self]
}
public static func register() {
let transformer = ScribblePointsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func transformedValue(_ value: Any?) -> Any? {
if let data = value as? Data {
// Following deprecated at iOS12:
// if let data = value as? Data {
// if let points = NSKeyedUnarchiver.unarchiveObject(with: data) as? [CGPoint] {
// return points
// }
// }
do {
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = false
let decodeResult = unarchiver.decodeObject(of: [NSArray.self, ScribblePoints.self], forKey: NSKeyedArchiveRootObjectKey)
if let points = decodeResult as? [CGPoint] {
return points
}
} catch {
}
}
return nil
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
if let points = value as? [CGPoint] {
// Following deprecated at iOS12:
// let data = NSKeyedArchiver.archivedData(withRootObject: points)
// return data
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: points, requiringSecureCoding: true)
return data
} catch {
}
}
return nil
}
}
完成上述操作后,我终于可以为 CoreData 中“points”属性的 Transformer 名称填写 ScribblePointsValueTransformer。
也可以将自定义 Class 从 [CGPoint] 切换为 ScribblePoints。这似乎不影响代码执行。但是,如果重新生成 +CoreDataClass.swift 文件,关键的兴趣行将变为:
@NSManaged public var points: ScribblePoints?
并且当您重新编译时,您将需要更改代码以处理不同的定义。如果您是从头开始,似乎您可能只想使用 ScribblePoints 定义,并避免处理 NSArrays 和 NSPoints 以及您使用 [CGPoint] 以奇怪的方式神奇地遇到的其他东西的麻烦。
上面是 Swift 5.
当我将较旧的 iOS 设备 (iOS9) 连接到 Xcode 时,运行 进入带有我上面回答的警告消息。一切正常,但有关未找到值转换器的警告消息令人不安。问题是如果你在 iOS12+ 上,之前的答案只定义和注册值转换器。为了在早期系统上无怨无悔地工作,需要避免使用 NSSecureUnarchiveFromDataTransformer,而是使用 ValueTransformer,并依赖 NSSecureCoding 一致性来实现您的编码对象。然后您可以在旧的 iOS 系统上注册您的价值转换器。还应该注意的是,transformedValue() 和 reverseTransformedValue() 函数被颠倒了。
最终结果是下面的代码。
//
// ScribblePoints.swift
//
import Foundation
import UIKit
public class ScribblePoints: NSObject, NSCoding {
var points:[CGPoint] = []
enum Key: String {
case points = "points"
}
init(points: [CGPoint]) {
self.points = points
}
public func encode(with coder: NSCoder) {
coder.encode(points, forKey: Key.points.rawValue)
}
public required convenience init?(coder: NSCoder) {
if let sPts = coder.decodeObject(of: ScribblePoints.self, forKey: Key.points.rawValue) {
self.init(points: sPts.points)
} else {
return nil
}
}
}
extension ScribblePoints : NSSecureCoding {
public static var supportsSecureCoding = true
}
@objc(ScribblePointsValueTransformer)
final class ScribblePointsValueTransformer: ValueTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: ScribblePointsValueTransformer.self))
public static func register() {
let transformer = ScribblePointsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override class func transformedValueClass() -> AnyClass {
return ScribblePoints.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
if let data = value as? Data {
do {
if #available(iOS 11.0, *) {
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = false
let decodeResult = unarchiver.decodeObject(of: [NSArray.self, ScribblePoints.self], forKey: NSKeyedArchiveRootObjectKey)
if let points = decodeResult as? [CGPoint] {
return points
}
} else {
// Fallback on earlier versions
if let data = value as? Data {
if let points = NSKeyedUnarchiver.unarchiveObject(with: data) as? [CGPoint] {
return points
}
}
}
} catch {
}
}
return nil
}
override func transformedValue(_ value: Any?) -> Any? {
if let points = value as? [CGPoint] {
do {
if #available(iOS 11.0, *) {
let data = try NSKeyedArchiver.archivedData(withRootObject: points, requiringSecureCoding: true)
return data
} else {
// Fallback on earlier versions
let data = NSKeyedArchiver.archivedData(withRootObject: points)
return data
}
} catch {
}
}
return nil
}
}
在 CoreData 中,事物的定义方式如下所示。
所以正如标题已经指出的那样,我正在尝试将一个数组保存到数据库中。如果这是可能的,我该怎么做?如果没有,我希望你能帮我解决其他问题。
我正在制作一个 iOS 应用程序,如果用户触摸(并移动)屏幕,我会将此数据存储在一个数组中。因为它需要 multi-touch 将某一时刻所有触摸(touchesBegan 或 touchesMoved)的 CGPoint 存储在一个数组中,该数组再次存储在主数组中。导致var everyLocation = [[CGPoint]]()
。我已经发现无法将 CGPoint 直接存储在数据库中,因此我可以使用 NSStringFromCGPoint(pointVariable)
将它们转换为字符串。然而,只要我不能存储数组,这就不是很有用......
我也想存储它发生的日期,所以在我的数据库中我创建了具有两个属性的实体 'Locations':'locations' 和 'date'。在最终的应用程序中,实体名称将是用户正在进行的练习的名称(我有大约四个练习,所以有四个实体)。
我见过的大多数示例代码都将 CGPoint 存储在单独的 x 和 y 中或存储在一个字符串中。我也许也可以这样做,所以我不必存储数组。要做到这一点,我想我必须使属性成为触摸的坐标,实体名称是日期,数据库名称是练习的名称。如果这是唯一的解决方案,我如何在 run-time?
提前致谢
1) 添加一个 "Transformable" 类型属性。
2) Event.h
@interface Event : NSManagedObject
@property (nonatomic, retain) NSArray * absentArray;
@interface AbsentArray : NSValueTransformer
@end
Event.m
@implementation AbsentArray
+ (Class)transformedValueClass
{
return [NSArray class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
return [NSKeyedArchiver archivedDataWithRootObject:value];
}
- (id)reverseTransformedValue:(id)value
{
return [NSKeyedUnarchiver unarchiveObjectWithData:value];
}
@end
3) 把它当作普通数组使用即可
Event *event = //init
event.absentArray = @[1,2,3];
[context save:nil]
只需更改 swift 中的这些代码。
你可以理解为.swfit合并.h/.m文件。 Objective C 有 .h 作为头文件,里面有很多属性。 .m 是暗示文件应该有哪些方法。
例如: .swift
import Foundation
import CoreData
class Event: NSManagedObject {
@NSManaged var absentArray: AnyObject
}
3) 保存:
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
在 William 向我指出变形的方向后,我终于设法将各个部分拼凑起来。我使用本教程来了解如何使用它:http://jamesonquave.com/blog/core-data-in-swift-tutorial-part-1/
Swift3 使其变得无缝, 随便写
typealias Point = CGPoint
并将属性类型设置为 Transformable 并将其自定义 class 设置为
Array<Point>
无需执行任何操作即可为我工作。
以下是我从警告消息提示的练习中学到的东西: 在某些时候,当指定 nil 时,Core Data 将默认使用“NSSecureUnarchiveFromData”,并且包含不支持 NSSecureCoding 的 类 的可转换属性将变得不可读。
我的应用程序收集了一系列点 [CGPoint],这些点是用 Apple Pencil 或手指在屏幕上绘图创建的,并将其存储在 CoreData 中——基本上是我称之为 Scribble 的东西的核心。为了存储在 CoreData 中,我创建了一个名为“points”的属性并将类型设置为 Transformable。自定义 Class 设置为 [CGPoint]。此外,我将 CodeGen 设置为手动,而不是自动的“Class 定义”选项。当我生成 CoreData 托管对象子类文件时,它会生成一个 +CoreDataClass.swift 文件,关键行是:
@NSManaged public 变量点:[CGPoint]?
需要注意的是,如果您使用自动选项实际上会出现问题,因为生成的文件不知道 CGPoint 是什么,也无法编辑以添加 UIKit 的导入以供其查找定义。
在 Apple 开始鼓励安全编码之前,这一直很有效。在下面的代码文件中,我开发了一个 ScribblePoints 对象来处理编码及其关联的数据转换器。
//
// ScribblePoints.swift
//
import Foundation
import UIKit
public class ScribblePoints: NSObject, NSCoding {
var points: [CGPoint] = []
enum Key: String {
case points = "points"
}
init(points: [CGPoint]) {
self.points = points
}
public func encode(with coder: NSCoder) {
coder.encode(points, forKey: Key.points.rawValue)
}
public required convenience init?(coder: NSCoder) {
if let sPts = coder.decodeObject(of: ScribblePoints.self, forKey: Key.points.rawValue) {
self.init(points: sPts.points)
} else {
return nil
}
}
}
extension ScribblePoints : NSSecureCoding {
public static var supportsSecureCoding = true
}
@available(iOS 12.0, *)
@objc(ScribblePointsValueTransformer)
final class ScribblePointsValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: ScribblePointsValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [ScribblePoints.self]
}
public static func register() {
let transformer = ScribblePointsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func transformedValue(_ value: Any?) -> Any? {
if let data = value as? Data {
// Following deprecated at iOS12:
// if let data = value as? Data {
// if let points = NSKeyedUnarchiver.unarchiveObject(with: data) as? [CGPoint] {
// return points
// }
// }
do {
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = false
let decodeResult = unarchiver.decodeObject(of: [NSArray.self, ScribblePoints.self], forKey: NSKeyedArchiveRootObjectKey)
if let points = decodeResult as? [CGPoint] {
return points
}
} catch {
}
}
return nil
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
if let points = value as? [CGPoint] {
// Following deprecated at iOS12:
// let data = NSKeyedArchiver.archivedData(withRootObject: points)
// return data
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: points, requiringSecureCoding: true)
return data
} catch {
}
}
return nil
}
}
完成上述操作后,我终于可以为 CoreData 中“points”属性的 Transformer 名称填写 ScribblePointsValueTransformer。
也可以将自定义 Class 从 [CGPoint] 切换为 ScribblePoints。这似乎不影响代码执行。但是,如果重新生成 +CoreDataClass.swift 文件,关键的兴趣行将变为:
@NSManaged public var points: ScribblePoints?
并且当您重新编译时,您将需要更改代码以处理不同的定义。如果您是从头开始,似乎您可能只想使用 ScribblePoints 定义,并避免处理 NSArrays 和 NSPoints 以及您使用 [CGPoint] 以奇怪的方式神奇地遇到的其他东西的麻烦。
上面是 Swift 5.
运行 进入带有我上面回答的警告消息。一切正常,但有关未找到值转换器的警告消息令人不安。问题是如果你在 iOS12+ 上,之前的答案只定义和注册值转换器。为了在早期系统上无怨无悔地工作,需要避免使用 NSSecureUnarchiveFromDataTransformer,而是使用 ValueTransformer,并依赖 NSSecureCoding 一致性来实现您的编码对象。然后您可以在旧的 iOS 系统上注册您的价值转换器。还应该注意的是,transformedValue() 和 reverseTransformedValue() 函数被颠倒了。
最终结果是下面的代码。
//
// ScribblePoints.swift
//
import Foundation
import UIKit
public class ScribblePoints: NSObject, NSCoding {
var points:[CGPoint] = []
enum Key: String {
case points = "points"
}
init(points: [CGPoint]) {
self.points = points
}
public func encode(with coder: NSCoder) {
coder.encode(points, forKey: Key.points.rawValue)
}
public required convenience init?(coder: NSCoder) {
if let sPts = coder.decodeObject(of: ScribblePoints.self, forKey: Key.points.rawValue) {
self.init(points: sPts.points)
} else {
return nil
}
}
}
extension ScribblePoints : NSSecureCoding {
public static var supportsSecureCoding = true
}
@objc(ScribblePointsValueTransformer)
final class ScribblePointsValueTransformer: ValueTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: ScribblePointsValueTransformer.self))
public static func register() {
let transformer = ScribblePointsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override class func transformedValueClass() -> AnyClass {
return ScribblePoints.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
if let data = value as? Data {
do {
if #available(iOS 11.0, *) {
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = false
let decodeResult = unarchiver.decodeObject(of: [NSArray.self, ScribblePoints.self], forKey: NSKeyedArchiveRootObjectKey)
if let points = decodeResult as? [CGPoint] {
return points
}
} else {
// Fallback on earlier versions
if let data = value as? Data {
if let points = NSKeyedUnarchiver.unarchiveObject(with: data) as? [CGPoint] {
return points
}
}
}
} catch {
}
}
return nil
}
override func transformedValue(_ value: Any?) -> Any? {
if let points = value as? [CGPoint] {
do {
if #available(iOS 11.0, *) {
let data = try NSKeyedArchiver.archivedData(withRootObject: points, requiringSecureCoding: true)
return data
} else {
// Fallback on earlier versions
let data = NSKeyedArchiver.archivedData(withRootObject: points)
return data
}
} catch {
}
}
return nil
}
}
在 CoreData 中,事物的定义方式如下所示。