应用设置与Cover Flow效果实现
1. 应用设置基础
应用的设置或偏好由键(key)和值(value)两部分组成。键用于唯一标识设置,例如
musicVolume
;值则是与该键关联存储的数据。处理设置主要涉及在属性列表中创建和修改键值对。
属性列表是XML格式的文件,用于存储构成应用设置的键值对。对于苹果开发者来说并不陌生,因为大多数最新版本的Mac OS X都支持,并且可以直接通过字典类进行读写,无需解析文件。
2. 字典与属性列表操作
-
字典创建
:可以使用多种方式创建字典,最简单的是使用
NSMutableDictionary类:
NSMutableDictionary *dict = [ [ NSMutableDictionary alloc ] init ];
-
键管理
:
- 添加键值对:
[ dict setValue: @"myValue" forKey: @"myKey" ];
[ dict setObject: myObject forKey: @"myKey" ];
- 删除键值对:
[ dict removeObjectForKey: @"myKey" ];
[ dict removeAllObjects ];
-
属性列表写入
:调用字典的
writeToFile方法将属性列表写入磁盘:
[ dict writeToFile: path atomically: YES ];
如果以原子方式写入文件,属性列表会先写入临时文件,然后重命名为指定路径。这在应用的其他部分可能正在读取属性列表时很有用,可防止另一个线程加载部分写入的属性列表导致崩溃。
-
属性列表读取
:使用
NSDictionary
类的
dictionaryWithContentsOfFile
方法读取属性列表:
NSString path = [ NSString stringWithFormat:
@"%@/Library/Preferences/bookmarks.plist", NSHomeDirectory()
];
NSMutableDictionary *dict = [ [ NSMutableDictionary alloc ]
initWithContentsOfFile: path
];
如果字典对象已存在于内存中,使用
setDictionary
方法替换其内容:
[ dict setDictionary: [ NSDictionary dictionaryWithContentsOfFile: path ] ];
3. 偏好设置包
对于大多数应用,全局设置可以(并且应该)在iPhone的“设置”应用中进行更改。创建偏好设置包后,应用的图标将出现在“设置”应用的底部,为用户提供一个集中且标准化的地方来编辑应用设置。
-
添加偏好设置包 :
- 在Xcode中打开项目,选择“文件”菜单中的“新建文件”。
- 从iPhone OS组中选择“设置”类别,然后选择“设置包”文件。
-
命名文件为
Settings.bundle并添加到项目中。
-
编辑设置 :打开
Root.plist文件,默认情况下会在属性列表编辑器中显示,可轻松添加新的键值对。若要处理文件的原始内容,右键单击文件并从“打开方式”菜单中选择“纯文本文件”。 -
添加键 :偏好设置包中的每个条目由属性列表的
PreferenceSpecifiers部分下的字典表示。添加新行的方法有两种:-
右键单击
PreferenceSpecifiers数组并选择“添加行”,然后右键单击新项并从“值类型”菜单中选择“字典”。 -
选择现有项并复制,再粘贴到
PreferenceSpecifiers数组中。
-
右键单击
-
不同类型的设置项 :
-
组分隔符
:用于将偏好设置表划分为逻辑组。添加一个名为
Type的键,值为PSGroupSpecifier;再添加一个名为Title的键,值为组标签名称。
-
组分隔符
:用于将偏好设置表划分为逻辑组。添加一个名为
<array>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string>Main Settings</string>
</dict>
</array>
- **文本字段**:允许用户直接输入,如用户名或密码。需要添加的键包括 `Type`(值为 `PSTextFieldSpecifier`)、`Title`、`Key`、`DefaultValue`、`IsSecure`、`KeyboardType`、`AutocapitalizationType` 和 `AutoCorrectionType`。
<dict>
<key>Type</key>
<string>PSTextFieldSpecifier</string>
<key>Title</key>
<string>Username</string>
<key>Key</key>
<string>username_preference</string>
<key>DefaultValue</key>
<string></string>
<key>IsSecure</key>
<false/>
<key>KeyboardType</key>
<string>Alphabet</string>
<key>AutocapitalizationType</key>
<string>None</string>
<key>AutocorrectionType</key>
<string>No</string>
</dict>
- **切换开关**:在偏好设置面板中显示一个 `UISwitch`,用户可以设置为开或关。需要添加的键包括 `Type`(值为 `PSToggleSwitchSpecifier`)、`Title`、`Key`、`TrueValue`、`FalseValue` 和 `DefaultValue`。
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Extra Points</string>
<key>Key</key>
<string>extrapoints_preference</string>
<key>DefaultValue</key>
<false/>
<key>TrueValue</key>
<string>YES</string>
<key>FalseValue</key>
<string>NO</string>
</dict>
- **滑块**:在偏好设置表中绘制一个 `UISlider`,允许用户大致指定一个值范围。需要添加的键包括 `Type`(值为 `PSSliderSpecifier`)、`Key`、`MinimumValue`、`MaximumValue`、`DefaultValue`、`MinimumValueImage` 和 `MaximumValueImage`。
<dict>
<key>Type</key>
<string>PSSliderSpecifier</string>
<key>Key</key>
<string>musicvolume_preference</string>
<key>DefaultValue</key>
<real>5.0</real>
<key>MinimumValue</key>
<real>0.0</real>
<key>MaximumValue</key>
<real>10.0</real>
<key>MinimumValueImage</key>
<string>minvalue.png</string>
<key>MaximumValueImage</key>
<string>maxvalue.png</string>
</dict>
- **多值字段**:类似于“设置”应用中的下拉菜单,允许用户从列表中选择一个选项。需要添加的键包括 `Type`(值为 `PSMultiValueSpecifier`)、`Key`、`Title`、`Titles`、`Values` 和 `DefaultValue`。
<dict>
<key>Type</key>
<string>PSMultiValueSpecifier</string>
<key>Title</key>
<string>Difficulty</string>
<key>Key</key>
<string>difficulty_preference</string>
<key>Values</key>
<array>
<string>1</string>
<string>2</string>
<string>3</string>
</array>
<key>Titles</key>
<array>
<string>Easy</string>
<string>Medium</string>
<string>Hard</string>
</array>
<key>DefaultValue</key>
<string>2</string>
</dict>
- **子面板**:允许指定额外的子面板。用户点击子面板时,“设置”应用将滚动到新面板并显示其选项。需要添加的键包括 `Type`(值为 `PSChildPaneSpecifier`)、`Key`、`Title` 和 `File`。
<dict>
<key>Type</key>
<string>PSChildPaneSpecifier</string>
<key>Title</key>
<string>Extended Preferences</string>
<key>Key</key>
<string>extended_preferences</string>
<key>File</key>
<string>Extended</string>
</dict>
-
读取偏好设置包的值
:可以使用
NSUserDefaults类来访问属性列表设置:
NSString *difficulty = [ [ NSUserDefaults standardUserDefaults ]
stringForKey:@"difficulty_preference" ];
也可以将包含所有偏好的属性列表直接加载到
NSDictionary
对象中:
NSDictionary *settings = [ NSUserDefaults standardUserDefaults
dictionaryRepresentation ];
NSLog(@"Difficulty: %@\n", [ settings valueForKey: @"difficulty_preference" ]);
4. Cover Flow效果实现
苹果的Cover Flow技术是基于Quartz Core框架和Core Animation实现的,但苹果将其Cover Flow类私有化,无法在SDK中使用。不过可以通过以下方式实现类似效果。
创建一个名为
CFView
的
UIScrollView
子类,该类使用Core Animation在iPhone屏幕上创建类似Cover Flow的布局。当手指滚动时,
UIScrollView
类会调用其委托的
scrollViewDidScroll
方法,计算哪个封面位于屏幕中心并将其旋转到视图中。
以下是相关代码示例:
-
CovertFlowAppDelegate.h
#import <UIKit/UIKit.h>
@class CovertFlowViewController;
@interface CovertFlowAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
CovertFlowViewController *viewController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet CovertFlowViewController *viewController;
@end
- CovertFlowAppDelegate.m
#import "CovertFlowAppDelegate.h"
#import "CovertFlowViewController.h"
@implementation CovertFlowAppDelegate
@synthesize window;
@synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
CGRect screenBounds = [ [ UIScreen mainScreen ] bounds ];
self.window = [ [ [ UIWindow alloc ] initWithFrame: screenBounds ] autorelease ];
viewController = [ [ CovertFlowViewController alloc ] init ];
[ window addSubview: viewController.view ];
[ window makeKeyAndVisible ];
}
- (void)dealloc {
[ viewController release ];
[ window release ];
[ super dealloc ];
}
@end
- CovertFlowViewController.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
/* Number of pixels scrolled before next cover comes front */
#define SCROLL_PIXELS 60.0
/* Size of each cover */
#define COVER_WIDTH_HEIGHT 128.0
@interface CFView : UIScrollView <UIScrollViewDelegate>
{
CAScrollLayer *cfIntLayer;
NSMutableArray *_covers;
NSTimer *timer;
int selectedCover;
}
- (id) initWithFrame:(struct CGRect)frame covers:(NSMutableArray *)covers;
- (void)layoutLayer:(CAScrollLayer *)layer;
@property(nonatomic,getter=getSelectedCover) int selectedCover;
@end
@interface CovertFlowViewController : UIViewController {
NSMutableArray *covers;
CFView *covertFlowView;
}
@end
- CovertFlowViewController.m
#import "CovertFlowViewController.h"
@implementation CFView
- (id) initWithFrame:(struct CGRect)frame covers:(NSMutableArray *)covers {
self = [ super initWithFrame: frame ];
if (self != nil) {
_covers = covers;
selectedCover = 0;
self.showsVerticalScrollIndicator = YES;
self.showsHorizontalScrollIndicator = NO;
self.delegate = self;
self.scrollsToTop = NO;
self.bouncesZoom = NO;
cfIntLayer = [ [ CAScrollLayer alloc ] init ];
cfIntLayer.bounds = CGRectMake(0.0, 0.0, frame.size.width,
frame.size.height + COVER_WIDTH_HEIGHT);
cfIntLayer.position = CGPointMake(160.0, 304.0);
cfIntLayer.frame = frame;
for(int i = 0; i < [ _covers count ]; i++) {
NSLog(@"Initializing cfIntLayer layer %d\n", i);
UIImageView *background = [ [ [ UIImageView alloc ] initWithImage:
[ _covers objectAtIndex: i ] ] autorelease ];
background.frame = CGRectMake(0.0, 0.0, COVER_WIDTH_HEIGHT,
COVER_WIDTH_HEIGHT);
[ cfIntLayer addSublayer: background.layer ];
}
self.contentSize = CGSizeMake(320.0, ( ( frame.size.height) +
(SCROLL_PIXELS * ([ _covers count ] −1)) ) );
[ self.layer addSublayer: cfIntLayer ];
[ self layoutLayer: cfIntLayer ];
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
selectedCover = (int) roundf((self.contentOffset.y/SCROLL_PIXELS));
if (selectedCover > [ _covers count ] −1) {
selectedCover = [ _covers count ] - 1;
}
[ self layoutLayer: cfIntLayer ];
}
- (void)setSelectedCover:(int)index {
if (index != selectedCover) {
selectedCover = index;
[ self layoutLayer: cfIntLayer ];
self.contentOffset = CGPointMake(self.contentOffset.x, selectedCover *
SCROLL_PIXELS);
}
}
- (int) getSelectedCover {
return selectedCover;
}
-(void) layoutLayer:(CAScrollLayer *)layer
{
CALayer *sublayer;
NSArray *array;
size_t i, count;
CGRect rect, cfImageRect;
CGSize cellSize, spacing, margin, size;
CATransform3D leftTransform, rightTransform, sublayerTransform;
float zCenterPosition, zSidePosition;
float sideSpacingFactor, rowScaleFactor;
float angle = 1.39;
int x;
size = [ layer bounds ].size;
zCenterPosition = 60; /* Z-Position of selected cover */
zSidePosition = 0; /* Default Z-Position for other covers */
sideSpacingFactor = .85; /* How close should slide covers be */
rowScaleFactor = .55; /* Distance between main cover and side covers */
leftTransform = CATransform3DMakeRotation(angle, −1, 0, 0);
rightTransform = CATransform3DMakeRotation(-angle, −1, 0, 0);
margin = CGSizeMake(5.0, 5.0);
spacing = CGSizeMake(5.0, 5.0);
cellSize = CGSizeMake (COVER_WIDTH_HEIGHT, COVER_WIDTH_HEIGHT);
margin.width += (size.width - cellSize.width * [ _covers count ]
- spacing.width * ([ _covers count ] - 1)) * .5;
margin.width = floor (margin.width);
/* Build an array of covers */
array = [ layer sublayers ];
count = [ array count ];
sublayerTransform = CATransform3DIdentity;
/* Set perspective */
sublayerTransform.m34 = −0.006;
/* Begin a CATransaction so that all animations happen simultaneously */
[ CATransaction begin ];
[ CATransaction setValue: [ NSNumber numberWithFloat: 0.3f ]
forKey:@"animationDuration" ];
for (i = 0; i < count; i++)
{
sublayer = [ array objectAtIndex:i ];
x = i;
rect.size = *(CGSize *)&cellSize;
rect.origin = CGPointZero;
cfImageRect = rect;
/* Base position */
rect.origin.x = size.width / 2 - cellSize.width / 2;
rect.origin.y = margin.height + x * (cellSize.height + spacing.height);
[ [ sublayer superlayer ] setSublayerTransform: sublayerTransform ];
if (x < selectedCover) /* Left side */
{
rect.origin.y += cellSize.height * sideSpacingFactor
* (float) (selectedCover - x - rowScaleFactor);
sublayer.zPosition = zSidePosition - 2.0 * (selectedCover - x);
sublayer.transform = leftTransform;
}
else if (x > selectedCover) /* Right side */
{
rect.origin.y -= cellSize.height * sideSpacingFactor
* (float) (x - selectedCover - rowScaleFactor);
sublayer.zPosition = zSidePosition - 2.0 * (x - selectedCover);
sublayer.transform = rightTransform;
}
else /* Selected cover */
{
sublayer.transform = CATransform3DIdentity;
sublayer.zPosition = zCenterPosition;
/* Position in the middle of the scroll layer */
[ layer scrollToPoint: CGPointMake(0, rect.origin.y
- (([ layer bounds ].size.height - cellSize.width)/2.0))
];
/* Position the scroll layer in the center of the view */
layer.position =
CGPointMake(160.0f, 240.0f + (selectedCover * SCROLL_PIXELS));
}
[ sublayer setFrame: rect ];
}
[ CATransaction commit ];
}
@end
@implementation CovertFlowViewController
- (id)init {
self = [ super init ];
if (self != nil) {
covers = [ [ NSMutableArray alloc ] init ];
for(int i = 1; i < 6; i++) {
NSLog(@"Loading demo image %d\n", i);
UIImage *image = [ [ UIImage alloc ] initWithData:
[ NSData dataWithContentsOfURL:
[ NSURL URLWithString: [ NSString stringWithFormat:
@"http://www.zdziarski.com/demo/%d.png", i ] ] ]
];
[ covers addObject: image ];
}
}
return self;
}
- (void)loadView {
[ super loadView ];
covertFlowView = [ [ CFView alloc ] initWithFrame:
self.view.frame
covers: covers
];
covertFlowView.selectedCover = 2;
self.view = covertFlowView;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)didReceiveMemoryWarning {
[ super didReceiveMemoryWarning ];
}
@end
通过以上步骤和代码,我们可以实现应用设置的管理以及类似Cover Flow的效果。在实际开发中,可以根据具体需求对代码进行调整和优化。
应用设置与Cover Flow效果实现(续)
5. 不同类型设置项的对比
为了更清晰地了解偏好设置包中不同类型设置项的特点,下面通过表格进行对比:
| 设置项类型 | 功能特点 | 所需键及作用 | 示例用途 |
| — | — | — | — |
| 组分隔符 | 将偏好设置表划分为逻辑组 |
Type
(值为
PSGroupSpecifier
)用于标识,
Title
用于显示组标签名称 | 区分不同类别的设置,如将基本设置和高级设置分组 |
| 文本字段 | 允许用户直接输入数据,如用户名、密码等 |
Type
(值为
PSTextFieldSpecifier
)、
Title
(显示在字段左侧)、
Key
(代表实际值的名称)、
DefaultValue
(默认值)、
IsSecure
(是否为安全文本)、
KeyboardType
(键盘类型)、
AutocapitalizationType
(自动大写类型)和
AutoCorrectionType
(自动更正类型) | 让用户输入登录信息、个人资料等 |
| 切换开关 | 提供布尔类型的选择,用户可设置为开或关 |
Type
(值为
PSToggleSwitchSpecifier
)、
Title
(显示在字段左侧)、
Key
(代表实际值的名称)、
TrueValue
(开关打开时的值)、
FalseValue
(开关关闭时的值)和
DefaultValue
(默认开关状态) | 启用或禁用某些功能,如开启夜间模式、消息提醒等 |
| 滑块 | 允许用户大致指定一个值范围 |
Type
(值为
PSSliderSpecifier
)、
Key
(代表实际值的名称)、
MinimumValue
(滑块最小值)、
MaximumValue
(滑块最大值)、
DefaultValue
(默认值)、
MinimumValueImage
和
MaximumValueImage
(滑块两端显示的图片) | 调整音量、亮度、游戏难度等 |
| 多值字段 | 类似于下拉菜单,用户可从列表中选择一个选项 |
Type
(值为
PSMultiValueSpecifier
)、
Key
(代表实际值的名称)、
Title
(显示在字段左侧)、
Titles
(显示给用户的可选项目列表)、
Values
(与
Titles
对应的实际值列表)和
DefaultValue
(默认选择的值) | 选择游戏难度级别、语言选项等 |
| 子面板 | 允许指定额外的子面板,用户点击可查看更多设置选项 |
Type
(值为
PSChildPaneSpecifier
)、
Key
(子面板唯一标识)、
Title
(子面板显示的标题)和
File
(包含子面板设置的属性列表文件名,不含
.plist
扩展名) | 展开显示更详细的设置内容,如高级设置子面板 |
6. Cover Flow效果实现的流程梳理
下面通过 mermaid 流程图来梳理实现 Cover Flow 效果的主要流程:
graph TD;
A[创建 CFView 子类] --> B[初始化 CFView 并传入封面图像数组];
B --> C[使用 Core Animation 创建布局];
C --> D[手指滚动触发 scrollViewDidScroll 方法];
D --> E[计算中心封面并旋转到视图中];
E --> F[更新界面显示];
7. 代码优化建议
-
应用设置部分
-
错误处理
:在写入和读取属性列表时,添加错误处理代码。例如,在调用
writeToFile方法时,检查返回值以确保文件写入成功;在调用initWithContentsOfFile方法时,检查字典是否成功初始化。
-
错误处理
:在写入和读取属性列表时,添加错误处理代码。例如,在调用
BOOL success = [dict writeToFile:path atomically:YES];
if (!success) {
NSLog(@"Failed to write property list to file: %@", path);
}
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
if (dict == nil) {
NSLog(@"Failed to read property list from file: %@", path);
}
- **数据验证**:在添加键值对时,对输入的数据进行验证,确保数据的合法性。例如,对于文本字段,检查输入是否符合特定的格式要求。
-
Cover Flow 效果部分
-
性能优化
:在
layoutLayer方法中,避免频繁的计算和重绘。可以考虑缓存一些不变的计算结果,减少不必要的重复计算。 -
内存管理
:在加载图片时,及时释放不再使用的图片资源,避免内存泄漏。可以使用
UIImage的imageWithData方法来减少内存占用。
-
性能优化
:在
8. 实际应用场景拓展
-
应用设置
- 个性化体验 :根据用户在设置中选择的偏好,如主题颜色、字体大小等,为用户提供个性化的应用界面。
- 数据同步 :结合云服务,将用户的设置数据同步到不同的设备上,实现多设备间的一致体验。
-
Cover Flow 效果
- 媒体展示 :在音乐、视频应用中,使用 Cover Flow 效果展示专辑封面、视频海报等,增强用户的视觉体验。
- 商品展示 :在电商应用中,使用 Cover Flow 效果展示商品图片,让用户更直观地浏览商品。
通过以上对应用设置和 Cover Flow 效果实现的深入探讨,我们不仅了解了相关的技术原理和实现方法,还探讨了优化建议和实际应用场景拓展。在实际开发中,我们可以根据具体需求灵活运用这些知识,为用户打造更加优质的应用体验。
超级会员免费看
8

被折叠的 条评论
为什么被折叠?



