1

我正在制作一个 iPad 应用程序,用户可以在其中创建带有图像和文本的图形内容。我将其存储在内存中的自定义 UIView 子类数组中。这些视图子类中的每一个都可以有任意数量的包含图像或文本的子视图。

现在我需要将这些保存在设备中。在我探索的过程中,似乎有很多方法可以做到这一点,并且想知道哪种方法最适合这种情况。

4

4 回答 4

2

看起来您正在要求最终成为绘图应用程序的架构设计。这意味着最好它真的取决于您的特定用例,除非您提供非常详细的需求列表,否则无法完全回答。

但总的来说,我可以尝试为您提供一些通用技巧,这些技巧无论如何都必须与您自己的特定细节实现相结合。

此描述将对此类应用程序可能需要的基本用例做出一些假设:

  • 用户可以使用多种工具创建图像以实现结果。这些可以是任何东西,从画笔到文本字段等等
  • 可以保存有关已使用哪些工具创建图片以及此工具如何影响图片当前外观的信息,以便用户稍后编辑图片

说到这里,主要问题是:如何存储您的绘图状态以便以后恢复它

确实有很多方法可以实现它,但我相信其中有两种方法会被认为是“足够干净和有名的”。

NSKeyedArchiver

这不是我最喜欢的(难以维护),但如果你必须处理 UIView,它可能是最快的。NSKeyedArchiver是_

.. NSCoder 的一个具体子类,提供了一种将对象(和标量值)编码为可存储在文件中的与体系结构无关的格式的方法。

它实现了Memento 设计模式,它与Pro Objective-C 设计模式中描述的模式相同,顺便提一下,它提供了一个案例研究,其中有许多与您匹配的最重要的用例:

  • 绘图板允许用用户的手指涂鸦。
    [...]
  • 它允许用户保存涂鸦。
  • 它允许用户打开保存的涂鸦。
    [...]

这是一个带有绘图板的应用程序,您可以在其中用手指画线。你的看起来像是这个的简化版本,用图像和文本代替涂鸦。

那么,在这种特定情况下,使用NSKeyedArchiver的优点是什么?UIView 已经实现了NSCoding 协议,这是归档对象所需的协议。因此,对于您需要存储的大部分信息(坐标、帧大小、背景颜色...),您无需执行任何操作,只需...归档对象。

对于 UIView 之上的任何附加属性(例如:图像的本地路径,因为归档 UIImageView 非常昂贵),您可以查看这篇文章,其中详细解释了您必须做什么才能利用NSKeyedArchiver来存储您的对象状态。

这一切都归结为:

  1. 为您的绘图应用程序将提供的每个工具实现 NSCoding 协议
  2. 跟踪用户创建的子视图(图像、文本...)
  3. 当用户点击“保存”时,循环浏览它们,创建一个存档,并将它们存储到一个有意义的路径中。路径的第一个组件可以是绘图的名称,第二个是工具的名称,第三个是每次使用工具时的 id。喜欢

    // A mountain image
    /<path to you Document dir>/Mountains/Image/1
    
    // A sun
    /<path to you Document dir>/Mountains/Image/2
    
    // The text "Mountain is awesome"
    /<path to you Document dir>/Mountains/Text/1  
    
  4. 然后,您当然必须将绘图名称列表保存在某处,无论是在 plist 文件中还是在NSUserDefault中,以便能够将它们显示给用户,以防他们想要恢复它们进行编辑。

核心数据

这可能是存储对象状态的最简洁和更易于维护的方式,但是会有点困难和麻烦,尤其是当您是第一次使用核心数据时。我不会深入研究 Core Data,但我可以为您提供整个过程的一些指导。基本上:

  1. 您创建了一个数据库模式,它代表您将让用户使用的每个工具。像:一个表格Image,一个表格Text等等
  2. 在每个表格上,您都放置了您需要记住的属性(位置、“文本”的文本颜色、“图像”的图像 URL 等)
  3. 您为用户创建的绘图创建一个表,与工具表具有一对多的关系。该关系表示图中所示的对象。
  4. 根据存储在数据库中的内容初始化您的画布和每个组件
  5. 每次用户点击“保存”时,创建或更新适当的数据库表以反映存储中的当前绘图配置。

这种方法的优点之一是,如果有一天您想要更改工具组件属性或添加新属性,您可以利用模式迁移来提供与新更新的向后兼容性。所以用户仍然可以使用他们的旧图纸。

等等等等...

这是无数种可能性中的两种。您还可以使用:

  • NSUSerDefault存储状态,我建议避免。真的很难维护
  • 上述两种技术的混合
  • 如果您计划提供 >= iOS6 仅支持,您可以查看
  • ETC

我所描述的这两种方式正是我认为通常和讨论最多的方式。你可以在书籍、教程中找到它们,它们让你可以灵活地完成任何你必须做的事情。
如果您需要更多解释性链接,请告诉我。

于 2013-08-08T13:45:43.447 回答
1

正如我在评论中提到的,您可能想查看 iOS 的状态保存 API。但是,如果您想构建自己的系统来执行此操作,则使用一些巧妙的类别和字典会非常简单。然后你可以使用 NSKeyedArchiver 和 NSKeyedUnarchiver 序列化/反序列化你的字典。

例如:

@interface UIButton (MyAppCategory)

- (NSDictionary *)viewProperties;
- (void)configureFromProperties: (NSDictionary *) properties;

@end

@implementation UIButton (MyAppCategory)

- (NSDictionary *)viewProperties {
    return @{ @"class" : NSStringFromClass([self class]),
              @"frame" : [NSValue valueWithRect:self.frame],
              @"titleLabelText" : self.titleLabel.text,
              // etc...
            };
}

- (void)configureFromProperties: (NSDictionary *) properties {
    NSValue * value = properties[@"frame"];
    if ([value isKindOfClass:[NSValue class]]) {
        self.frame = value.rectValue;
    }
    NSSString * titleLabelText = properties[@"titleLabelText"];
    if ([titleLabelText isKindOfClass:[NSString class]]) {
        self.titleLabel.text = titleLabelText;
    }
}

@end

// replicate the above pattern for other view objects you need to support


@implementation MyViewFactory

- (UIView)recreateViewFromProperties: (NSDictionary *) properties {
    NSString * className = properties[@"class"];
    if ([className isKindOfClass:[NSString class]]) {
        Class viewClass = NSClassFromString(className);
        id viewObject = [[viewClass alloc] init];
        if ([viewObject respondsToSelector:@selector(configureFromProperties:)]]) {
            [viewObject performSelector:@selector(configureFromProperties:) withObject:properties];
            return viewObject;
        }
    }
    return nil;
}

// exercise for the reader: iterate your views and use the viewProperties: method to collect your views' configuration info...

@end
于 2013-08-01T16:40:30.660 回答
1

如果您想允许将来的会话编辑和加载等。我建议设计一个数据结构并从中创建一个核心数据模型。

一些保存会话元数据的结构,例如 sessionID、creationDate、key 字典:imageName 值:imageFrame(CGRect 包装在 NSValue 中,使用 setObjectForKey)。

为会话加载图像将通过使用 eg[sessionImageDictionary allKeys] 将键调用到数组中来工作,遍历键并异步(NSOperationQueue with maxConcurrentOperationCount)将图像加载到某个宏路径到例如库目录,并附加键,这是图像名称。

在同一迭代中,您可以通过调用[sessionImageDictionary valueForKey:[arrayOfKeys objectAtIndex:currentIteration];将先前存储的 NSValue 转换回 CGRect 来设置其框架。

数据结构完全取决于您想要的功能数量,但好处是它允许扩展并且以核心数据作为后备存储,您可以执行设备之间同步等操作,启用多个会话进行加载和保存,如“我的项目”功能。如果假设用户建立一个图像库(全部存储在您的应用程序库目录中)然后用户在同一个会话或多个会话中使用相同的图像,这将有所帮助,只需要存在一个图像副本,零重复写入磁盘,核心数据对象将文件名存储在会话中。

最重要的部分是构建一个正确的核心数据模型并编写一个可以接受这些自定义子类的提取器,剥离数据以创建、填充和保存 NSManagedObject 到持久存储。

于 2013-08-04T10:09:00.017 回答
1

您最好的选择是UIDocumentNSFileWrapper文件夹一起使用。然后,您可以将所有文件存储在一个文件夹中,该文件夹会在内容更改时自动保存。参考:http: //developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/DocumentBasedAppPGiOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011149-CH1-SW1

于 2013-08-07T23:48:04.043 回答