在 CoreData 数据库中存储包含 CGPoints 的数组(Swift)

Posted

技术标签:

【中文标题】在 CoreData 数据库中存储包含 CGPoints 的数组(Swift)【英文标题】:store Array containing CGPoints in CoreData database (Swift) 【发布时间】:2015-02-28 01:35:04 【问题描述】:

正如标题已经说明的那样,我正在尝试将数组保存到数据库中。如果这是可能的,我该怎么做?如果没有,我希望你能帮助我解决其他问题。

我正在制作一个 ios 应用程序,如果用户触摸(并移动)屏幕,我会将这些数据存储在一个数组中。因为它需要多点触摸,所以所有触摸的 CGPoints(touchesBegan 或 touchesMoved)都存储在一个数组中,该数组再次存储在主数组中。导致var everyLocation = [[CGPoint]]()。我已经发现无法将 CGPoint 直接存储在数据库中,因此我可以使用 NSStringFromCGPoint(pointVariable) 将它们转换为字符串。但是,只要我不能存储数组,这并不是很有用...... 我也想存储它发生的日期,所以在我的数据库中我创建了具有两个属性的实体“位置”:“位置”和“日期”。在最终的应用程序中,实体名称将是用户正在执行的练习的名称(我有大约四个练习,所以四个实体)。 我见过的大多数示例代码将 CGPoint 存储在单独的 x 和 y 或一个字符串中。我也许也可以这样做,所以我不必存储数组。为此,我认为我必须将属性设置为触摸的坐标,实体名称将是日期,数据库名称将是练习的名称。如果这是唯一的解决方案,我如何在运行时创建一个实体(带有属性)?

提前致谢

【问题讨论】:

【参考方案1】:

Swift3 使其无缝, 随便写

typealias Point = CGPoint

并将属性类型设置为 Transformable 并将其自定义类设置为

Array<Point>

为我工作,无需做任何事情。

【讨论】:

【参考方案2】:

1) 添加“Transformable”类型属性。

2) 事件.h

@interface Event : NSManagedObject

@property (nonatomic, retain) NSArray * absentArray;
@interface AbsentArray : NSValueTransformer

@end

事件.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]

只需快速更改这些代码。

你可以理解为 .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)")
  

【讨论】:

很抱歉,我完全没有使用 Objective-C 的经验……你能告诉我 Event.m 是做什么的吗? (我猜 event.h 正在设置变量并 3)将其保存到数据库) 如果这是我提供的帮助,请接受它作为答案:)【参考方案3】:

在威廉向我指出可变形的方向后,我终于设法将这些碎片拼凑在一起。我使用本教程来了解如何使用它:http://jamesonquave.com/blog/core-data-in-swift-tutorial-part-1/

【讨论】:

请帮帮我。我遇到了同样的问题。我正在尝试存储电话号码数组。 按照该教程进行操作,它提供了您需要了解的内容。【参考方案4】:

以下是我从警告消息提示的这个练习中学到的东西: 在某些时候,当指定 nil 时,Core Data 将默认使用“NSSecureUnarchiveFromData”,并且包含不支持 NSSecureCoding 的类的可转换属性将变得不可读。

我的应用程序收集了通过用 Apple Pencil 或手指在屏幕上绘图创建的一系列点 [CGPoint],并将其存储在 CoreData 中 - 基本上是我称之为 Scribble 的东西的核心。为了存储在 CoreData 中,我创建了一个名为“points”的属性并将类型设置为 Transformable。自定义类设置为 [CGPoint]。另外,我将 CodeGen 设置为 Manual 而不是自动的“Class Definition”选项。当我生成 CoreData 托管对象子类文件时,它会生成一个 +CoreDataClass.swift 文件,其中关键行是:

@NSManaged 公共变量点:[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。

还可以将自定义类从 [CGPoint] 切换到 ScribblePoints。这似乎不会影响代码执行。但是,如果您重新生成 +CoreDataClass.swift 文件,则感兴趣的关键行将变为:

@NSManaged public var points: ScribblePoints?

当您重新编译时,您将需要更改代码以处理不同的定义。如果您从头开始,似乎您可能只想简单地使用 ScribblePoints 定义,并避免处理 NSArrays 和 NSPoints 以及您在使用 [CGPoint] 时以奇怪的方式神奇地遇到的其他东西的麻烦。

以上是 Swift 5。

【讨论】:

【参考方案5】:

当我将较旧的 iOS 设备 (iOS9) 连接到 Xcode 时,我在上面的回答中遇到了一条警告消息。事情奏效了,但是关于找不到价值转换器的警告信息令人不安。问题是,如果您在 iOS12+ 上,之前的答案仅定义和注册了值转换器。要在早期系统上毫无怨言地工作,需要避免使用 NSSecureUnarchiveFromDataTransformer,而是使用 ValueTransformer,并依赖于编码对象的 NSSecureCoding 一致性。然后,您可以在较旧的 iOS 系统上注册您的价值转换器。还需要注意的是,transformValue() 和 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中,事物的定义方式如下所示。

【讨论】:

以上是关于在 CoreData 数据库中存储包含 CGPoints 的数组(Swift)的主要内容,如果未能解决你的问题,请参考以下文章

CoreData 多个 Fe​​tch 请求

iOS8 扩展:将 CoreData 与包含的应用程序同步

CoreData - NSManagedObject 子对象一次只存储在一个对象中

Swift下CoreData的使用

在 CoreData 中存储 NSMutableAttributedString 的简单方法

在内存中缓存 CoreData 存储