简体   繁体   English

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

[英]store Array containing CGPoints in CoreData database (Swift)

so as the title already states I'm trying to save an array to the database.所以标题已经说明我正在尝试将数组保存到数据库中。 If this is possible, how do I do it?如果这是可能的,我该怎么做? If not I hope you can help me with some other solution.如果没有,我希望你能帮我解决一些其他的问题。

I am making an iOS app where if the user touches (and moves) the screen I store this data in an array.我正在制作一个 iOS 应用程序,如果用户触摸(并移动)屏幕,我会将这些数据存储在一个数组中。 Because it needs to be multi-touch all the CGPoints of the touches (either touchesBegan or touchesMoved) on one moment are stored in an array, which again is stored in the main array.因为它需要多点触摸,所以某一时刻触摸的所有 CGPoints(touchesBegan 或 touchesMoved)都存储在一个数组中,该数组再次存储在主数组中。 Resulting in var everyLocation = [[CGPoint]]() .导致var everyLocation = [[CGPoint]]() I already found out that it's not possible to store a CGPoint in a database directly, so I can convert them to string with NSStringFromCGPoint(pointVariable) .我已经发现无法将 CGPoint 直接存储在数据库中,因此我可以使用NSStringFromCGPoint(pointVariable)将它们转换为字符串。 This however isn't very useful as long as I can't store the array... I want to store the date on which it happened too, so in my database I created the entity 'Locations' with two attributes: 'locations' and 'date'.然而,只要我不能存储数组,这并不是很有用......我也想存储它发生的日期,所以在我的数据库中我创建了具有两个属性的实体“位置”:“位置”和“日期”。 In the final application the entity name will be the name of the exercise the user was doing (I have about four exercises, so four entities).在最终的应用程序中,实体名称将是用户正在做的练习的名称(我有大约四个练习,所以有四个实体)。 Most of the sample code I've seen stores the CGPoint either in a separate x and y or in one string.我见过的大多数示例代码都将 CGPoint 存储在单独的 x 和 y 或一个字符串中。 I can maybe do this too, so I don't have to store arrays.我也许也可以这样做,所以我不必存储数组。 To do this I think I will have to make the attribute(s) the coordinates of the touche(s), the entity name would be the date, and the db name would be the name of the exercise.为此,我想我必须使属性成为触摸的坐标,实体名称将是日期,数据库名称将是练习的名称。 If this is the only solution, how do I create an entity (with attributes) at run-time?如果这是唯一的解决方案,我如何在运行时创建实体(带有属性)?

Thanks in advance提前致谢

Swift3 makes it seamless, just write Swift3 使它无缝,只需编写

typealias Point = CGPoint

and set the attribute type to Transformable and set the Custom class of it to并将属性类型设置为 Transformable 并将其自定义类设置为

Array<Point>

Works for me without having to do anything.无需做任何事情即可为我工作。

1) add a "Transformable" type attribute. 1)添加“可转换”类型属性。

在此处输入图片说明

2) Event.h 2) 事件.h

@interface Event : NSManagedObject

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

@end

Event.m事件.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) Just use it as a normal array 3)只需将其用作普通数组

Event *event = //init
event.absentArray = @[1,2,3];
[context save:nil]

Just change these code in swift.只需快速更改这些代码即可。

You can understand as .swfit combine .h/.m file.你可以理解为 .swfit 结合 .h/.m 文件。 Objective C has .h as header file which many properties there.目标 C 有 .h 作为头文件,其中有许多属性。 .m is implication file which methods should be there. .m 是隐含文件,哪些方法应该在那里。

For example: .swift例如:.swift

import Foundation
import CoreData

class Event: NSManagedObject {

    @NSManaged var absentArray: AnyObject

}

3) save: 3)保存:

let appDelegate =
  UIApplication.sharedApplication().delegate as AppDelegate

  let managedContext = appDelegate.managedObjectContext!
  if !managedContext.save(&error) {
  println("Could not save \(error), \(error?.userInfo)")
 } 

I finally managed to put the pieces together after William pointed me in the direction of transformables.在威廉向我指出可变形的方向后,我终于设法将这些碎片拼凑起来。 I used this tutorial to understand how to work with this: http://jamesonquave.com/blog/core-data-in-swift-tutorial-part-1/我使用本教程来了解如何使用它: http : //jamesonquave.com/blog/core-data-in-swift-tutorial-part-1/

Here are the things I learned from going through this exercise that was prompted by the warning message: At some point, Core Data will default to using "NSSecureUnarchiveFromData" when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable.以下是我从警告消息提示的练习中学到的东西:在某些时候,Core Data 将在指定 nil 时默认使用“NSSecureUnarchiveFromData”,并且包含不支持 NSSecureCoding 的类的可转换属性将变得不可读.

My app had collected the series of points [CGPoint] created by drawing on the screen with an Apple Pencil or finger and stored that in CoreData - basically the heart of a thing I called a Scribble.我的应用程序收集了通过使用 Apple Pencil 或手指在屏幕上绘制而创建的一系列点 [CGPoint],并将其存储在 CoreData 中 - 基本上是我称为 Scribble 的东西的核心。 To store in CoreData, I created an attribute named “points” and set the type to Transformable.为了存储在 CoreData 中,我创建了一个名为“points”的属性并将类型设置为 Transformable。 The Custom Class was set to [CGPoint].自定义类设置为 [CGPoint]。 Also, I set CodeGen to Manual rather than the automatic “Class Definition” option.此外,我将 CodeGen 设置为手动而不是自动的“类定义”选项。 When I generated the CoreData managed object subclass files, it generates a +CoreDataClass.swift file with the critical line of interest being:当我生成 CoreData 托管对象子类文件时,它会生成一个 +CoreDataClass.swift 文件,感兴趣的关键行是:

@NSManaged public var points: [CGPoint]? @NSManaged 公共变量点:[CGPoint]?

It should be noted, that there is actually a problem if you use the automatic option as the file that is generated doesn't know what a CGPoint is and cannot be edited to add the import for UIKit for it to find the definition.需要注意的是,如果使用自动选项实际上是有问题的,因为生成的文件不知道 CGPoint 是什么,并且无法编辑以添加 UIKit 的导入以查找定义。

This worked fine until Apple started wanting to encourage secure coding.这一直很好,直到 Apple 开始想要鼓励安全编码。 In the code file below, I developed a ScribblePoints object to work with the encoding and its associated data transformer.在下面的代码文件中,我开发了一个 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
    }

}

With the above in place, I could finally fill in ScribblePointsValueTransformer for the Transformer name for the “points” attribute in CoreData.有了上面的内容,我终于可以为 CoreData 中的“points”属性的 Transformer 名称填写 ScribblePointsValueTransformer。

One can also switch the Custom Class from [CGPoint] to ScribblePoints.还可以将自定义类从 [CGPoint] 切换到 ScribblePoints。 This doesn't appear to affect code execution.这似乎不会影响代码执行。 However, if you re-generate the +CoreDataClass.swift file, the critical line of interest will become:但是,如果您重新生成 +CoreDataClass.swift 文件,感兴趣的关键行将变为:

@NSManaged public var points: ScribblePoints? @NSManaged 公共变量点:ScribblePoints?

and when you re-compile you will have code changes to make to deal with the different definition.当您重新编译时,您将需要更改代码以处理不同的定义。 If you were starting from scratch, it seems you may want to simply use the ScribblePoints definition, and avoid the hassles of dealing with NSArrays and NSPoints and other stuff you magically encounter in strange ways with [CGPoint].如果您是从头开始,似乎您可能只想使用 ScribblePoints 定义,并避免处理 NSArrays 和 NSPoints 以及您使用 [CGPoint] 以奇怪的方式神奇地遇到的其他东西的麻烦。

Above was with Swift 5.上面是 Swift 5。

Ran into a warning message with my answer above when I hooked up an older iOS device (iOS9) to Xcode.当我将旧的 iOS 设备 (iOS9) 连接到 Xcode 时,我的回答出现了一条警告消息。 Things worked, but the warning message about not finding the value transformer was disturbing.一切正常,但有关未找到值转换器的警告信息令人不安。 The problem was that the previous answer only defined and registered the value transformer if you were on iOS12+.问题是,如果您使用的是 iOS12+,则之前的答案仅定义并注册了值转换器。 To work without complaint on earlier systems, one needs to avoid the NSSecureUnarchiveFromDataTransformer, use ValueTransformer instead, and rely on the NSSecureCoding conformance for your coding object.为了在早期系统上毫无怨言地工作,需要避免使用 NSSecureUnarchiveFromDataTransformer,而是使用 ValueTransformer,并依赖 NSSecureCoding 一致性来实现您的编码对象。 Then you can register your value transformer on older iOS systems.然后您可以在较旧的 iOS 系统上注册您的值转换器。 It should also be noted that the transformedValue() and reverseTransformedValue() functions became reversed.还应该注意的是,transformedValue() 和 reverseTransformedValue() 函数被颠倒了。

The net result is the following code instead.最终结果是以下代码。

//
//  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
    }

}

In CoreData, the way things are defined is shown below.在 CoreData 中,定义事物的方式如下所示。

CoreData 涂鸦定义

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM