хранить массив, содержащий CGPoints, в базе данных CoreData (Swift)

так как в заголовке уже указано, что я пытаюсь сохранить массив в базе данных. Если это возможно, как мне это сделать? Если нет, я надеюсь, что вы можете помочь мне с другим решением.

Я делаю приложение для iOS, в котором, если пользователь касается (и перемещает) экран, я сохраняю эти данные в массиве. Поскольку это должно быть мультитач, все CGPoints касаний (либо touchesBegan, либо touchesMoved) в один момент сохраняются в массиве, который снова хранится в основном массиве. В результате var everyLocation = [[CGPoint]](). Я уже выяснил, что невозможно напрямую хранить CGPoint в базе данных, поэтому я могу преобразовать их в строку с помощью NSStringFromCGPoint(pointVariable). Это, однако, не очень полезно, пока я не могу сохранить массив... Я также хочу сохранить дату, когда это произошло, поэтому в своей базе данных я создал объект «Местоположения» с двумя атрибутами: «Местоположения» и «дата». В окончательном приложении имя объекта будет названием упражнения, которое выполнял пользователь (у меня около четырех упражнений, поэтому четыре объекта). Большая часть кода, который я видел, хранит CGPoint либо в отдельных x и y, либо в одной строке. Возможно, я тоже могу это сделать, поэтому мне не нужно хранить массивы. Чтобы сделать это, я думаю, мне нужно будет сделать атрибут(ы) координатами прикосновения(ов), имя объекта будет датой, а имя базы данных будет названием упражнения. Если это единственное решение, как мне создать объект (с атрибутами) во время выполнения?

заранее спасибо


person Fr4nc3sc0NL    schedule 28.02.2015    source источник


Ответы (5)


1) добавить атрибут типа «Трансформируемый».

введите здесь описание изображения

2) Событие.ч

@interface Event : NSManagedObject

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

@end

Событие.м

@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)")
 } 
person William Hu    schedule 28.02.2015
comment
Извините, но у меня совсем нет опыта работы с Objective-C... не могли бы вы дать мне представление о том, что делает Event.m? (Я предполагаю, что event.h настраивает переменные и 3) сохраняет их в БД) - person Fr4nc3sc0NL; 28.02.2015
comment
Если это то, что я предоставляю, приносит помощь, пожалуйста, примите это как ответ :) - person William Hu; 01.03.2015

Swift3 делает это без проблем, просто напишите

typealias Point = CGPoint

и установите тип атрибута на Transformable и установите его пользовательский класс на

Array<Point>

Работает для меня, ничего не делая.

person Fabian    schedule 31.05.2017

Мне, наконец, удалось собрать все воедино после того, как Уильям указал мне на трансформируемые автомобили. Я использовал этот учебник, чтобы понять, как работать с этим: http://jamesonquave.com/blog/core-data-in-swift-tutorial-part-1/

person Fr4nc3sc0NL    schedule 01.03.2015
comment
Помогите пожалуйста мне. Я застрял с той же проблемой. Я пытаюсь сохранить массив телефонных номеров. - person Ankita Shah; 04.03.2015
comment
просто следуйте этому руководству, оно содержит все, что вам нужно знать. - person Fr4nc3sc0NL; 08.03.2015

Вот что я узнал из этого упражнения, которое было вызвано предупреждающим сообщением: в какой-то момент Core Data по умолчанию будет использовать NSSecureUnarchiveFromData, когда указано nil, и трансформируемые свойства, содержащие классы, которые не поддерживают NSSecureCoding, станут нечитаемыми.

Мое приложение собирало серию точек [CGPoint], созданных путем рисования на экране с помощью Apple Pencil или пальца, и сохраняло их в CoreData — по сути, это сердце того, что я назвал Scribble. Для хранения в CoreData я создал атрибут с именем «points» и установил тип Transformable. Пользовательский класс был установлен на [CGPoint]. Кроме того, я установил для CodeGen значение «Вручную», а не автоматическую опцию «Определение класса». Когда я сгенерировал файлы подкласса управляемых объектов CoreData, он генерирует файл +CoreDataClass.swift с критической интересующей строкой:

@NSManaged public var points: [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
    }

}

Имея все вышеперечисленное, я смог, наконец, заполнить ScribblePointsValueTransformer для имени Transformer для атрибута «points» в CoreData.

Также можно переключить пользовательский класс с [CGPoint] на ScribblePoints. Похоже, это не влияет на выполнение кода. Однако, если вы повторно сгенерируете файл +CoreDataClass.swift, критической интересующей строкой станет:

@NSManaged public var points: ScribblePoints?

и когда вы перекомпилируете, вам нужно будет внести изменения в код, чтобы справиться с другим определением. Если вы начинали с нуля, возможно, вы захотите просто использовать определение ScribblePoints и избежать неприятностей, связанных с NSArrays и NSPoints и другими вещами, с которыми вы волшебным образом сталкиваетесь странными способами с [CGPoint].

Выше было со Swift 5.

person anorskdev    schedule 10.05.2021

Наткнулся на предупреждающее сообщение с моим ответом выше, когда я подключил более старое устройство 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 Scribble

person anorskdev    schedule 18.05.2021