├── DZTableView.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── DZTableView.xccheckout
│ └── xcuserdata
│ │ ├── jack.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ │ └── stonedong.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── xcuserdata
│ ├── jack.xcuserdatad
│ └── xcschemes
│ │ ├── DZTableView.xcscheme
│ │ └── xcschememanagement.plist
│ └── stonedong.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ ├── DZTableView.xcscheme
│ └── xcschememanagement.plist
├── DZTableView
├── DZAppDelegate.h
├── DZAppDelegate.m
├── DZTableView-Info.plist
├── DZTableView-Prefix.pch
├── DZTypeCell.h
├── DZTypeCell.m
├── DZTypesViewController.h
├── DZTypesViewController.m
├── Devices
│ ├── DZDevices.h
│ ├── DZDevices.m
│ ├── NSString+WizString.h
│ ├── NSString+WizString.mm
│ ├── UIDeviceHardware.h
│ └── UIDeviceHardware.m
├── Images.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── LaunchImage.launchimage
│ │ └── Contents.json
├── Programbase
│ ├── DZProgramDefines.h
│ └── DZProgramDefines.m
├── Storyboard.storyboard
├── TableView
│ ├── DZCellActionItem.h
│ ├── DZCellActionItem.m
│ ├── DZCellActionsView.h
│ ├── DZCellActionsView.m
│ ├── DZInputCellView.h
│ ├── DZInputCellView.m
│ ├── DZPullDownDelegate.h
│ ├── DZPullDownView.h
│ ├── DZPullDownView.m
│ ├── DZSawtoothView.h
│ ├── DZSawtoothView.m
│ ├── DZSeparationLine.h
│ ├── DZSeparationLine.m
│ ├── DZTableView.h
│ ├── DZTableView.mm
│ ├── DZTableViewActionDelegate.h
│ ├── DZTableViewCell.h
│ ├── DZTableViewCell.m
│ ├── DZTableViewCell_private.h
│ ├── DZTableViewController.h
│ ├── DZTableViewController.m
│ └── DZTableViewSourceDelegate.h
├── UITools
│ ├── DZAnimationDefines.h
│ ├── DZDirection.h
│ ├── DZDirection.m
│ ├── DZGeometryTools.h
│ ├── DZGeometryTools.m
│ ├── DZSendSelector.h
│ ├── DZSendSelector.m
│ ├── DZUITools.h
│ ├── HexColor.h
│ ├── HexColor.m
│ ├── UIColor+DZColor.h
│ ├── UIColor+DZColor.m
│ ├── UIView+AddTaps.h
│ ├── UIView+AddTaps.m
│ ├── UIView+Shadow.h
│ └── UIView+Shadow.m
├── UIView+RedPoint.h
├── UIView+RedPoint.m
├── en.lproj
│ └── InfoPlist.strings
├── main.m
└── number_bg.png
├── DZTableViewTests
├── DZTableViewTests-Info.plist
├── DZTableViewTests.m
└── en.lproj
│ └── InfoPlist.strings
├── articles
├── Chapter0
│ ├── animation
│ │ └── animation.md
│ ├── images
│ │ ├── geometry.jpg
│ │ ├── incontentsize.png
│ │ ├── keyboard.png
│ │ ├── outcontentsize.png
│ │ ├── pics.png
│ │ ├── superview.png
│ │ ├── view_tree_3d.jpeg
│ │ ├── view_tree_text.jpeg
│ │ └── wKiom1MNiXaQh-LKAAEklpTy71Q964.png
│ ├── interaction
│ │ ├── delivery.md
│ │ ├── gestures.md
│ │ ├── imgs
│ │ │ ├── discrete_vs_continuous_2x.png
│ │ │ ├── event_touch_time_2x.png
│ │ │ ├── events.png
│ │ │ ├── gestureRecognizer_2x.png
│ │ │ ├── gr_state_transitions_2x.png
│ │ │ ├── hit_testing_2x.png
│ │ │ ├── iOS_responder_chain_2x.png
│ │ │ ├── path_of_touches_2x.png
│ │ │ └── recognize_touch_2x.png
│ │ ├── summary.md
│ │ └── touch.md
│ ├── layout
│ │ ├── coordinate.md
│ │ ├── gemotry.md
│ │ ├── howlayout.md
│ │ ├── imgs
│ │ │ ├── coordinate.jpg
│ │ │ └── update_circle.png
│ │ ├── layout.md
│ │ └── zorder.md
│ ├── scrollview.md
│ ├── summary.md
│ ├── uikit.md
│ └── uikit
│ │ ├── application.md
│ │ ├── architecture.md
│ │ ├── classes.md
│ │ ├── imgs
│ │ ├── button_scale.jpg
│ │ ├── drawing_model.jpg
│ │ ├── frame_bounds_rects.jpg
│ │ ├── native_coordinate_system.jpg
│ │ ├── scale_aspect.jpg
│ │ ├── uikit_classes.jpg
│ │ ├── view-layer-store.jpg
│ │ ├── view_classes.jpg
│ │ └── xform_rotations.jpg
│ │ ├── uiview.md
│ │ ├── viewcontroller.md
│ │ ├── window.md
│ │ └── windowAview.md
├── Chapter1
│ ├── cell.md
│ ├── cellfunctions
│ │ ├── actions.md
│ │ ├── gemotry.md
│ │ ├── selected.md
│ │ └── subclass.md
│ ├── event.md
│ ├── gemotry.md
│ ├── images
│ │ ├── TableView datasource.png
│ │ ├── aim.jpeg
│ │ ├── flightWeight
│ │ └── tableView_tree_3d.jpg
│ ├── interface.md
│ ├── shareCell.md
│ ├── subclassScrollView.md
│ └── summary.md
├── Chapter2
│ ├── controldata.md
│ ├── custome
│ │ ├── custome.md
│ │ └── imgs
│ │ │ ├── tunnel-screenshot@2x.png
│ │ │ └── view-insertion@2x.png
│ ├── layoutviews.md
│ ├── mainview.md
│ ├── mvc
│ │ ├── imgs
│ │ │ ├── 500px-MVC-Process.png
│ │ │ └── thirdLevel.jpg
│ │ ├── mvc.md
│ │ └── thirdLevel.md
│ └── summary.md
└── Chapter3
│ ├── subclass.md
│ ├── summary.md
│ └── topic.md
├── imgs
└── qrcode_for_gh_34347e9f195e_430.jpg
├── ppt
├── 构建TableView.pdf
└── 构建TableView.pptx
└── readme.md
/DZTableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/project.xcworkspace/xcshareddata/DZTableView.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | 076FC408-3CB0-4DC6-84E2-82CA2004FB6D
9 | IDESourceControlProjectName
10 | DZTableView
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | A150221FDC497C49ABD26ABFF23D2676A847F714
14 | https://github.com/yishuiliunian/DZTableView
15 |
16 | IDESourceControlProjectPath
17 | DZTableView.xcodeproj
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | A150221FDC497C49ABD26ABFF23D2676A847F714
21 | ../..
22 |
23 | IDESourceControlProjectURL
24 | https://github.com/yishuiliunian/DZTableView
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | A150221FDC497C49ABD26ABFF23D2676A847F714
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | A150221FDC497C49ABD26ABFF23D2676A847F714
36 | IDESourceControlWCCName
37 | DZTableView
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/project.xcworkspace/xcuserdata/jack.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/DZTableView.xcodeproj/project.xcworkspace/xcuserdata/jack.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/project.xcworkspace/xcuserdata/stonedong.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/DZTableView.xcodeproj/project.xcworkspace/xcuserdata/stonedong.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/xcuserdata/jack.xcuserdatad/xcschemes/DZTableView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/xcuserdata/jack.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | DZTableView.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 65B90AC718BE090F0074447A
16 |
17 | primary
18 |
19 |
20 | 65B90AE218BE090F0074447A
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/xcuserdata/stonedong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
20 |
21 |
22 |
24 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/xcuserdata/stonedong.xcuserdatad/xcschemes/DZTableView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/DZTableView.xcodeproj/xcuserdata/stonedong.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | DZTableView.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 65B90AC718BE090F0074447A
16 |
17 | primary
18 |
19 |
20 | 65B90AE218BE090F0074447A
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/DZTableView/DZAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZAppDelegate.h
3 | // DZTableView
4 | //
5 | // Created by stonedong on 14-2-26.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DZAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/DZTableView/DZAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZAppDelegate.m
3 | // DZTableView
4 | //
5 | // Created by stonedong on 14-2-26.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZAppDelegate.h"
10 | #import "DZTypesViewController.h"
11 |
12 | @implementation DZAppDelegate
13 |
14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
15 | {
16 |
17 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
18 | // Override point for customization after application launch.
19 | self.window.backgroundColor = [UIColor whiteColor];
20 | self.window.rootViewController = [DZTypesViewController new];
21 | [self.window makeKeyAndVisible];
22 | return YES;
23 | }
24 |
25 | - (void)applicationWillResignActive:(UIApplication *)application
26 | {
27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
28 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
29 | }
30 |
31 | - (void)applicationDidEnterBackground:(UIApplication *)application
32 | {
33 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
34 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
35 | }
36 |
37 | - (void)applicationWillEnterForeground:(UIApplication *)application
38 | {
39 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
40 | }
41 |
42 | - (void)applicationDidBecomeActive:(UIApplication *)application
43 | {
44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
45 | }
46 |
47 | - (void)applicationWillTerminate:(UIApplication *)application
48 | {
49 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/DZTableView/DZTableView-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | com.dzpqzb.com.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/DZTableView/DZTableView-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_3_0
10 | #warning "This project uses features only available in iOS SDK 3.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | #import
15 | #import
16 | #import "DZProgramDefines.h"
17 | #import "DZUITools.h"
18 | #endif
19 |
--------------------------------------------------------------------------------
/DZTableView/DZTypeCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTypeCell.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTableViewCell.h"
10 |
11 | #define DZTypeCellHeight 75
12 |
13 | @interface DZTypeCell : DZTableViewCell
14 | @property (nonatomic, strong, readonly) UIImageView * typeImageView;
15 | DEFINE_PROPERTY_STRONG(UITextField*, costLabel);
16 | DEFINE_PROPERTY_STRONG(UITextField*, nameLabel);
17 | DEFINE_PROPERTY_STRONG(UITextField*, countLabel);
18 | @end
19 |
--------------------------------------------------------------------------------
/DZTableView/DZTypeCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZTypeCell.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTypeCell.h"
10 | #import "NSString+WizString.h"
11 | float CountLabelWidth = DZTypeCellHeight -40;
12 | float TypeImageLabelWidth = 1;
13 |
14 | @interface DZTypeCell()
15 | {
16 | UIView* _selectedIndicaterView;
17 | }
18 | @end
19 | @implementation DZTypeCell
20 |
21 |
22 |
23 |
24 |
25 | - (id)initWithFrame:(CGRect)frame
26 | {
27 | self = [super initWithFrame:frame];
28 | if (self) {
29 | _selectedIndicaterView = [UIView new];
30 | _typeImageView = [UIImageView new];
31 |
32 | [_contentView addSubview:_selectedIndicaterView];
33 | [_contentView addSubview:_typeImageView];
34 |
35 | INIT_SUBVIEW(_contentView, UITextField, _countLabel);
36 | INIT_SUBVIEW(_contentView, UITextField, _nameLabel);
37 | INIT_SUBVIEW(_contentView, UITextField, _costLabel);
38 |
39 | _countLabel.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
40 | _countLabel.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
41 | _countLabel.textAlignment = NSTextAlignmentCenter;
42 | _nameLabel.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
43 | self.backgroundColor = [UIColor clearColor];
44 | _costLabel.textAlignment = NSTextAlignmentRight;
45 | _countLabel.enabled = NO;
46 | _nameLabel.enabled = NO;
47 | _costLabel.enabled = NO;
48 | _nameLabel.textColor = [UIColor whiteColor];
49 | _costLabel.textColor = [UIColor whiteColor];
50 |
51 |
52 | }
53 | return self;
54 | }
55 |
56 | - (void) setIsSelected:(BOOL)isSelected
57 | {
58 | [super setIsSelected:isSelected];
59 |
60 | }
61 |
62 | - (void) layoutSubviews
63 | {
64 | [super layoutSubviews];
65 | _selectedIndicaterView.frame = CGRectMake(0, 0, 10, CGRectGetHeight(self.frame));
66 | _typeImageView.frame = CGRectMake(CGRectGetMaxX(_selectedIndicaterView.frame), 0, TypeImageLabelWidth, TypeImageLabelWidth);
67 |
68 | _countLabel.frame = CGRectMake(CGRectGetWidth(self.frame) - CountLabelWidth - 5, (CGRectViewHeight - CountLabelWidth)/2, CountLabelWidth, CountLabelWidth);
69 | _costLabel.frame = CGRectMake(CGRectGetMinX(_countLabel.frame) - 80, 0, 80, CGRectViewHeight);
70 | _nameLabel.frame = CGRectMake(CGRectGetMaxX(_typeImageView.frame),
71 | 0,
72 | CGRectGetMinX(_costLabel.frame) - CGRectGetMaxX(_typeImageView.frame),
73 | CGRectGetHeight(self.frame));
74 | // _costLabel.frame = CGRectOffset(_nameLabel.frame, 0, CGRectGetHeight(self.frame)/2);
75 | }
76 |
77 | /*
78 | // Only override drawRect: if you perform custom drawing.
79 | // An empty implementation adversely affects performance during animation.
80 | - (void)drawRect:(CGRect)rect
81 | {
82 | // Drawing code
83 | }
84 | */
85 |
86 | @end
87 |
--------------------------------------------------------------------------------
/DZTableView/DZTypesViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTypesViewController.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-11.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTableViewController.h"
10 | #import "DZInputCellView.h"
11 |
12 | @interface DZTypesViewController : DZTableViewController
13 | {
14 | NSMutableArray* _typesArray;
15 | NSMutableArray* _timeTypes;
16 |
17 | }
18 |
19 | - (void) reloadAllData;
20 | @end
21 |
--------------------------------------------------------------------------------
/DZTableView/DZTypesViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZTypesViewController.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-11.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTypesViewController.h"
10 | #import "DZTypeCell.h"
11 | #import "DZSawtoothView.h"
12 | #import "NSString+WizString.h"
13 | @interface DZTypesViewController ()
14 | @end
15 |
16 | @implementation DZTypesViewController
17 |
18 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
19 | {
20 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
21 | if (self) {
22 | // Custom initialization
23 | }
24 | return self;
25 | }
26 | - (void) loadViewCSS:(id)cssValue forKey:(NSString *)key
27 | {
28 | if ([key isEqualToString:@"background"]) {
29 | self.backgroudView.image = cssValue;
30 | } else if ([key isEqualToString:@"table_gradient"])
31 | {
32 | self.tableView.gradientColor = cssValue;
33 | }
34 | }
35 | - (void)viewDidLoad
36 | {
37 |
38 | [super viewDidLoad];
39 | self.tableView.showsVerticalScrollIndicator = NO;
40 | self.tableView.showsHorizontalScrollIndicator = NO;
41 | DZSawtoothView* tooth = [[DZSawtoothView alloc] initWithFrame:CGRectMake(0, 0, 0, 10)];
42 | self.tableView.bottomView = tooth;
43 | tooth.color = [UIColor lightGrayColor];
44 |
45 | [self reloadAllData];
46 | }
47 |
48 | - (void) printTypes
49 | {
50 | for (int i = 0; i < _timeTypes.count; i++) {
51 | NSLog(@"type %d is %@",i, [_timeTypes[i] name]);
52 | }
53 | }
54 |
55 | - (void) reloadAllData
56 | {
57 | _timeTypes = [@[@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a",@"a"] mutableCopy];
58 | [self.tableView reloadData];
59 | }
60 |
61 | - (void) viewWillAppear:(BOOL)animated
62 | {
63 | [super viewWillAppear:animated];
64 | }
65 |
66 | - (CGFloat) dzTableView:(DZTableView *)tableView cellHeightAtRow:(NSInteger)row
67 | {
68 | return DZTypeCellHeight;
69 | }
70 | - (NSInteger) numberOfRowsInDZTableView:(DZTableView *)tableView
71 | {
72 | return _timeTypes.count;
73 | }
74 |
75 | - (DZTableViewCell*) dzTableView:(DZTableView *)tableView cellAtRow:(NSInteger)row
76 | {
77 | static NSString* const cellIdentifiy = @"detifail";
78 | DZTypeCell* cell = (DZTypeCell*)[tableView dequeueDZTalbeViewCellForIdentifiy:cellIdentifiy];
79 | if (!cell) {
80 | cell = [[DZTypeCell alloc] initWithIdentifiy:cellIdentifiy];
81 | cell.nameLabel.textColor = [UIColor whiteColor];
82 | cell.nameLabel.font = [UIFont systemFontOfSize:28];
83 | }
84 | NSString* text = _timeTypes[row];
85 | cell.nameLabel.text = text;
86 | cell.backgroundColor = [UIColor clearColor];
87 | return cell;
88 | }
89 | - (void) scrollViewDidScroll:(UIScrollView *)scrollView
90 | {
91 | self.tableView.topPullDownView.topYOffSet = self.tableView.contentOffset.y ;
92 | }
93 |
94 | - (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
95 | {
96 | if (self.tableView.topPullDownView.state == DZPullDownViewStateToggled) {
97 | DZInputCellView* inputView = [[DZInputCellView alloc] init];
98 | inputView.delegate = self;
99 | [inputView showInView:[UIApplication sharedApplication].keyWindow withAnimation:YES completion:^{
100 |
101 | }];
102 | }
103 | }
104 |
105 |
106 | - (void) dzInputCellView:(DZInputCellView *)inputView hideWithText:(NSString *)text
107 | {
108 |
109 | [_timeTypes insertObject:[NSString stringWithFormat:@"%@",text] atIndex:0];
110 | [self.tableView insertRowAt:[NSSet setWithObject:@(0)] withAnimation:YES];
111 | }
112 |
113 | - (void) dzInputCellViewUserCancel:(DZInputCellView *)inputView
114 | {
115 |
116 | }
117 |
118 | - (void) dzTableView:(DZTableView *)tableView deleteCellAtRow:(NSInteger)row
119 | {
120 | [_timeTypes removeObjectAtIndex:row];
121 | [self.tableView removeRowAt:row withAnimation:YES];
122 | }
123 |
124 | - (void) dzTableView:(DZTableView *)tableView editCellDataAtRow:(NSInteger)row
125 | {
126 |
127 | }
128 |
129 |
130 | @end
--------------------------------------------------------------------------------
/DZTableView/Devices/DZDevices.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZDevices.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #ifdef __cplusplus
12 | extern "C" {
13 | #endif
14 | float DeviceSystemMajorVersion();
15 | NSString* netDeviceMacAddress();
16 | NSString* DZDevicesIdentify();
17 | NSDictionary* DZDevicesInfos();
18 | #ifdef __cplusplus
19 | }
20 | #endif
21 |
22 | #define bDEVICE_OSVERSION_BEFORE6 (DeviceSystemMajorVersion() < 6)
23 | #define bDEVICE_OSVERSION_EQUAL_OR_BEFORE6 (DeviceSystemMajorVersion() - 5.99999999>0)
24 | #define bDEVICE_OSVERSION_EQUAL_OR_LATER7 (DeviceSystemMajorVersion() - 6.9999999999 > 0)
25 | #define bDEVICE_MACHINE_SCREEN_1136 (CGSizeEqualToSize([[UIScreen mainScreen].currentMode size], CGSizeMake(640, 1136)))
26 | #define bDEVICE_MACHINE_SCREEN_960 (CGSizeEqualToSize([[UIScreen mainScreen].currentMode size], CGSizeMake(640, 960)))
27 | #define CGRectLoadViewFrame (bDEVICE_OSVERSION_EQUAL_OR_LATER7?[[UIScreen mainScreen] applicationFrame]:[UIScreen mainScreen].bounds)
28 |
29 | @interface DZDevices : NSObject
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/DZTableView/Devices/DZDevices.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZDevices.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZDevices.h"
10 | #include // Per msqr
11 | #include
12 | #include
13 | #include
14 | #import "NSString+WizString.h"
15 | #import "UIDeviceHardware.h"
16 |
17 | float DeviceSystemMajorVersion() {
18 |
19 | static NSUInteger _deviceSystemMajorVersion = -1;
20 | static dispatch_once_t onceToken;
21 | dispatch_once(&onceToken, ^{
22 | NSString* versionStr = [[UIDevice currentDevice] systemVersion];
23 | NSArray* dots = [versionStr componentsSeparatedByString:@"."];
24 | float v = 0;
25 | for (int i = 0 ; i < dots.count; ++i) {
26 | NSString* str = dots[i];
27 | float value = [str floatValue] * pow(0.1, i);
28 | v += value;
29 | }
30 | _deviceSystemMajorVersion = v;
31 |
32 | });
33 | return _deviceSystemMajorVersion;
34 |
35 | }
36 |
37 |
38 |
39 | #pragma mark MAC addy
40 | // Return the local MAC addy
41 | // Courtesy of FreeBSD hackers email list
42 | // Accidentally munged during previous update. Fixed thanks to mlamb.
43 | NSString* netDeviceMacAddress()
44 | {
45 | int mib[6];
46 | size_t len;
47 | char *buf;
48 | unsigned char *ptr;
49 | struct if_msghdr *ifm;
50 | struct sockaddr_dl *sdl;
51 |
52 | mib[0] = CTL_NET;
53 | mib[1] = AF_ROUTE;
54 | mib[2] = 0;
55 | mib[3] = AF_LINK;
56 | mib[4] = NET_RT_IFLIST;
57 |
58 | if ((mib[5] = if_nametoindex("en0")) == 0) {
59 | printf("Error: if_nametoindex error/n");
60 | return NULL;
61 | }
62 |
63 | if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
64 | printf("Error: sysctl, take 1/n");
65 | return NULL;
66 | }
67 |
68 | if ((buf = malloc(len)) == NULL) {
69 | printf("Could not allocate memory. error!/n");
70 | return NULL;
71 | }
72 |
73 | if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
74 | printf("Error: sysctl, take 2");
75 | return NULL;
76 | }
77 |
78 | ifm = (struct if_msghdr *)buf;
79 | sdl = (struct sockaddr_dl *)(ifm + 1);
80 | ptr = (unsigned char *)LLADDR(sdl);
81 | NSString *outstring = [NSString stringWithFormat:@"%x%x%x%x%x%x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
82 | free(buf);
83 | return [outstring uppercaseString];
84 | }
85 |
86 |
87 | NSString* DZDevicesIdentify()
88 | {
89 | static NSString* identify = nil;
90 | static dispatch_once_t onceToken;
91 | dispatch_once(&onceToken, ^{
92 | identify = [netDeviceMacAddress() MD5Hash];
93 | });
94 | return identify;
95 | }
96 |
97 |
98 | NSDictionary* DZDevicesInfos()
99 | {
100 | static NSMutableDictionary* infos = nil;
101 | static dispatch_once_t onceToken;
102 | dispatch_once(&onceToken, ^{
103 | infos = [NSMutableDictionary new];
104 | [infos setObject:DZDevicesIdentify() forKey:@"guid"];
105 | [infos setObject:[UIDevice currentDevice].name forKey:@"name"];
106 | [infos setObject:@"ios" forKey:@"type"];
107 | [infos setObject:[UIDeviceHardware platformString] forKey:@"platform"];
108 | });
109 | return infos;
110 | }
111 |
112 | @implementation DZDevices
113 |
114 | @end
115 |
--------------------------------------------------------------------------------
/DZTableView/Devices/NSString+WizString.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSString+WizString.h
3 | // Wiz
4 | //
5 | // Created by 朝 董 on 12-4-23.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | extern NSString* (^Int64ToString)(int64_t);
12 | extern NSString* (^Int32ToString)(int32_t);
13 | extern NSString* (^BoolTOString)(BOOL);
14 |
15 | @interface NSString (WizString)
16 | - (BOOL) isBlock;
17 | - (NSString*) fileName;
18 | - (NSString*) fileType;
19 | - (NSString*) stringReplaceUseRegular:(NSString*)regex;
20 | - (NSString*) stringReplaceUseRegular:(NSString *)regex withString:(NSString*)replaceStr;
21 | - (NSDate *) dateFromSqlTimeString;
22 | //help
23 | - (NSString*) trim;
24 | - (NSString*) trimChar:(unichar) ch;
25 | - (int) indexOfChar:(unichar)ch;
26 | - (int) indexOf:(NSString*)find;
27 | - (int) lastIndexOfChar: (unichar)ch;
28 | - (int) lastIndexOf:(NSString*)find;
29 | - (NSString*) firstLine;
30 | - (NSString*) toHtml;
31 | - (NSString*) pinyinFirstLetter;
32 | - (NSString*) pinyinFirstLettersForSentence;
33 | - (NSString*) toValidPathComponent;
34 | - (NSComparisonResult) compareByChinese:(NSString*)string;
35 |
36 | - (NSComparisonResult) compareFirstCharacter:(NSString*)string;
37 |
38 | - (NSString *)URLEncodedString;
39 | - (NSString*)URLDecodedString;
40 | - (NSString*) fromHtml;
41 | - (NSString*) nToHtmlBr;
42 | //
43 | - (BOOL) writeToFile:(NSString *)path useUtf8Bom:(BOOL)isWithBom error:(NSError **)error;
44 | //
45 | - (NSInteger) indexOf:(NSString *)find compareOptions:(NSStringCompareOptions)mask;
46 |
47 | - (BOOL) checkHasInvaildCharacters;
48 | - (NSString*) processHtml;
49 | - (NSString*) htmlToText:(int)maxSize;
50 | //
51 | - (NSArray*) separateTagGuids;
52 | - (NSString*) removeTagguid:(NSString*)tagGuid;
53 | - (NSString*) stringAppendingPath:(NSString*)path;
54 |
55 | - (NSString*) parentPath;
56 | - (NSString*) getAtString;
57 | - (NSString *) MD5Hash;
58 | - (NSString*) exceptAtString;
59 | + (NSString*) stringMSFromTimeInterval:(NSTimeInterval) ftime;
60 | + (NSString*) stringHMSFromTimeInterval:(NSTimeInterval) ftime;
61 | + (NSString*) stringDHMSFromTimeInterval:(NSTimeInterval) ftime;
62 |
63 | - (NSString*) folderFormat;
64 | - (NSString*) deleteLastPathComponent;
65 |
66 | + (NSString*) readableTimeStringWithInterval:(NSTimeInterval) ftime;
67 | @end
68 |
--------------------------------------------------------------------------------
/DZTableView/Devices/UIDeviceHardware.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIDeviceHardware.h
3 | // QQPicShow
4 | //
5 | // Created by welseyxiao on 13-9-27.
6 | // Copyright (c) 2013年 Tencent SNS Terminal Develope Center. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef NS_ENUM(NSInteger, DeviceName) {
12 | IPHONE_1,
13 | IPHONE_3,
14 | IPHONE_3GS,
15 | IPHONE_4,
16 | IPHONE_4S,
17 | IPHONE_5,
18 | IPHONE_5C,
19 | IPHONE_5S
20 | };
21 | #ifndef PLATFROM_DEVIDE_NAME
22 | #define PLATFROM_DEVIDE_NAME
23 | #define IPHONE_1 @"iPhone 1G"
24 | #define IPHONE_3 @"iPhone 3G"
25 | #define IPHONE_3GS @"iPhone 3GS"
26 | #define IPHONE_4 @"iPhone 4"
27 | #define IPHONE_4S @"iPhone 4S"
28 | #define IPHONE_5 @"iPhone 5"
29 | #define IPHONE_5C @"iPhone 5C"
30 | #define IPHONE_5S @"iPhone 5S"
31 |
32 |
33 | #endif
34 | @interface UIDeviceHardware : NSObject
35 | + (NSString *) platform;
36 | /*
37 | * 返回具体的设备型号,
38 | * 现在不再次细分网络制式,只判断设备型号
39 | * @return
40 | */
41 | + (NSString *) platformString;
42 |
43 | @end
44 |
--------------------------------------------------------------------------------
/DZTableView/Devices/UIDeviceHardware.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIDeviceHardware.m
3 | // QQPicShow
4 | //
5 | // Created by welseyxiao on 13-9-27.
6 | // Copyright (c) 2013年 Tencent SNS Terminal Develope Center. All rights reserved.
7 | //
8 |
9 | #import "UIDeviceHardware.h"
10 | #import
11 | @implementation UIDeviceHardware
12 |
13 | + (NSString *) platform{
14 | struct utsname systemInfo;
15 | uname(&systemInfo);
16 | return [NSString stringWithCString:systemInfo.machine
17 | encoding:NSUTF8StringEncoding];
18 | }
19 | + (NSString *) platformString{
20 | NSString *platform = [UIDeviceHardware platform];
21 | //iPhone
22 | if ([platform isEqualToString:@"iPhone1,1"]) return IPHONE_1;
23 | if ([platform isEqualToString:@"iPhone1,2"]) return IPHONE_3;
24 | if ([platform isEqualToString:@"iPhone2,1"]) return IPHONE_3GS;
25 |
26 | if ([platform isEqualToString:@"iPhone3,1"]) return IPHONE_4;
27 | if ([platform isEqualToString:@"iPhone3,3"]) return IPHONE_4S;
28 | if ([platform isEqualToString:@"iPhone4,1"]) return IPHONE_4S;
29 |
30 | if ([platform isEqualToString:@"iPhone5,1"]) return IPHONE_5;//iPhone 5 (model A1428, AT&T/Canada)
31 | if ([platform isEqualToString:@"iPhone5,2"]) return IPHONE_5;//iPhone 5 (model A1429, everything else)
32 |
33 | if ([platform isEqualToString:@"iPhone5,3"]) return IPHONE_5C;//iPhone 5c (model A1456, A1532 | GSM)
34 | if ([platform isEqualToString:@"iPhone5,4"]) return IPHONE_5C;//iPhone 5c (model A1507, A1516, A1526 (China), A1529 | Global)
35 |
36 | if ([platform isEqualToString:@"iPhone6,1"]) return IPHONE_5S;//iPhone 5S(model A1433, A1533 | GSM)
37 | if ([platform isEqualToString:@"iPhone6,2"]) return IPHONE_5S;//iPhone 5s (model A1457, A1518, A1528 (China), A1530 | Global)
38 | //iPod
39 | if ([platform isEqualToString:@"iPod1,1"]) return @"iPod Touch 1G";
40 | if ([platform isEqualToString:@"iPod2,1"]) return @"iPod Touch 2G";
41 | if ([platform isEqualToString:@"iPod3,1"]) return @"iPod Touch 3G";
42 | if ([platform isEqualToString:@"iPod4,1"]) return @"iPod Touch 4G";
43 | //iPad
44 | if ([platform isEqualToString:@"iPad1,1"]) return @"iPad";
45 | if ([platform isEqualToString:@"iPad2,1"]) return @"iPad 2";
46 | if ([platform isEqualToString:@"iPad2,2"]) return @"iPad 2";
47 | if ([platform isEqualToString:@"iPad2,3"]) return @"iPad 2";
48 | if ([platform isEqualToString:@"iPad2,3"]) return @"iPad 2";//iPad Mini
49 | if ([platform isEqualToString:@"iPad3,4"]) return @"iPad 3";
50 | if ([platform isEqualToString:@"iPad3,4"]) return @"iPad 3";//4th Generation iPad
51 | //Simulator
52 | if ([platform isEqualToString:@"i386"]) return @"Simulator";
53 | if ([platform isEqualToString:@"x86_64"]) return @"Simulator";
54 | return platform;
55 | }
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/DZTableView/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "40x40",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "60x60",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "ipad",
20 | "size" : "29x29",
21 | "scale" : "1x"
22 | },
23 | {
24 | "idiom" : "ipad",
25 | "size" : "29x29",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "ipad",
30 | "size" : "40x40",
31 | "scale" : "1x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "40x40",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "76x76",
41 | "scale" : "1x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "76x76",
46 | "scale" : "2x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/DZTableView/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "portrait",
5 | "idiom" : "iphone",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "7.0",
8 | "scale" : "2x"
9 | },
10 | {
11 | "orientation" : "portrait",
12 | "idiom" : "iphone",
13 | "subtype" : "retina4",
14 | "extent" : "full-screen",
15 | "minimum-system-version" : "7.0",
16 | "scale" : "2x"
17 | },
18 | {
19 | "orientation" : "portrait",
20 | "idiom" : "ipad",
21 | "extent" : "full-screen",
22 | "minimum-system-version" : "7.0",
23 | "scale" : "1x"
24 | },
25 | {
26 | "orientation" : "landscape",
27 | "idiom" : "ipad",
28 | "extent" : "full-screen",
29 | "minimum-system-version" : "7.0",
30 | "scale" : "1x"
31 | },
32 | {
33 | "orientation" : "portrait",
34 | "idiom" : "ipad",
35 | "extent" : "full-screen",
36 | "minimum-system-version" : "7.0",
37 | "scale" : "2x"
38 | },
39 | {
40 | "orientation" : "landscape",
41 | "idiom" : "ipad",
42 | "extent" : "full-screen",
43 | "minimum-system-version" : "7.0",
44 | "scale" : "2x"
45 | }
46 | ],
47 | "info" : {
48 | "version" : 1,
49 | "author" : "xcode"
50 | }
51 | }
--------------------------------------------------------------------------------
/DZTableView/Programbase/DZProgramDefines.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZProgramDefines.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-21.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #define DEFINE_PROPERTY_KEY(key) static void const * kPK##key = &kPK##key
12 |
13 | /**
14 | *
15 | * 定义字符串
16 | */
17 | #define DEFINE_NSString(str) static NSString* const kDZ##str = @""#str;
18 |
19 | #define DEFINE_NOTIFICATION_MESSAGE(str) static NSString* const kDZNotification_##str = @""#str;
20 |
21 | #define DEFINE_PROPERTY(mnmKind, type , name) @property (nonatomic, mnmKind) type name
22 | #define DEFINE_PROPERTY_ASSIGN(type, name) DEFINE_PROPERTY(assign, type, name)
23 | #define DEFINE_PROPERTY_ASSIGN_Float(name) DEFINE_PROPERTY_ASSIGN(float, name)
24 | #define DEFINE_PROPERTY_ASSIGN_INT64(name) DEFINE_PROPERTY_ASSIGN(int64_t, name)
25 | #define DEFINE_PROPERTY_ASSIGN_INT32(name) DEFINE_PROPERTY_ASSIGN(int32_t, name)
26 | #define DEFINE_PROPERTY_ASSIGN_INT16(name) DEFINE_PROPERTY_ASSIGN(int16_t, name)
27 | #define DEFINE_PROPERTY_ASSIGN_INT8(name) DEFINE_PROPERTY_ASSIGN(int8_t, name)
28 | #define DEFINE_PROPERTY_ASSIGN_Double(name) DEFINE_PROPERTY_ASSIGN(double, name)
29 |
30 |
31 | #define DEFINE_PROPERTY_STRONG(type, name) DEFINE_PROPERTY(strong, type, name)
32 | #define DEFINE_PROPERTY_STRONG_UILabel(name) DEFINE_PROPERTY_STRONG(UILabel*, name)
33 | #define DEFINE_PROPERTY_STRONG_NSString(name) DEFINE_PROPERTY_STRONG(NSString*, name)
34 | #define DEFINE_PROPERTY_STRONG_UIImageView(name) DEFINE_PROPERTY_STRONG(UIImageView*, name)
35 |
36 |
37 |
38 | #define INIT_SUBVIEW(sView, class, name) name = [[class alloc] init]; [sView addSubview:name];
39 | #define INIT_SUBVIEW_UIImageView(sView, name) INIT_SUBVIEW(sView, UIImageView, name)
40 | #define INIT_SUBVIEW_UILabel(sView, name) INIT_SUBVIEW(sView, UILabel, name)
41 |
42 | #define INIT_SELF_SUBVIEW(class, name) INIT_SUBVIEW(self, class , name)
43 | #define INIT_SELF_SUBVIEW_UIImageView(name) INIT_SUBVIEW_UIImageView(self, name)
44 | #define INIT_SELF_SUBVIEW_UILabel(name) INIT_SUBVIEW_UILabel(self, name)
45 |
46 |
47 | #define DEFINE_PROPERTY_WEAK(type, name) DEFINE_PROPERTY(weak, type, name)
48 |
49 | /**
50 | * 初始化一个点击的手势
51 | *
52 | * @param name 点击手势的名称
53 | * @param view 要添加手势的视图的名称
54 | * @param taps 需要的点击次数
55 | * @param touchs 需要的手指数量
56 | *
57 | */
58 | #define INIT_GESTRUE_TAP_IN_VIEW(name, view, taps, touchs) name=[[UITapGestureRecognizer alloc] init];\
59 | name.numberOfTapsRequired = 1;\
60 | name.numberOfTouchesRequired = 1;\
61 | [view addGestureRecognizer:name];
62 |
63 | /**
64 | * 在当前视图上初始化一个点击手势
65 | */
66 | #define INIT_GESTRUE_TAP_IN_SELF(name, taps, touchs) INIT_GESTRUE_TAP_IN_VIEW(name, self, taps, touchs)
67 |
68 | @interface DZProgramDefines : NSObject
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/DZTableView/Programbase/DZProgramDefines.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZProgramDefines.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-21.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZProgramDefines.h"
10 |
11 | @implementation DZProgramDefines
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/DZTableView/Storyboard.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZCellActionItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZCellActionItem.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class DZTableViewCell;
12 |
13 | @interface DZCellActionItem : UIButton
14 | @property (nonatomic, weak) DZTableViewCell* linkedTableViewCell;
15 | @property (nonatomic, assign) UIEdgeInsets edgeInset;
16 | @end
17 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZCellActionItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZCellActionItem.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZCellActionItem.h"
10 |
11 | @implementation DZCellActionItem
12 |
13 | - (id)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | if (self) {
17 | _edgeInset = UIEdgeInsetsZero;
18 | }
19 | return self;
20 | }
21 |
22 | /*
23 | // Only override drawRect: if you perform custom drawing.
24 | // An empty implementation adversely affects performance during animation.
25 | - (void)drawRect:(CGRect)rect
26 | {
27 | // Drawing code
28 | }
29 | */
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZCellActionsView.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZCellActionsView.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DZCellActionItem.h"
11 |
12 | @interface DZCellActionsView : UIView
13 | @property (nonatomic, strong, readonly) DZCellActionItem* abledItem;
14 | @property (nonatomic, strong) NSArray* items;
15 | - (instancetype) initWithItems:(NSArray*)items;
16 | - (void) setEableItemWithMaskOffSet:(float)offSet;
17 | @end
18 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZCellActionsView.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZCellActionsView.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZCellActionsView.h"
10 |
11 | @implementation DZCellActionsView
12 |
13 | - (id)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | if (self) {
17 | // Initialization code
18 | }
19 | return self;
20 | }
21 |
22 | - (void) setItems:(NSArray *)items
23 | {
24 | if (_items != items) {
25 | for (DZCellActionItem* item in _items) {
26 | [item removeFromSuperview];
27 | }
28 | _items = items;
29 | for (DZCellActionsView* item in _items) {
30 | [self addSubview:item];
31 | }
32 | }
33 | }
34 | - (instancetype) initWithItems:(NSArray*)items
35 | {
36 | self = [super init];
37 | if (!self) {
38 | return nil;
39 | }
40 | [self setItems:items];
41 | return self;
42 | }
43 | - (void) setEableItemWithMaskOffSet:(float)offSet
44 | {
45 | CGRect emptyMaskRect = CGRectMake(0, 0, offSet, CGRectGetHeight(self.frame));
46 | NSMutableArray* enableItems = [NSMutableArray new];
47 | for (DZCellActionItem* item in _items) {
48 | CGRect rect = CGRectUseEdge(self.bounds, item.edgeInset);
49 | if (CGRectContainsRect(emptyMaskRect, rect)) {
50 | item.enabled = YES;
51 | [enableItems addObject:item];
52 | }
53 | else
54 | {
55 | item.enabled = NO;
56 | [enableItems removeObject:item];
57 | }
58 | }
59 |
60 | CGPoint point = CGPointMake(offSet, CGRectGetHeight(self.frame)/2);
61 | float lastDistance = INT_MAX;
62 | if (enableItems.count) {
63 | for (DZCellActionItem* item in enableItems) {
64 | CGRect rect = CGRectUseEdge(self.bounds, item.edgeInset);
65 | CGPoint centerPoint = CGPointCenterRect(rect);
66 | float distance = CGDistanceBetweenPoints(centerPoint, point);
67 | if (distance < lastDistance) {
68 | _abledItem = item;
69 | lastDistance = distance;
70 | }
71 | }
72 | }
73 | else
74 | {
75 | _abledItem = nil;
76 | }
77 |
78 |
79 | }
80 | - (void) layoutSubviews
81 | {
82 | for (DZCellActionItem* item in _items) {
83 | CGRect rect = CGRectUseEdge(self.bounds, item.edgeInset);
84 | item.frame = rect;
85 | }
86 | }
87 | /*
88 | // Only override drawRect: if you perform custom drawing.
89 | // An empty implementation adversely affects performance during animation.
90 | - (void)drawRect:(CGRect)rect
91 | {
92 | // Drawing code
93 | }
94 | */
95 |
96 | @end
97 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZInputCellView.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZInputCellView.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | @class DZInputCellView;
11 | @protocol DZInputCellViewDelegate
12 |
13 | - (void) dzInputCellView:(DZInputCellView*)inputView hideWithText:(NSString*)text;
14 | - (void) dzInputCellViewUserCancel:(DZInputCellView *)inputView;
15 |
16 | @end
17 |
18 | @interface DZInputCellView : UIView
19 | @property (nonatomic, weak) id delegate;
20 | @property (nonatomic, strong, readonly) UITextField* textField;
21 | @property (nonatomic, strong, readonly) UIImageView* backgroudView;
22 |
23 | - (void) showInView:(UIView*)view withAnimation:(BOOL)animation completion:(DZAnimationCompletion)completionBlock;
24 | @end
25 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZInputCellView.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZInputCellView.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZInputCellView.h"
10 |
11 | @interface DZInputCellView ()
12 | {
13 | UIView* _containerView;
14 |
15 | UIImageView* _containerBackgroudView;
16 | }
17 | @end
18 |
19 | @implementation DZInputCellView
20 | @synthesize backgroudView = _backgroudView;
21 | @synthesize textField = _textField;
22 | - (id)initWithFrame:(CGRect)frame
23 | {
24 | self = [super initWithFrame:frame];
25 | if (self) {
26 |
27 | _backgroudView = [UIImageView new];
28 | _backgroudView.backgroundColor = [UIColor blackColor];
29 | [self addSubview:_backgroudView];
30 |
31 | _containerView = [UIView new];
32 | [self addSubview:_containerView];
33 |
34 | _containerBackgroudView = [[UIImageView alloc] init];
35 | [_containerView addSubview:_containerBackgroudView];
36 |
37 | [_containerBackgroudView addTapTarget:self selector:@selector(_hideSelf)];
38 |
39 | _textField = [[UITextField alloc] init];
40 | [_containerView addSubview:_textField];
41 | _textField.backgroundColor = [UIColor whiteColor];
42 |
43 | [_textField addTarget:self action:@selector(_hideSelf) forControlEvents:UIControlEventEditingDidEndOnExit];
44 | }
45 | return self;
46 | }
47 |
48 | - (void) _hideSelf
49 | {
50 | [self hideWithAnimation:YES completion:^{
51 | NSString* text = _textField.text;
52 | if (!text || [text isEqual:@""]) {
53 | if ([_delegate respondsToSelector:@selector(dzInputCellViewUserCancel:)]) {
54 | [_delegate dzInputCellViewUserCancel:self];
55 | }
56 | }
57 | else
58 | {
59 | if ([_delegate respondsToSelector:@selector(dzInputCellView:hideWithText:)]) {
60 | [_delegate dzInputCellView:self hideWithText:text];
61 | }
62 | }
63 |
64 | }];
65 | }
66 |
67 |
68 | - (void) layoutSubviews
69 | {
70 | _textField.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), 44);
71 | _backgroudView.frame = self.bounds;
72 | _containerView.frame = self.bounds;
73 | _containerBackgroudView.frame = self.bounds;
74 | }
75 |
76 |
77 | - (void) showInView:(UIView*)view withAnimation:(BOOL)animation completion:(DZAnimationCompletion)completionBlock
78 | {
79 | [view addSubview:self];
80 | self.frame = view.frame;
81 | void(^StartAnimationBlock)(void) = ^(void) {
82 | _backgroudView.alpha = 0.0f;
83 | };
84 |
85 | void(^AnimationBlock)(void) = ^(void) {
86 | _backgroudView.alpha = 0.4;
87 | };
88 |
89 | void(^EndAnimationBlock)(void) = ^(void) {
90 | if (completionBlock) {
91 | completionBlock();
92 | }
93 | [_textField becomeFirstResponder];
94 | };
95 |
96 | StartAnimationBlock();
97 | if (animation) {
98 | [UIView animateWithDuration:DZAnimationDefualtDuration animations:AnimationBlock completion:^(BOOL finished) {
99 | EndAnimationBlock();
100 | }];
101 | }
102 | else
103 | {
104 | AnimationBlock();
105 | completionBlock();
106 | }
107 | }
108 |
109 |
110 | - (void) hideWithAnimation:(BOOL)animation completion:(DZAnimationCompletion)completionBlock
111 | {
112 |
113 | void(^StartAnimationBlock)(void) = ^(void) {
114 | [_textField resignFirstResponder];
115 | };
116 |
117 | void(^AnimationBlock)(void) = ^(void) {
118 | _backgroudView.alpha = 0.;
119 | };
120 |
121 | void(^EndAnimationBlock)(void) = ^(void) {
122 | if (completionBlock) {
123 | completionBlock();
124 | }
125 | [self removeFromSuperview];
126 | };
127 |
128 | StartAnimationBlock();
129 | if (animation) {
130 | [UIView animateWithDuration:DZAnimationDefualtDuration animations:AnimationBlock completion:^(BOOL finished) {
131 | EndAnimationBlock();
132 | }];
133 | }
134 | else
135 | {
136 | AnimationBlock();
137 | completionBlock();
138 | }
139 | }
140 |
141 | - (void) textFieldDidEndEditing:(UITextField *)textField
142 | {
143 | [self _hideSelf];
144 | }
145 | @end
146 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZPullDownDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZPullDownDelegate.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef enum {
12 | DZPullDownViewStateNormal,
13 | DZPullDownViewStateDraging,
14 | DZPullDownViewStateToggled,
15 | DZPullDownViewStateReleasing
16 | }DZPullDownViewState;
17 | @class DZPullDownView;
18 | @class DZTableView;
19 | @protocol DZPullDownDelegate
20 |
21 | - (void) pullDownView:(DZPullDownView*)pullDownView didChangedState:(DZPullDownViewState)originState toState:(DZPullDownViewState)aimState;
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZPullDownView.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZPullDownView.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DZPullDownDelegate.h"
11 |
12 |
13 |
14 | @interface DZPullDownView : UIView
15 | @property (nonatomic, weak) id delegate;
16 | @property (nonatomic, assign, readonly) DZPullDownViewState state;
17 | @property (nonatomic, strong, readonly) UILabel* textLabel;
18 | @property (nonatomic, assign) float height;
19 | @property (nonatomic, assign) float topYOffSet;
20 | @end
21 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZPullDownView.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZPullDownView.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZPullDownView.h"
10 | @interface DZPullDownView ()
11 | {
12 | float _reallyTopYOffSet;
13 | }
14 | @end
15 |
16 | @implementation DZPullDownView
17 | @synthesize delegate = _delegate;
18 | @synthesize state = _state;
19 | @synthesize textLabel = _textLabel;
20 | - (id)initWithFrame:(CGRect)frame
21 | {
22 | self = [super initWithFrame:frame];
23 | if (self) {
24 | // Initialization code
25 | _reallyTopYOffSet = -1;
26 | _state = DZPullDownViewStateNormal;
27 |
28 | _textLabel = [[UILabel alloc] init];
29 | _textLabel.backgroundColor = [UIColor clearColor];
30 | _textLabel.textAlignment = NSTextAlignmentCenter;
31 | [self addSubview:_textLabel];
32 | }
33 | return self;
34 | }
35 |
36 | /*
37 | // Only override drawRect: if you perform custom drawing.
38 | // An empty implementation adversely affects performance during animation.
39 | - (void)drawRect:(CGRect)rect
40 | {
41 | // Drawing code
42 | }
43 | */
44 |
45 | - (void) setPullDownState:(DZPullDownViewState)state
46 | {
47 | DZPullDownViewState oldState = _state;
48 | _state = state;
49 | if ([_delegate respondsToSelector:@selector(pullDownView:didChangedState:toState:)]) {
50 | [_delegate pullDownView:self didChangedState:oldState toState:state];
51 | }
52 | if (state == DZPullDownViewStateNormal) {
53 | _textLabel.text = nil;
54 | }
55 | else if (state == DZPullDownViewStateDraging || state == DZPullDownViewStateReleasing)
56 | {
57 | _textLabel.text = NSLocalizedString(@"Pull To Create New Type", nil);
58 | }
59 | else if (state == DZPullDownViewStateToggled)
60 | {
61 | _textLabel.text = NSLocalizedString(@"Release to create type ", nil);
62 | }
63 | }
64 | - (void) setTopYOffSet:(float)topYOffSet
65 | {
66 | if (topYOffSet > 0) {
67 | return;
68 | }
69 | topYOffSet = ABS(topYOffSet);
70 | if (_state == DZPullDownViewStateNormal) {
71 | if (topYOffSet > 0) {
72 | [self setPullDownState:DZPullDownViewStateDraging];
73 | }
74 | }
75 | else if (_state == DZPullDownViewStateDraging)
76 | {
77 | if (topYOffSet > _height) {
78 | [self setPullDownState:DZPullDownViewStateToggled];
79 | }
80 | else if (topYOffSet < 1)
81 | {
82 | [self setPullDownState:DZPullDownViewStateNormal];
83 | }
84 | }
85 | else if (_state == DZPullDownViewStateToggled)
86 | {
87 | if (topYOffSet < _height) {
88 | [self setPullDownState:DZPullDownViewStateReleasing];
89 | }
90 | }
91 | else if (_state == DZPullDownViewStateReleasing)
92 | {
93 | if (topYOffSet > _height) {
94 | [self setPullDownState:DZPullDownViewStateToggled];
95 | }
96 | else if (topYOffSet < 1)
97 | {
98 | [self setPullDownState:DZPullDownViewStateNormal];
99 | }
100 | }
101 |
102 | if (topYOffSet > _height) {
103 | _topYOffSet = _height;
104 | }
105 | else
106 | {
107 | _topYOffSet = topYOffSet;
108 | }
109 | [self setNeedsLayout];
110 | }
111 | - (void) setAnchorPoint:(CGPoint)anchorpoint forView:(UIView *)view{
112 | CGRect oldFrame = view.frame;
113 | view.layer.anchorPoint = anchorpoint;
114 | view.frame = oldFrame;
115 | }
116 | - (void) layoutSubviews
117 | {
118 | CGFloat fraction = (_height - _topYOffSet)/ self.height;
119 | fraction = MAX(MIN(1, fraction), 0);
120 | _textLabel.frame = self.bounds;
121 | CGFloat angle = fraction*M_PI/2;
122 | CATransform3D transform = CATransform3DMakeRotation(angle, 0.5, 0, 0);
123 | [self setAnchorPoint:CGPointMake(0.5, 1) forView:_textLabel];
124 | [CATransaction begin];
125 | [CATransaction disableActions];
126 | [_textLabel.layer setTransform:CATransform3DPerspect(transform, CGPointMake(0, 0), 100)];
127 | [CATransaction commit];
128 | }
129 |
130 | @end
131 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZSawtoothView.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZSawtoothView.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-24.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DZSawtoothView : UIView
12 | DEFINE_PROPERTY_STRONG(UIColor*, color);
13 | @end
14 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZSawtoothView.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZSawtoothView.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-24.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZSawtoothView.h"
10 |
11 | @implementation DZSawtoothView
12 |
13 | - (id)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | if (self) {
17 | // Initialization code
18 | _color = [UIColor whiteColor];
19 | self.backgroundColor = [UIColor clearColor];
20 | }
21 | return self;
22 | }
23 |
24 |
25 | // Only override drawRect: if you perform custom drawing.
26 | // An empty implementation adversely affects performance during animation.
27 | - (void)drawRect:(CGRect)rect
28 | {
29 | CGFloat height = CGRectViewHeight;
30 | CGFloat width = CGRectViewWidth;
31 | int toothCount = ceil(width/height);
32 | CGFloat toothWidth = width/toothCount;
33 | UIBezierPath* bezierPath = [UIBezierPath bezierPath];
34 | [bezierPath moveToPoint:CGPointMake(0, 0)];
35 | for (int i = 0 ; i < toothCount; i ++) {
36 | CGPoint beginPoint = CGPointMake(i*toothWidth - 3* (i?1:0), 0);
37 | CGPoint bottomPoint = CGPointMake(beginPoint.x + toothWidth/2 + 3*((i == toothCount-1)?1:0), height);
38 | CGPoint endPoint = CGPointMake((i+1)*toothWidth, 0);
39 | [bezierPath addLineToPoint:beginPoint];
40 | [bezierPath addLineToPoint:bottomPoint];
41 | [bezierPath addLineToPoint:endPoint];
42 | }
43 | [bezierPath closePath];
44 | [_color setFill];
45 | [bezierPath fill];
46 | }
47 |
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZSeparationLine.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZSeparationLine.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-23.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DZSeparationLine : UIView
12 | DEFINE_PROPERTY_STRONG(UIColor*, lineColor);
13 | @end
14 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZSeparationLine.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZSeparationLine.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-23.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZSeparationLine.h"
10 |
11 | @interface DZSeparationLine ()
12 | {
13 | UIColor* _topColor;
14 | UIColor* _bottomColor;
15 | }
16 | @end
17 | @implementation DZSeparationLine
18 |
19 | - (id)initWithFrame:(CGRect)frame
20 | {
21 | self = [super initWithFrame:frame];
22 | if (self) {
23 | // Initialization code
24 | }
25 | return self;
26 | }
27 |
28 |
29 | - (void) setLineColor:(UIColor *)lineColor
30 | {
31 | if (_lineColor != lineColor) {
32 | _lineColor = lineColor;
33 | if (_lineColor) {
34 | CGFloat red;
35 | CGFloat green;
36 | CGFloat blue;
37 | CGFloat alpha;
38 | if ([_lineColor getRed:&red green:&green blue:&blue alpha:&alpha]) {
39 | _topColor = [UIColor colorWithRed:red-0.1 green:green-0.1 blue:blue-0.1 alpha:1.0];
40 | _bottomColor = [UIColor colorWithRed:red+0.2 green:green+0.1 blue:blue+0.1 alpha:1.0];
41 | }
42 | }
43 | }
44 | }
45 |
46 |
47 | - (void)drawRect:(CGRect)rect
48 | {
49 | UIBezierPath* bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, CGRectViewWidth, CGRectViewHeight/2)];
50 | [_topColor setFill];
51 | [bezierPath fill];
52 |
53 | UIBezierPath* bottomPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, CGRectViewHeight/2, CGRectViewWidth, CGRectViewHeight/2)];
54 | [_bottomColor setFill];
55 | [bottomPath fill];
56 | }
57 |
58 | @end
59 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableView.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableView.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DZTableViewSourceDelegate.h"
11 | #import "DZTableViewCell.h"
12 | #import "DZPullDownView.h"
13 | #import "DZPullDownDelegate.h"
14 | #import "DZTableViewActionDelegate.h"
15 |
16 | @interface DZTableView : UIScrollView
17 | DEFINE_PROPERTY_STRONG(UIColor*, gradientColor);
18 | @property (nonatomic, strong) UIImageView* backgroudView;
19 | @property (nonatomic, strong, readonly) NSArray* visibleCells;
20 | @property (nonatomic, weak) id actionDelegate;
21 | @property (nonatomic, weak) id dataSource;
22 | @property (nonatomic, assign) NSInteger selectedIndex;
23 | @property (nonatomic, strong) DZPullDownView* topPullDownView;
24 | DEFINE_PROPERTY_STRONG(UIView*, bottomView);
25 | - (DZTableViewCell*) dequeueDZTalbeViewCellForIdentifiy:(NSString*)identifiy;
26 | - (void) reloadData;
27 | - (void) insertRowAt:(NSSet *)rowsSet withAnimation:(BOOL)animation;
28 | - (void) removeRowAt:(NSInteger)row withAnimation:(BOOL)animation;
29 | - (void) manuSelectedRowAt:(NSInteger)row;
30 | @end
31 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewActionDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewActionDelegate.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | @class DZTableView;
11 | @class DZTableViewCell;
12 | @protocol DZTableViewActionDelegate
13 |
14 | - (void) dzTableView:(DZTableView*)tableView didTapAtRow:(NSInteger)row;
15 | - (void) dzTableView:(DZTableView *)tableView deleteCellAtRow:(NSInteger)row;
16 | - (void) dzTableView:(DZTableView *)tableView editCellDataAtRow:(NSInteger)row;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewCell.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DZCellActionsView.h"
11 | #import "DZSeparationLine.h"
12 | @interface DZTableViewCell : UIView
13 | {
14 | UIView* _contentView;
15 | }
16 | DEFINE_PROPERTY_STRONG(DZSeparationLine*, topSeperationLine);
17 | DEFINE_PROPERTY_STRONG(DZSeparationLine*, bottomSeperationLine);
18 | DEFINE_PROPERTY_STRONG(CAGradientLayer*, gradientLayer);
19 | @property (nonatomic, strong) UIView* contentView;
20 | @property (nonatomic, strong) DZCellActionsView* actionsView;
21 | @property (nonatomic, strong) UIView* selectedBackgroudView;
22 | @property (nonatomic, assign) BOOL isSelected;
23 | - (instancetype) initWithIdentifiy:(NSString*)identifiy;
24 |
25 | - (void) showGradientStart:(UIColor*)startColor endColor:(UIColor*)end;
26 | @end
27 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewCell.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTableViewCell.h"
10 | #import "DZTableViewCell_private.h"
11 | #import "UIColor+DZColor.h"
12 | @interface DZTableViewCell ()
13 | {
14 | UIPanGestureRecognizer* _panGestureRcognizer;
15 | CGPoint _startPoint;
16 | }
17 | @end
18 |
19 | @implementation DZTableViewCell
20 | @synthesize identifiy = _identifiy;
21 | @synthesize index = _index;
22 | - (id)initWithFrame:(CGRect)frame
23 | {
24 | self = [super initWithFrame:frame];
25 | if (self) {
26 |
27 | _gradientLayer = [CAGradientLayer layer];
28 | [self.layer addSublayer:_gradientLayer];
29 |
30 | UIView* contentV = [UIView new];
31 | contentV.backgroundColor = [UIColor clearColor];
32 | [self setContentView:contentV];
33 |
34 | UIView* a = [UIView new];
35 | a.backgroundColor = [UIColor lightGrayColor];
36 | [self setSelectedBackgroudView:a];
37 |
38 | //
39 | _panGestureRcognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)];
40 | _panGestureRcognizer.maximumNumberOfTouches = 1;
41 | _panGestureRcognizer.minimumNumberOfTouches = 1;
42 | _panGestureRcognizer.delegate = self;
43 |
44 | [self addGestureRecognizer:_panGestureRcognizer];
45 |
46 | //
47 | DZCellActionsView* actionView = [[DZCellActionsView alloc] init];
48 | [self setActionsView:actionView];
49 |
50 | INIT_SELF_SUBVIEW(DZSeparationLine, _bottomSeperationLine);
51 | INIT_SELF_SUBVIEW(DZSeparationLine, _topSeperationLine);
52 |
53 |
54 | }
55 | return self;
56 | }
57 |
58 | - (void) printtag:(UIButton*)btn
59 | {
60 | NSLog(@"btn %@ tag %d",btn, btn.tag);
61 | }
62 |
63 | - (BOOL) gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
64 | {
65 | return YES;
66 | }
67 |
68 | - (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
69 | {
70 | if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
71 | return YES;
72 | }
73 | return YES;
74 | }
75 |
76 | //- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
77 | //{
78 | // if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
79 | // return YES;
80 | // }
81 | // else
82 | // {
83 | // return YES;
84 | // }
85 | //}
86 | //- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
87 | //{
88 | // if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
89 | // return NO;
90 | // }
91 | // else
92 | // {
93 | // return YES;
94 | // }
95 | //}
96 | - (void) handlePanGestureRecognizer:(UIPanGestureRecognizer*)prcg
97 | {
98 | if ([(UIScrollView *)self.superview isDragging]) {
99 | return;
100 | }
101 | CGPoint point = [prcg locationInView:self];
102 | if (prcg.state == UIGestureRecognizerStateBegan) {
103 | _startPoint = point;
104 | }
105 | else if (prcg.state == UIGestureRecognizerStateChanged)
106 | {
107 | float offset = point.x - _startPoint.x;
108 | _contentView.frame = CGRectOffset(self.bounds, offset, 0);
109 | [_actionsView setEableItemWithMaskOffSet:offset];
110 | }
111 | else if (prcg.state == UIGestureRecognizerStateCancelled)
112 | {
113 | _contentView.frame = self.bounds;
114 | }
115 | else if (prcg.state == UIGestureRecognizerStateEnded)
116 | {
117 | if (_actionsView.abledItem) {
118 | [_actionsView.abledItem sendActionsForControlEvents:UIControlEventAllEvents];
119 | }
120 | _contentView.frame = self.bounds;
121 | }
122 | }
123 |
124 | - (void) prepareForReused
125 | {
126 | _index = NSNotFound;
127 | [self setIsSelected:NO];
128 | }
129 | - (instancetype) initWithIdentifiy:(NSString*)identifiy
130 | {
131 | self = [super init];
132 | if (self) {
133 | _identifiy = identifiy;
134 | }
135 | return self;
136 | }
137 | - (void) setContentView:(UIView *)contentView
138 | {
139 | if (_contentView != contentView) {
140 | [_contentView removeFromSuperview];
141 | _contentView = contentView;
142 | [self addSubview:contentView];
143 | [self setNeedsLayout];
144 | }
145 | }
146 |
147 | - (void) setSelectedBackgroudView:(UIView *)selectedBackgroudView
148 | {
149 | if (_selectedBackgroudView != selectedBackgroudView) {
150 | [_selectedBackgroudView removeFromSuperview];
151 | _selectedBackgroudView = selectedBackgroudView;
152 | [self addSubview:_selectedBackgroudView];
153 | [self setNeedsLayout];
154 | }
155 | }
156 |
157 | - (void) setActionsView:(DZCellActionsView *)actionsView
158 | {
159 | if (_actionsView != actionsView) {
160 | [_actionsView removeFromSuperview];
161 | [self insertSubview:actionsView atIndex:0];
162 | _actionsView = actionsView;
163 | [self setNeedsLayout];
164 | }
165 | }
166 |
167 |
168 | - (void) layoutSubviews
169 | {
170 | _contentView.frame = self.bounds;
171 | _actionsView.frame = self.bounds;
172 | if (_isSelected) {
173 | _selectedBackgroudView.frame = _contentView.bounds;
174 | _selectedBackgroudView.hidden = NO;
175 | [_contentView insertSubview:_selectedBackgroudView atIndex:0];
176 | }
177 | else
178 | {
179 | _selectedBackgroudView.hidden = YES;
180 | }
181 | [self bringSubviewToFront:_topSeperationLine];
182 | [self bringSubviewToFront:_bottomSeperationLine];
183 | LAYOUT_SUBVIEW_TOP_FILL_WIDTH(_topSeperationLine, 0, 0, 3);
184 | LAYOUT_SUBVIEW_BOTTOM_FILL_WIDTH(_bottomSeperationLine, 0, 0, 3);
185 | _gradientLayer.frame = self.bounds;
186 | [_contentView.layer insertSublayer:_gradientLayer atIndex:0];
187 | }
188 |
189 | - (void) setIsSelected:(BOOL)isSelected
190 | {
191 | if (_isSelected != isSelected) {
192 | _isSelected = isSelected;
193 | [self setNeedsLayout];
194 | }
195 | }
196 |
197 | - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
198 | {
199 | [super touchesBegan:touches withEvent:event];
200 | [self setIsSelected:YES];
201 | }
202 |
203 | - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
204 | {
205 | [super touchesEnded:touches withEvent:event];
206 | [self setIsSelected:NO];
207 | }
208 |
209 | - (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
210 | {
211 | [super touchesCancelled:touches withEvent:event];
212 | [self setIsSelected:NO];
213 | }
214 | /*
215 | // Only override drawRect: if you perform custom drawing.
216 | // An empty implementation adversely affects performance during animation.
217 | - (void)drawRect:(CGRect)rect
218 | {
219 | // Drawing code
220 | }
221 | */
222 |
223 | - (void) showGradientStart:(UIColor *)startColor endColor:(UIColor *)end
224 | {
225 | CGFloat white;
226 | CGFloat alpha;
227 | if ([startColor getWhite:&white alpha:&alpha]) {
228 | NSLog(@"aa");
229 | }
230 | _gradientLayer.colors = @[(id)startColor.CGColor, (id)end.CGColor];
231 | _topSeperationLine.lineColor = startColor;
232 | _bottomSeperationLine.lineColor = end;
233 | }
234 |
235 | @end
236 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewCell_private.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewCell_private.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTableViewCell.h"
10 |
11 | @interface DZTableViewCell ()
12 | @property (nonatomic, strong) NSString* identifiy;
13 | @property (nonatomic, assign) NSInteger index;
14 | - (void) prepareForReused;
15 | @end
16 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewController.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DZTableView.h"
11 | @interface DZTableViewController : UIViewController
12 | @property (nonatomic, strong) UIImageView* headerView;
13 | @property (nonatomic, strong) UIImageView* backgroudView;
14 | @property (nonatomic, strong ,readonly) DZTableView* tableView;
15 | @end
16 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewController.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZTableViewController.h"
10 | #import "DZGeometryTools.h"
11 | #import "DZInputCellView.h"
12 | @interface DZTableViewController ()
13 | {
14 | }
15 | @end
16 |
17 | @implementation DZTableViewController
18 | @synthesize tableView = _tableView;
19 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
20 | {
21 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
22 | if (self) {
23 | // Custom initialization
24 | }
25 | return self;
26 | }
27 |
28 | - (DZTableView*) tableView
29 | {
30 | if (!_tableView) {
31 | _tableView = [[DZTableView alloc] initWithFrame:CGRectLoadViewFrame];
32 | _tableView.dataSource = self;
33 | _tableView.delegate = self;
34 | _tableView.actionDelegate = self;
35 | }
36 | return _tableView;
37 | }
38 |
39 | - (void) loadView
40 | {
41 | [super loadView];
42 | DZTableView* tableView = self.tableView;
43 | DZPullDownView* pullView = [[DZPullDownView alloc] init];
44 | pullView.height = 44;
45 | pullView.delegate = self;
46 | tableView.topPullDownView = pullView;
47 | }
48 | - (void)viewDidLoad
49 | {
50 | [super viewDidLoad];
51 |
52 | _backgroudView = [UIImageView new];
53 | _backgroudView.contentMode = UIViewContentModeScaleAspectFill;
54 | [self.view addSubview:_backgroudView];
55 |
56 | _headerView = [[UIImageView alloc] init];
57 | [self.view addSubview:_headerView];
58 | [self.view addSubview:self.tableView];
59 |
60 | _headerView.backgroundColor = [UIColor darkGrayColor];
61 | _headerView.alpha = 0.8;
62 | _headerView.layer.cornerRadius = 10;
63 |
64 | // Do any additional setup after loading the view.
65 | }
66 | - (void) viewWillLayoutSubviews
67 | {
68 | [super viewWillLayoutSubviews];
69 | _backgroudView.frame = self.view.bounds;
70 | [self.view insertSubview:_backgroudView atIndex:0];
71 | _headerView.frame = CGRectMake(5, 20, CGRectGetWidth(self.view.frame)-10, 20);
72 |
73 | _tableView.frame = CGRectMake(10, CGRectGetMaxY(_headerView.frame)-10, CGRectGetWidth(self.view.frame)-20, CGRectGetHeight(self.view.frame) - CGRectGetMaxY(_headerView.frame));
74 | [_tableView reloadData];
75 | }
76 | - (void) viewWillAppear:(BOOL)animated
77 | {
78 | [super viewWillAppear:animated];
79 |
80 | }
81 |
82 | - (void)didReceiveMemoryWarning
83 | {
84 | [super didReceiveMemoryWarning];
85 | // Dispose of any resources that can be recreated.
86 | }
87 |
88 |
89 | @end
90 |
--------------------------------------------------------------------------------
/DZTableView/TableView/DZTableViewSourceDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewSourceDelegate.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-12.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class DZTableView;
12 | @class DZTableViewCell;
13 | @class DZPullDownView;
14 | @protocol DZTableViewSourceDelegate
15 | - (NSInteger) numberOfRowsInDZTableView:(DZTableView*)tableView;
16 | - (DZTableViewCell*) dzTableView:(DZTableView*)tableView cellAtRow:(NSInteger)row;
17 | - (CGFloat) dzTableView:(DZTableView*)tableView cellHeightAtRow:(NSInteger)row;
18 | @end
19 |
--------------------------------------------------------------------------------
/DZTableView/UITools/DZAnimationDefines.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZAnimationDefines.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #ifndef TimeUI_DZAnimationDefines_h
10 | #define TimeUI_DZAnimationDefines_h
11 |
12 | typedef void(^DZAnimationCompletion)(void);
13 |
14 |
15 |
16 | static float const DZAnimationDefualtDuration = 0.25;
17 | #endif
18 |
--------------------------------------------------------------------------------
/DZTableView/UITools/DZDirection.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZDirection.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-2-13.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef enum {
12 | DZDirectionLeft,
13 | DZDirectionRight,
14 | DZDirectionUp,
15 | DZDirectionDown
16 | }DZDirection;
17 |
18 |
19 | #ifdef __cplusplus
20 | extern "C"
21 | {
22 | #endif
23 | CGVector CGVectorWithPoints(CGPoint start, CGPoint end);
24 | DZDirection DZDirectionWithPoints(CGPoint start, CGPoint end);
25 | DZDirection DZDirectionVerticalityWithPoints(CGPoint start, CGPoint end);
26 | #ifdef __cplusplus
27 | }
28 | #endif
29 |
30 |
--------------------------------------------------------------------------------
/DZTableView/UITools/DZDirection.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZDirection.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-2-13.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZDirection.h"
10 | #import "DZGeometryTools.h"
11 |
12 | CGVector __CGVectorUnit()
13 | {
14 | static CGVector v ;
15 | static dispatch_once_t onceToken;
16 | dispatch_once(&onceToken, ^{
17 | v = CGVectorMake(1, 0);
18 | });
19 | return v;
20 | }
21 |
22 | #define CGVectorUint __CGVectorUnit()
23 |
24 | typedef float CGAngle;
25 | typedef float CGDegree;
26 |
27 |
28 | CGVector CGVectorWithPoints(CGPoint start, CGPoint end)
29 | {
30 | return CGVectorMake(end.x - start.x, end.y - start.y);
31 | }
32 |
33 | CGAngle CGAngleBetweenVector(CGVector v1, CGVector v2)
34 | {
35 | double value = v1.dx * v2.dx + v1.dy * v2.dy;
36 | double v1Val = sqrt(v1.dx * v1.dx + v1.dy * v1.dy);
37 | double v2Val = sqrt(v2.dx * v2.dx + v2.dy* v2.dy);
38 |
39 | double cosValue = 0;
40 | double mul = v1Val * v2Val;
41 | if (mul != 0) {
42 | cosValue = value / mul;
43 | } else
44 | {
45 | return 0;
46 | }
47 | if (cosValue > 1 ) {
48 | cosValue = 1;
49 | } else if (cosValue < -1) {
50 | cosValue = -1;
51 | }
52 | return acos(cosValue);
53 | }
54 |
55 | CGAngle CGVectorAngle(CGVector vector)
56 | {
57 | return CGAngleBetweenVector(vector, CGVectorUint);
58 | }
59 |
60 | CGAngle CGAngleWithPoints(CGPoint start, CGPoint end)
61 | {
62 | return CGVectorAngle(CGVectorWithPoints(start, end));
63 | }
64 |
65 | DZDirection DZDirectionWithPoints(CGPoint start, CGPoint end)
66 | {
67 | CGAngle angle = CGAngleWithPoints(start, end);
68 | CGDegree degree = ANGLE_TO_DEGREE(angle);
69 |
70 | NSLog(@"degree is %f",degree);
71 | return DZDirectionUp;
72 | }
73 |
74 | DZDirection DZDirectionVerticalityWithPoints(CGPoint start, CGPoint end)
75 | {
76 | CGAngle angle = CGAngleWithPoints(start, end);
77 | CGDegree degree = ANGLE_TO_DEGREE(angle);
78 | if (end.y > start.y) {
79 | return DZDirectionDown;
80 | }
81 | else
82 | {
83 | return DZDirectionUp;
84 | }
85 | // if (degree > 0 && degree < 180) {
86 | // return DZDirectionUp;
87 | // } else
88 | // {
89 | // return DZDirectionDown;
90 | // }
91 | }
--------------------------------------------------------------------------------
/DZTableView/UITools/DZGeometryTools.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZGeometryTools.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-10.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DZDevices.h"
11 |
12 | //角度转化
13 | #define DEGREE_TO_ANGLE(x) (x / 180.0f * M_PI)
14 | #define ANGLE_TO_DEGREE(x) (x * 180.0f / M_PI)
15 |
16 | //
17 |
18 | #define CGRectViewWidth (CGRectGetWidth(self.bounds))
19 | #define CGRectViewHeight CGRectGetHeight(self.bounds)
20 |
21 | #define CGRectVCWidth (CGRectGetWidth(self.view.bounds))
22 | #define CGRectVCHeight CGRectGetHeight(self.view.bounds)
23 |
24 |
25 | #define CGRectGetViewControllerWidth (CGRectGetWidth(self.view.frame))
26 | #define CGRectGetViewControllerHeight (CGRectGetHeight(self.view.frame))
27 | #ifdef __cplusplus
28 | extern "C"
29 | {
30 | #endif
31 | /**
32 | * 打印一个CGRect
33 | *
34 | * @param rect 要打印的CGRect
35 | */
36 | void CGPrintRect(CGRect rect );
37 | float CGDistanceBetweenPoints(CGPoint p1, CGPoint p2);
38 | CGRect CGRectCenter(CGRect rect, CGSize size);
39 | CGPoint CGPointCenterRect(CGRect rect);
40 | CGRect CGRectUseEdge(CGRect parent, UIEdgeInsets edge);
41 | CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ);
42 | CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ);
43 |
44 |
45 | CGRect CGRectCenterSubSize(CGRect rect, CGSize size);
46 | CGRect CGRectWithEdgeInsetsForRect(UIEdgeInsets edge, CGRect rect);
47 | CGPoint CGRectGetCenter(CGRect rect);
48 | CGPoint CGPointSubtraction(CGPoint p1, CGPoint p2);
49 | CGSize CGSizeScaleToSize(CGSize originSize, CGSize aimSize);
50 | CGFloat CGPointDistance(CGPoint p1, CGPoint p2);
51 | CGSize CGSizeScale(CGSize size, CGFloat scale);
52 | //计算一个Size在Rect中的居住位置
53 | CGRect CGRectOfCenterForContainsSize(CGRect rect , CGSize size);
54 | BOOL NSRangeCotainsIndex(NSRange range, NSInteger index);
55 | //
56 | void LogCGRect(NSString* prefix, CGRect rect);
57 | void LogCGPoint(NSString* prefix, CGPoint point);
58 | void LogCGSize(NSString* prefix, CGSize point);
59 | NSString* DevicePlatfromString();
60 | BOOL UIDeviceISIphone5s();
61 | CGRect CGRectExpandPoint(CGPoint pint , CGSize aimSize);
62 | #ifdef __cplusplus
63 | }
64 | #endif
65 |
66 | #define CGRectSetX(rect, x) CGRectMake(x, CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect));
67 | #define CGRectSetY(rect, y) CGRectMake(CGRectGetMinX(rect), y, CGRectGetWidth(rect), CGRectGetHeight(rect));
68 | #define CGRectSetWidth(rect, width) CGRectMake(x , y, width , CGRectGetHeight(rect));
69 | #define CGRectSetHeight(rect, height) CGRectMake(x, y, CGRectGetWidth(rect), width);
70 |
71 | #define LAYOUT_View_Frame(view, x, y , width, height) view.frame = CGRectMake(x,y, width, height)
72 | #define LAYOUT_SubView_Fill(view) view.frame = self.bounds
73 |
74 |
75 | #define MARGIN_LEFT left
76 | #define MARGIN_RIGHT right
77 | #define MARGIN_ALL all
78 |
79 |
80 | #define MARGIN_NAME_X(subfix) margin_x_##subfix
81 | #define MARGIN_NAME_Y(subfix) margin_y_##subfix
82 |
83 |
84 | #define MARGIN_NAME_X_ALL MARGIN_NAME_X(MARGIN_X_ALL)
85 |
86 |
87 | #define LAYOUT_DEFINE_MARGIN_X(subfix, x) static float MARGIN_NAME_X(subfix) = x
88 | #define LAYOUT_DEFINE_MARGIN_Y(subfix, y) static float MARGIN_NAME_Y(subfix) = y
89 | #define LAYOUT_DEFINE_MARGIN_X_ALL(x) LAYOUT_DEFINE_MARGIN_X(MARGIN_ALL,x)
90 | #define LAYOUT_DEFINE_MARGIN_Y_ALL(x) LAYOUT_DEFINE_MARGIN_Y(MARGIN_ALL,x)
91 |
92 |
93 | #define LAYOUT_RECT_ORIGIN_RELY_MIN_X(rect, view, margin_x) rect.origin.x = CGRectGetMinX(view.frame) + margin_x
94 | #define LAYOUT_RECT_ORIGIN_RELY_MAX_X(rect, view, margin_x) rect.origin.x = CGRectGetMaxX(view.frame) + margin_x
95 | #define LAYOUT_RECT_ORIGIN_RELY_MIN_Y(rect, view, margin_y) rect.origin.y = CGRectGetMinY(view.frame) + margin_y
96 | #define LAYOUT_RECT_ORIGIN_RELY_MAX_Y(rect, view, margin_y) rect.origin.y = CGRectGetMaxY(view.frame) + margin_y
97 |
98 |
99 | #define LAYOUT_VIEW_RELY_MAX_X_Y(view, xRV, xMargin,yRV, yMargin, width_, height_) CGRect rect##view = CGRectZero;\
100 | LAYOUT_RECT_ORIGIN_RELY_MAX_X(rect##view, xRV, xMargin);\
101 | LAYOUT_RECT_ORIGIN_RELY_MAX_Y(rect##view, yRV, yMargin);\
102 | rect##view.size.width = width_;\
103 | rect##view.size.height = height_;
104 |
105 |
106 | //y依赖于顶部元素,并且尽可能填充满width的布局
107 | #define LAYOUT_SUBVIEW_FILL_WIDTH_RELY_MAX_Y(view, supView, xMargin, yRV, yMargin, height__) CGRect rect##view = CGRectZero;\
108 | LAYOUT_RECT_ORIGIN_RELY_MAX_Y(rect##view, yRV, yMargin);\
109 | rect##view.size.width = CGRectGetWidth(supView.frame) - xMargin*2;\
110 | rect##view.origin.x = xMargin;\
111 | rect##view.size.height = height__;\
112 | view.frame = rect##view;
113 |
114 | #define LAYOUT_VIEW_FILL_WIDTH_RELY_MAX_Y(view, xMargin, yRV, yMargin, height__)\
115 | LAYOUT_SUBVIEW_FILL_WIDTH_RELY_MAX_Y(view, self, xMargin, yRV, yMargin, height__)
116 |
117 | //顶部固定高度,铺满width的布局
118 | #define LAYOUT_VIEW_TOP_FILL_WIDTH(view, sView__, xMargin, yMargin, refHeight__) CGRect rect##view = CGRectZero;\
119 | rect##view.origin.x = xMargin;\
120 | rect##view.origin.y = yMargin;\
121 | rect##view.size.width = CGRectGetWidth(sView__.bounds) - xMargin*2;\
122 | rect##view.size.height = refHeight__ - yMargin;\
123 | view.frame = rect##view;
124 |
125 | #define LAYOUT_SUBVIEW_TOP_FILL_WIDTH(view, xMargin, yMargin, refHeight__) LAYOUT_VIEW_TOP_FILL_WIDTH(view, self, xMargin, yMargin, refHeight__)
126 |
127 |
128 |
129 | //顶部固定高度,铺满width的布局
130 | #define LAYOUT_VIEW_BOTTOM_FILL_WIDTH(view, sView__, xMargin, yMargin, refHeight__) CGRect rect##view = CGRectZero;\
131 | rect##view.origin.x = xMargin;\
132 | rect##view.origin.y = CGRectGetHeight(sView__.bounds) - refHeight__;\
133 | rect##view.size.width = CGRectGetWidth(sView__.bounds) - xMargin*2;\
134 | rect##view.size.height = refHeight__ - yMargin;\
135 | view.frame = rect##view;
136 |
137 | #define LAYOUT_SUBVIEW_BOTTOM_FILL_WIDTH(view, xMargin, yMargin, refHeight__) LAYOUT_VIEW_BOTTOM_FILL_WIDTH(view, self, xMargin, yMargin, refHeight__)
138 |
139 | #define LAYOUT_SUBVIEW_CENTER(view, refSV, xMargin ,yMargin) view.frame = CGRectCenter(refSV.bounds, CGSizeMake(CGRectGetWidth(refSV.bounds) - xMargin*2,CGRectGetHeight(refSV.bounds) - yMargin*2));
140 |
141 | @interface DZGeometryTools : NSObject
142 |
143 | @end
144 |
--------------------------------------------------------------------------------
/DZTableView/UITools/DZGeometryTools.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZGeometryTools.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-10.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZGeometryTools.h"
10 | #import "UIDeviceHardware.h"
11 | NSString* DevicePlatfromString()
12 | {
13 | static NSString* str = nil;
14 | static dispatch_once_t onceToken;
15 | dispatch_once(&onceToken, ^{
16 | str = [UIDeviceHardware platformString];
17 | });
18 | return str;
19 | }
20 |
21 |
22 | BOOL UIDeviceISIphone5s()
23 | {
24 | static BOOL isiphone5s = NO;
25 | // static dispatch_once_t onceToken;
26 | // dispatch_once(&onceToken, ^{
27 | // isiphone5s = [DevicePlatfromString() isEqualToString:IPHONE_5S];
28 | // });
29 | // return isiphone5s;
30 | return isiphone5s;
31 | }
32 |
33 | void LogCGRect(NSString* prefix, CGRect rect)
34 | {
35 | NSLog(@"%@, x:%f--y:%f--width:%f--height:%f",prefix, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
36 | }
37 | void LogCGPoint(NSString* prefix, CGPoint point)
38 | {
39 | NSLog(@"%@ , x:%f--y:%f",prefix,point.x, point.y );
40 | }
41 | void LogCGSize(NSString* prefix, CGSize size)
42 | {
43 | NSLog(@"%@, width:%f--height:%f",prefix ,size.width, size.height);
44 | }
45 |
46 | CGPoint CGRectGetCenter(CGRect rect)
47 | {
48 | return CGPointMake(CGRectGetMinX(rect) + CGRectGetWidth(rect) /2.0, CGRectGetMinY(rect) + CGRectGetHeight(rect)/ 2.0f);
49 | }
50 |
51 | CGPoint CGPointSubtraction(CGPoint p1, CGPoint p2)
52 | {
53 | return CGPointMake(p1.x - p2.x, p1.y - p2.y);
54 | }
55 | CGFloat CGPointDistance(CGPoint p1, CGPoint p2)
56 | {
57 | return sqrtf(powf(p1.x - p2.x , 2) + pow(p1.y - p2.y, 2));
58 | }
59 |
60 | CGRect CGRectWithEdgeInsetsForRect(UIEdgeInsets edge, CGRect rect)
61 | {
62 | return CGRectMake(CGRectGetMinX(rect) + edge.left, CGRectGetMinY(rect) + edge.top, CGRectGetWidth(rect) - edge.left- edge.right, CGRectGetHeight(rect) - edge.top - edge.bottom);
63 | }
64 |
65 | CGSize CGSizeScaleToSize(CGSize originSize, CGSize aimSize)
66 | {
67 | if (originSize.width < originSize.height) {
68 | return CGSizeMake(aimSize.width, originSize.height * aimSize.width/ originSize.width);
69 | }
70 | else
71 | {
72 | return CGSizeMake(originSize.width * aimSize.height / originSize.height, aimSize.height);
73 | }
74 | }
75 |
76 | CGSize CGSizeScale(CGSize size, CGFloat scale)
77 | {
78 | return CGSizeMake(size.width * scale, size.height * scale);
79 | }
80 | CGRect CGRectCenterSubSize(CGRect rect, CGSize size)
81 | {
82 | float aimWidth = (CGRectGetWidth(rect) - size.width );
83 | float aminHeiht = CGRectGetHeight(rect) - size.height;
84 | return CGRectMake(CGRectGetMinX(rect) + size.width/2, CGRectGetMinY(rect) + size.height/2, aimWidth, aminHeiht);
85 | }
86 |
87 | BOOL NSRangeCotainsIndex(NSRange range, NSInteger index)
88 | {
89 | return index >= range.location && index < range.location + index;
90 | }
91 |
92 | CGRect CGRectOfCenterForContainsSize(CGRect rect , CGSize size)
93 | {
94 | return CGRectMake((CGRectGetWidth(rect) - size.width)/2, (CGRectGetHeight(rect) - size.height) /2, size.width, size.height);
95 | }
96 |
97 | void CGPrintRect(CGRect rect )
98 | {
99 | NSLog(@"rec--|origin x:%f |y:%f |width:%f | height:%f", CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect));
100 | }
101 |
102 | CGRect CGRectExpandPoint(CGPoint pint , CGSize aimSize)
103 | {
104 | return CGRectMake(pint.x - aimSize.width/2, pint.y - aimSize.height/2, aimSize.width, aimSize.height);
105 | }
106 |
107 | CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
108 | {
109 | CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
110 | CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
111 | CATransform3D scale = CATransform3DIdentity;
112 | scale.m34 = -1.0f/disZ;
113 | return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
114 | }
115 | CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
116 | {
117 | return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
118 | }
119 |
120 |
121 | CGRect CGRectUseEdge(CGRect parent, UIEdgeInsets edge)
122 | {
123 | float startX =CGRectGetMinX(parent) + edge.left;
124 | float startY = CGRectGetMinY(parent) + edge.top;
125 | float endX = CGRectGetMaxX(parent) - edge.right;
126 | float endY = CGRectGetMaxY(parent) - edge.bottom;
127 | return CGRectMake(startX, startY, endX - startX, endY - startY);
128 | }
129 |
130 | CGPoint CGPointCenterRect(CGRect rect)
131 | {
132 | return CGPointMake(CGRectGetWidth(rect)/2, CGRectGetHeight(rect)/2);
133 | }
134 |
135 | float CGDistanceBetweenPoints(CGPoint p1, CGPoint p2)
136 | {
137 | return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
138 | }
139 |
140 | CGRect CGRectCenter(CGRect rect, CGSize size)
141 | {
142 | return CGRectMake((CGRectGetWidth(rect) - size.width) /2, (CGRectGetHeight(rect) - size.height) /2, size.width, size.height);
143 | }
144 |
145 | @implementation DZGeometryTools
146 |
147 | @end
148 |
--------------------------------------------------------------------------------
/DZTableView/UITools/DZSendSelector.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZSendSelector.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-15.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 | #ifdef __cplusplus
11 | extern "C" {
12 | #endif
13 | extern void addArgumentToArray(NSMutableArray* array, id param);
14 | extern void SendSelectorToObjectInMainThread(SEL selector, id observer, id params);
15 | extern void SendSelectorToObjectInMainThreadWith2Params(SEL selector, id observer, id params, id);
16 | extern void SendSelectorToObjectInMainThreadWith3Params(SEL selector, id observer , id param1, id param2, id param3) ;
17 | extern void SendSelectorToObjectInMainThreadWithoutParams(SEL selecrot, id object);
18 |
19 | #ifdef __cplusplus
20 | }
21 | #endif
--------------------------------------------------------------------------------
/DZTableView/UITools/DZSendSelector.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZSendSelector.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-15.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "DZSendSelector.h"
10 |
11 | void addArgumentToArray(NSMutableArray* array, id param)
12 | {
13 | if (param) {
14 | [array addObject:param];
15 | }
16 | else
17 | {
18 | [array addObject:[NSNull null]];
19 | }
20 | }
21 |
22 | void (^SendSelectorToObjectInMainThreadWithParams)(SEL selector, id observer, NSArray* params) = ^(SEL selector, id observer, NSArray* params)
23 | {
24 | if([observer respondsToSelector:selector])
25 | {
26 | NSMethodSignature* methodSignature = [[observer class] instanceMethodSignatureForSelector:selector];
27 | if(methodSignature)
28 | {
29 | NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
30 | [invocation setSelector:selector];
31 | [invocation setTarget:observer];
32 | NSInteger count = [params count];
33 | for(int i = 2 ; i < count + 2; ++i)
34 | {
35 | id argument = [params objectAtIndex:i-2];
36 | if([argument isKindOfClass:[NSNull class]])
37 | {
38 | continue;
39 | }
40 | else
41 | {
42 | [invocation setArgument:&argument atIndex:i];
43 | }
44 | }
45 | [invocation retainArguments];
46 | if([NSThread isMainThread])
47 | {
48 | [invocation invoke];
49 | }
50 | else
51 | {
52 | dispatch_async(dispatch_get_main_queue(), ^{
53 | [invocation invoke];
54 | });
55 | }
56 | }
57 | }
58 | };
59 | void SendSelectorToObjectInMainThread(SEL selector, id observer, id params)
60 | {
61 | NSMutableArray* array = [NSMutableArray array];
62 | addArgumentToArray(array, params);
63 | SendSelectorToObjectInMainThreadWithParams(selector, observer, array);
64 |
65 | };
66 | void SendSelectorToObjectInMainThreadWithoutParams(SEL selecrot, id object)
67 | {
68 | SendSelectorToObjectInMainThreadWithParams(selecrot,object,nil);
69 | };
70 |
71 |
72 | void SendSelectorToObjectInMainThreadWith2Params(SEL selector, id observer, id params1, id param2){
73 |
74 | NSMutableArray* array = [NSMutableArray array];
75 | addArgumentToArray(array, params1);
76 | addArgumentToArray(array, param2);
77 | SendSelectorToObjectInMainThreadWithParams(selector, observer, array);
78 |
79 | };
80 |
81 | void SendSelectorToObjectInMainThreadWith3Params(SEL selector, id observer , id param1, id param2, id param3)
82 | {
83 | NSMutableArray* array = [NSMutableArray array];
84 | addArgumentToArray(array, param1);
85 | addArgumentToArray(array, param2);
86 | addArgumentToArray(array, param3);
87 | SendSelectorToObjectInMainThreadWithParams(selector,observer,array);
88 | };
--------------------------------------------------------------------------------
/DZTableView/UITools/DZUITools.h:
--------------------------------------------------------------------------------
1 | //
2 | // DZUITools.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-10.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #ifndef TimeUI_DZUITools_h
10 | #define TimeUI_DZUITools_h
11 |
12 | #import "DZGeometryTools.h"
13 | #import "UIView+Shadow.h"
14 | #import "DZDevices.h"
15 | #import "UIView+AddTaps.h"
16 | #import "DZDirection.h"
17 | #import "DZAnimationDefines.h"
18 | #endif
19 |
--------------------------------------------------------------------------------
/DZTableView/UITools/HexColor.h:
--------------------------------------------------------------------------------
1 | //
2 | // HexColor.h
3 | //
4 | // Created by Marius Landwehr on 02.12.12.
5 | // The MIT License (MIT)
6 | // Copyright (c) 2013 Marius Landwehr marius.landwehr@gmail.com
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11 | //
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 | //
14 |
15 | #if (TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE)
16 | #import
17 | #define HXColor UIColor
18 | #else
19 | #import
20 | #define HXColor NSColor
21 | #endif
22 |
23 | @interface HXColor (HexColorAddition)
24 |
25 | + (HXColor *)colorWithHexString:(NSString *)hexString;
26 | + (HXColor *)colorWithHexString:(NSString *)hexString alpha:(CGFloat)alpha;
27 |
28 | + (HXColor *)colorWith8BitRed:(NSInteger)red green:(NSInteger)green blue:(NSInteger)blue;
29 | + (HXColor *)colorWith8BitRed:(NSInteger)red green:(NSInteger)green blue:(NSInteger)blue alpha:(CGFloat)alpha;
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/DZTableView/UITools/HexColor.m:
--------------------------------------------------------------------------------
1 | //
2 | // HexColor.m
3 | //
4 | // Created by Marius Landwehr on 02.12.12.
5 | // The MIT License (MIT)
6 | // Copyright (c) 2013 Marius Landwehr marius.landwehr@gmail.com
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11 | //
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 | //
14 |
15 | #import "HexColor.h"
16 |
17 | @implementation HXColor (HexColorAddition)
18 |
19 | + (HXColor *)colorWithHexString:(NSString *)hexString
20 | {
21 | return [[self class] colorWithHexString:hexString alpha:1.0];
22 | }
23 |
24 | + (HXColor *)colorWithHexString:(NSString *)hexString alpha:(CGFloat)alpha
25 | {
26 | // Check for hash and add the missing hash
27 | if('#' != [hexString characterAtIndex:0])
28 | {
29 | hexString = [NSString stringWithFormat:@"#%@", hexString];
30 | }
31 |
32 | // check for string length
33 | assert(7 == hexString.length || 4 == hexString.length);
34 |
35 | // check for 3 character HexStrings
36 | hexString = [[self class] hexStringTransformFromThreeCharacters:hexString];
37 |
38 | NSString *redHex = [NSString stringWithFormat:@"0x%@", [hexString substringWithRange:NSMakeRange(1, 2)]];
39 | unsigned redInt = [[self class] hexValueToUnsigned:redHex];
40 |
41 | NSString *greenHex = [NSString stringWithFormat:@"0x%@", [hexString substringWithRange:NSMakeRange(3, 2)]];
42 | unsigned greenInt = [[self class] hexValueToUnsigned:greenHex];
43 |
44 | NSString *blueHex = [NSString stringWithFormat:@"0x%@", [hexString substringWithRange:NSMakeRange(5, 2)]];
45 | unsigned blueInt = [[self class] hexValueToUnsigned:blueHex];
46 |
47 | HXColor *color = [HXColor colorWith8BitRed:redInt green:greenInt blue:blueInt alpha:alpha];
48 |
49 | return color;
50 | }
51 |
52 | + (HXColor *)colorWith8BitRed:(NSInteger)red green:(NSInteger)green blue:(NSInteger)blue
53 | {
54 | return [[self class] colorWith8BitRed:red green:green blue:blue alpha:1.0];
55 | }
56 |
57 | + (HXColor *)colorWith8BitRed:(NSInteger)red green:(NSInteger)green blue:(NSInteger)blue alpha:(CGFloat)alpha
58 | {
59 | HXColor *color = nil;
60 | #if (TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE)
61 | color = [HXColor colorWithRed:(float)red/255 green:(float)green/255 blue:(float)blue/255 alpha:alpha];
62 | #else
63 | color = [HXColor colorWithCalibratedRed:(float)red/255 green:(float)green/255 blue:(float)blue/255 alpha:alpha];
64 | #endif
65 |
66 | return color;
67 | }
68 |
69 | + (NSString *)hexStringTransformFromThreeCharacters:(NSString *)hexString
70 | {
71 | if(hexString.length == 4)
72 | {
73 | hexString = [NSString stringWithFormat:@"#%@%@%@%@%@%@",
74 | [hexString substringWithRange:NSMakeRange(1, 1)],[hexString substringWithRange:NSMakeRange(1, 1)],
75 | [hexString substringWithRange:NSMakeRange(2, 1)],[hexString substringWithRange:NSMakeRange(2, 1)],
76 | [hexString substringWithRange:NSMakeRange(3, 1)],[hexString substringWithRange:NSMakeRange(3, 1)]];
77 | }
78 |
79 | return hexString;
80 | }
81 |
82 | + (unsigned)hexValueToUnsigned:(NSString *)hexValue
83 | {
84 | unsigned value = 0;
85 |
86 | NSScanner *hexValueScanner = [NSScanner scannerWithString:hexValue];
87 | [hexValueScanner scanHexInt:&value];
88 |
89 | return value;
90 | }
91 |
92 |
93 | @end
94 |
--------------------------------------------------------------------------------
/DZTableView/UITools/UIColor+DZColor.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+DZColor.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-23.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIColor (DZColor)
12 | - (UIColor*) colorWithOffset:(float)offset;
13 | + (NSDictionary*) typeCellColors;
14 | @end
15 |
--------------------------------------------------------------------------------
/DZTableView/UITools/UIColor+DZColor.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+DZColor.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 14-1-23.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "UIColor+DZColor.h"
10 | #import "HexColor.h"
11 | @implementation UIColor (DZColor)
12 | - (UIColor*) colorWithOffset:(float)offset
13 | {
14 | CGFloat red;
15 | CGFloat green;
16 | CGFloat blue;
17 | CGFloat alpha;
18 | if ([self getRed:&red green:&green blue:&blue alpha:&alpha]) {
19 | return [UIColor colorWithRed:red+offset green:green+offset blue:blue+offset alpha:alpha];
20 | }
21 | return self;
22 | }
23 |
24 | + (NSDictionary*) typeCellColors
25 | {
26 | return @{@(0): [UIColor colorWithHexString:@"#4859ad"],
27 | @(1): [UIColor colorWithHexString:@"#bd64d3"],
28 | @(2): [UIColor colorWithHexString:@"#2ea9df"],
29 | @(3): [UIColor colorWithHexString:@"76c61e"],
30 | @(4): [UIColor colorWithHexString:@"ffc000"],
31 | @(5): [UIColor colorWithHexString:@"#ffb19b"]};
32 | }
33 | @end
34 |
--------------------------------------------------------------------------------
/DZTableView/UITools/UIView+AddTaps.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+AddTaps.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIView (AddTaps)
12 | - (void) addTapTarget:(id)target selector:(SEL)selecotr;
13 | @end
14 |
--------------------------------------------------------------------------------
/DZTableView/UITools/UIView+AddTaps.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+AddTaps.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-13.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "UIView+AddTaps.h"
10 |
11 | @implementation UIView (AddTaps)
12 | - (void) addTapTarget:(id)target selector:(SEL)selecotr
13 | {
14 | self.userInteractionEnabled = YES;
15 | UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:target action:selecotr];
16 | tapGesture.numberOfTapsRequired = 1;
17 | tapGesture.numberOfTouchesRequired = 1;
18 | [self addGestureRecognizer:tapGesture];
19 | }
20 | @end
21 |
--------------------------------------------------------------------------------
/DZTableView/UITools/UIView+Shadow.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Shadow.h
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-10.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIView (Shadow)
12 | - (void) addShadow;
13 | @end
14 |
--------------------------------------------------------------------------------
/DZTableView/UITools/UIView+Shadow.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Shadow.m
3 | // TimeUI
4 | //
5 | // Created by Stone Dong on 13-12-10.
6 | // Copyright (c) 2013年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "UIView+Shadow.h"
10 |
11 | @implementation UIView (Shadow)
12 | - (void) addShadow
13 | {
14 | CALayer* layer = self.layer;
15 | layer.shadowColor = [UIColor lightGrayColor].CGColor;
16 | layer.shadowOffset = CGSizeMake(5, 5);
17 | layer.shadowOpacity = 0.4;
18 | layer.shadowRadius = 5;
19 | }
20 | @end
21 |
--------------------------------------------------------------------------------
/DZTableView/UIView+RedPoint.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+RedPoint.h
3 | // DZTableView
4 | //
5 | // Created by stonedong on 14-2-27.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIView (RedPoint)
12 | - (void) triggleRePoint:(NSString*)path;
13 | - (void) cleanRedPoint;
14 | @end
15 |
--------------------------------------------------------------------------------
/DZTableView/UIView+RedPoint.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+RedPoint.m
3 | // DZTableView
4 | //
5 | // Created by stonedong on 14-2-27.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import "UIView+RedPoint.h"
10 | #import
11 |
12 |
13 |
14 | static void * kRedhed = &kRedhed;
15 |
16 | @implementation UIView (RedPoint)
17 |
18 | - (void) setTap:(UITapGestureRecognizer*)tap
19 | {
20 | // objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
21 | }
22 |
23 | //
24 | - (UIView*) redItem
25 | {
26 | return objc_getAssociatedObject(self, kRedhed);
27 | }
28 |
29 | - (void) setRedItem:(UIView*)a
30 | {
31 | objc_setAssociatedObject(self, kRedhed, a, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
32 | }
33 |
34 | //
35 |
36 | - (void) addBeforeHidePoint:(id)target sel:(SEL)selecotr
37 | {
38 | // .....
39 | }
40 | - (void) triggleRePoint:(NSString *)path
41 | {
42 | UIView* a;
43 |
44 | UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hidePoint)];
45 | [self addGestureRecognizer:tap];
46 | }
47 |
48 | - (void) beforeHidePoint:(UIView*)item
49 | {
50 | id targt;
51 | SEL selector;
52 | [targt performSelector:selector];
53 |
54 |
55 | }
56 |
57 | - (void) aa
58 | {
59 | UITableViewCell* cell;
60 | [cell cleanRedPoint];
61 |
62 | [cell triggleRePoint:@"asdf"];
63 | }
64 |
65 | - (void) hidePoint
66 | {
67 |
68 |
69 |
70 | }
71 | @end
72 |
--------------------------------------------------------------------------------
/DZTableView/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/DZTableView/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // DZTableView
4 | //
5 | // Created by stonedong on 14-2-26.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "DZAppDelegate.h"
12 |
13 | int main(int argc, char * argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DZAppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DZTableView/number_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/DZTableView/number_bg.png
--------------------------------------------------------------------------------
/DZTableViewTests/DZTableViewTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | com.dzpqzb.com.${PRODUCT_NAME:rfc1034identifier}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/DZTableViewTests/DZTableViewTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // DZTableViewTests.m
3 | // DZTableViewTests
4 | //
5 | // Created by stonedong on 14-2-26.
6 | // Copyright (c) 2014年 Stone Dong. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DZTableViewTests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation DZTableViewTests
16 |
17 | - (void)setUp
18 | {
19 | [super setUp];
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | - (void)tearDown
24 | {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | [super tearDown];
27 | }
28 |
29 | - (void)testExample
30 | {
31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
32 | }
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/DZTableViewTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/articles/Chapter0/animation/animation.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/animation/animation.md
--------------------------------------------------------------------------------
/articles/Chapter0/images/geometry.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/geometry.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/images/incontentsize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/incontentsize.png
--------------------------------------------------------------------------------
/articles/Chapter0/images/keyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/keyboard.png
--------------------------------------------------------------------------------
/articles/Chapter0/images/outcontentsize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/outcontentsize.png
--------------------------------------------------------------------------------
/articles/Chapter0/images/pics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/pics.png
--------------------------------------------------------------------------------
/articles/Chapter0/images/superview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/superview.png
--------------------------------------------------------------------------------
/articles/Chapter0/images/view_tree_3d.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/view_tree_3d.jpeg
--------------------------------------------------------------------------------
/articles/Chapter0/images/view_tree_text.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/view_tree_text.jpeg
--------------------------------------------------------------------------------
/articles/Chapter0/images/wKiom1MNiXaQh-LKAAEklpTy71Q964.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/images/wKiom1MNiXaQh-LKAAEklpTy71Q964.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/delivery.md:
--------------------------------------------------------------------------------
1 | #事件传递:响应链
2 |
3 | 当你设计应用程序时,你很可能想要动态地响应事件。 比如,一个触摸(touch)事件可以发生在屏幕上的不同对象中,并且对于一个给定事件你必须决定你想要响应哪个对象并理解哪个对象如何接收事件。
4 |
5 |
6 | 当一个用户生成的事件发生时,UIKit创建一个事件对象,它包含了需要处理该事件的各种信息。 然后把事件对象放在活动应用程序的事件队列中。 对于多个触摸事件,对象是一组打包进一个UIEvent对象的触摸事件。 对于运动事件(motion events),事件对象根据你使用的不同框架以及你感兴趣的不同移动事件类型发生改变。
7 |
8 |
9 | 事件沿着一个指定的路径传递知道它遇见可以处理它的对象。 首先一个UIApplication 对象从队列顶部获取一个事件并分发(dispatches)它以便处理。 通常,它把事件传递给应用程序的关键窗口对象,该对象把事件传递给一个初始对象来处理。 初始对象取决于事件的类型。
10 |
11 |
12 | * 触摸事件。 对于触摸事件,窗口对象首先尝试把事件传递给触摸发生的视图。那个视图被称为hit-test(点击测试)视图。 寻找hit-test视图的过程被称为hit-testing, 参见 “Hit-Testing Returns the View Where a Touch Occurred.”
13 |
14 | * 运动和远程控制事件。 对于这些事件,窗口对象把shaking-motion(摇晃运动)或远程控制事件传递给第一响应者来处理。第一响应者请参见 “The Responder Chain Is Made Up of Responder Objects.”
15 |
16 |
17 | 这些事件路径的最终目标是找到一个可以处理并相应事件的对象。 因此,UIKit首先把事件传递给最适合处理该事件的对象。 对于触摸事件,最适合处理该事件的对象是hit-test视图,而对于其它事件,那个对象是第一响应者。 以下章节讲述了更多关于如何决定hit-test视图和第一响应者的详情。
18 |
19 |
20 |
21 | ##一、Hit-Testing 返回触摸事件发生的视图
22 |
23 |
24 | iOS 使用hit-testing来找到事件发生的视图。 Hit-testing包括检查触摸事件是否发生在任何相关视图对象的范围内, 如果是,则递归地检查所有视图的子视图。在视图层次中的最底层视图,如果它包含了触摸点,那么它就是hit-test视图。等 iOS决定了hit-test视图之后,它把触摸事件传递给该视图以便处理。
25 |
26 |
27 | 假设用户触摸了视图E。iOS通过以下吮吸检查子视图来查找hit-test视图:
28 | 
29 |
30 | 因为触摸发生在视图A范围内,所以它检查子视图B和C。触摸不在视图B范围内,但是它在视图C范围内,所以它检查子视图D和E。触摸没有在视图D范围内,但是它在视图E范围内。视图E是视图层次结构的最底层并且它包含了触摸,因此它是hit-test视图。
31 |
32 |
33 |
34 | hitTest:withEvent: 方法为给定的CGPoint 和 UIEvent返回hit test 视图。hitTest:withEvent:方法通过在自身调用pointInside:withEvent: 方法开始。 如果传递到方法hitTest:withEvent:内的点在视图的范围内,pointInside:withEvent:返回YES。然后,方法递归地给每个子视图调用hitTest:withEvent:方法并返回YES。
35 |
36 |
37 |
38 |
39 | 如果传递到hitTest:withEvent:方法的电不再视图的范围内,pointInside:withEvent:方法返回NO,点被忽视,并且hitTest:withEvent:返回nil. 如果一个子视图返回NO,则视图层次的整个分支都被忽视,因为如果触摸事件没有发生在那个子视图中,那么事件也不会在任何一个该子视图的子视图中发生。 这意味着在一个子视图中的任何点,如果它在其父视图的外面,那么它不能接收触摸事件,因为触摸点必须在父视图和子视图的范围内。但是如果子视图的clipsToBounds 特性被设置为NO时这可以发生。(主意:一个触摸对象在生命周期内都跟它的hit-test视图相关,即使触摸事件在稍候会移出该视图。)
40 |
41 |
42 | hit-test是第一处理触摸事件的视图。如果hit-test视图不能处理该事件,事件沿着视图的响应链传递直到系统找到可以处理该事件的视图,响应链在 “The Responder Chain Is Made Up of Responder Objects”中有描述。
43 |
44 |
45 |
46 | ##二、响应链由响应者对象组成
47 |
48 |
49 | 很多类型的事件都在事件传递中依赖响应链。 响应链是一系列相连的响应者对象。 它由第一个响应者开始,以应用对象结束。 如果第一响应者不能处理该事件,它把事件传递给响应链中的下一个响应者。
50 |
51 |
52 | 响应者对象是可以响应并处理各种事件的对象。 UIResponder 类是所有响应者对象的基类,它为事件处理和通用响应者行为都定义了可编程接口。 UIApplication, UIViewController, 和UIView的实例都是响应者,就是说所有的视图和大多数主要对象都是响应者。 请注意Core 动画层不是响应者。
53 |
54 |
55 | 第一响应者被设计为首先接收事件。通常,第一响应者是一个视图对象。 一个对象通过实现以下事情可以成为第一响应者:重写 canBecomeFirstResponder 方法并返回YES。
56 |
57 |
58 | 接收一个becomeFirstResponder 消息。如果必要,一个对象可以给自己发送该消息。
59 |
60 |
61 | 注意:确保你的应用程序在把一个对象分配为第一响应者之前已经建立了它的对象图。比如,通常你重写viewDidAppear: 方法时会调用becomeFirstResponder方法。 如果你尝试在viewWillAppear:方法里分配第一响应者,那么你的对象图还没有建立,因此becomeFirstResponder方法返回NO。
62 |
63 |
64 | 事件并不是依赖响应链的唯一对象。响应链可以用于以下所有对象:
65 |
66 |
67 | 触摸事件。如果hit-test视图无法处理一个触摸事件。该事件在以hit-test视图开始的响应链中往上传递。
68 |
69 |
70 | 运动事件。 要想用UIKit处理摇晃运动事件,第一响应者必须实现UIResponder类的motionBegan:withEvent: 方法或motionEnded:withEvent: 方法,参见 “Detecting Shake-Motion Events with UIEvent.”
71 |
72 |
73 | 远程控制事件。 要想处理远程控制事件,第一响应者必须实现UIResponder类的 remoteControlReceivedWithEvent:方法。
74 |
75 |
76 | 动作消息。当用户操纵一个控件,比如一个按钮或开关,并且操作方法的目标(target)为nil时,消息通过以控件视图开始的响应者链里被发送。
77 |
78 |
79 | 编辑菜单消息。 当用户点击了编辑菜单的命令时,iOS使用一个响应者链来找出实现必要方法的对象(比如cut:, copy:, 以及paste:) 。 更多信息,请看“Displaying and Managing the Edit Menu” 以及示例代码项目,CopyPasteTile.
80 |
81 |
82 | 文本编辑。 当用户点击一个文本区或一个文本视图时,那视图自动成为第一响应者。 默认情况下,虚拟键盘出现后文本区或文本视图成为可编辑状态。 你可以显示一个自定义输入视图来取代键盘,如果它更适合你的应用程序。 你还可以添加一个自定义输入视图到任何响应者对象。 更多信息,请看 “Custom Views for Data Input”.
83 |
84 |
85 | UIKit 自动把用户点击的文本区或文本视图设置为第一响应者;应用程序必须明确地通过becomeFirstResponder方法设置所有其它第一响应者对象。
86 |
87 | 三、响应者链遵循一个特定的传递路径
88 |
89 |
90 | 如果初始对象---hit-test视图或者第一响应者链--不能处理一个事件, UIKit 把事件传递给响应者链中的下一个响应者。 每个响应者决定是否想要处理该事件或者调用nextResponder 方法把该事件传递给它的下个响应者。 该过程一直持续直到找到一个响应者来处理该事件或者没有任何其它响应者。
91 |
92 |
93 | 当iOS侦测到一个事件时,响应者链序列开始并把事件传递给一个初始对象,初始对象通常为一个视图。 初始视图首先可以处理一个事件。 图2-2显示了为两个应用程序配置的两个不同事件传递路径。 应用程序的事件传递路径依赖于它的特定结构, 但是所有的事件传递路径遵循同样的试探法(heuristics)。
94 | 
95 |
96 |
97 |
98 |
99 | 对于左边的应用程序,事件沿着以下路径:
100 |
101 | 1. 初始视图尝试处理事件或消息。 如果它不能处理该事件,它把事件传递给它的父视图,因为初始视图在它的视图控制器的视图层次里不是最顶层视图。
102 | 2. 父视图尝试处理该事件。 如果父视图不能处理该事件,它把事件传递给父视图的父视图,因为该父视图还不是视图层次结构里的最顶层视图。
103 | 3. 视图控制器的视图层次结构中的最顶层视图尝试处理该事件。如果它还是不能处理该事件,它把事件传递给它的视图控制器。
104 | 4. 视图控制器尝试处理该事件,如果它不能处理,则把事件传递给窗口。
105 | 5. 如果窗口还是不能处理该事件,它把事件传递给单个应用程序对象。
106 | 6. 如果应用程序对象还是不能处理该事件,它把事件丢弃。
107 |
108 |
109 | 右边的应用程序沿着稍稍不同的路径,但是所有的事件传递路径遵循这些探索法:
110 |
111 | 1. 一个视图在它的视图控制器的视图层次中把事件向上传递,直到它达到最顶层视图。
112 | 2. 最顶层视图把事件传递给它的视图控制器。
113 | 3. 视图控制器把事件传递给它的最顶层视图的父视图。 重复步骤1-3 直到事件到达根视图控制器。
114 | 4. 根视图控制器把事件传递给窗口对象。
115 | 5. 窗口把事件传递给应用程序对象。
116 |
117 | 重要提示: 如果你实现了一个自定义视图用UIKit来处理远程控制事件,操作消息,摇晃运动事件,或者编辑菜单消息,不要直接把事件或消息分配给nextResponder并在响应者链里向上发送。 相反,调用父类的当前事件处理方法的实现方法并且让UIKit为你处理响应者链的遍历。
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/gestures.md:
--------------------------------------------------------------------------------
1 |
2 | 手势识别把低层次的事件处理代码转换为高层次的操作。 它们是你连接到视图的各种对象,它们允许视图像控件一样响应各种操作。 手势识别翻译(interpret)各种触摸来决定它们是否响应一个特定的手势,比如一个点击(swipe), 捏合(pinch),或旋转。 如果它们识别到它们指定的手势,就给目标对象发送一个操作信息。 目标对象通常是视图的视图控制器,它响应如图1-1中所示的手势。该设计模式既有力又简单;你可以动态地决定视图响应什么操作,你也可以给视图添加各种手势识别而不必要子类化该视图。
3 |
4 | 
5 |
6 |
7 |
8 | 一、使用手势识别器来简化事件处理
9 |
10 |
11 | UIKit 框架提供了一些已经预先定义好的手势识别来侦测各种常用手势。如果可能的话,最好是使用预定义的手势识别,因为它们缩减了你必须写的代码总量。另外,使用一个标准的手势识别而不是自己编写以确保你的应用程序如用户期望的那样工作。
12 |
13 |
14 | 如果你想你的应用程序识别一个独特的手势,比如一个勾或一个漩涡状运动,你可以创建你自己的自定义手势识别。 学习如何设计和实现你自己的手势识别,请看 “Creating a Custom Gesture Recognizer.”
15 |
16 |
17 | 1、内建手势识别来识别各种常用手势
18 |
19 |
20 | 当你设计应用程序时,考虑你想要开启什么手势。 然后,对于每个手势,决定列表1-1中的预定义手势识别是否就足够。
21 |
22 |
23 | Gesture | UIKit class
24 | :---------|:--------
25 | Tapping (any number of taps) | UITapGestureRecognizer
26 | Pinching in and out (for zooming a view) | UIPinchGestureRecognizer
27 | Panning or dragging|UIPanGestureRecognizer
28 | Swiping (in any direction)|UISwipeGestureRecognizer
29 | Rotating (fingers moving in opposite directions)|UIRotationGestureRecognizer
30 | Long press (also known as “touch and hold”)|UILongPressGestureRecognizer
31 |
32 |
33 | 你的应用程序应该只以用户期待的方式来响应。 比如,一个捏合动作应该缩放,而轻击动作应该选择一些东西。 关于正确使用手势的指南,请看iOS Human Interface Guidelines 中的 “Apps Respond to Gestures, Not Clicks”。
34 |
35 |
36 | 2、手势识别连接到视图
37 |
38 |
39 | 每个手势识别都跟一个视图相关联。 通常,视图可以有多个手势识别,因为单个视图可能响应多个不同的手势。 要想一个手势识别能够识别在一个特殊视图上发生的各种触摸,你必须把手势识别连接到那个视图。 当用户触摸了那个视图,手势识别先于视图对象接收一个触摸发生的信息。 结果,手势识别可以通过视图的行为来响应各种触摸。
40 |
41 |
42 | 3、 手势触发操作消息
43 |
44 |
45 | 当一个手势识别识别出其制定的手势后,它给它的目标发送一个操作消息。 要想创建一个手势识别,你需要初始化一个目标和一个操作。
46 |
47 |
48 |
49 | 1). 离散和连续的手势
50 |
51 |
52 | 手势有离散手势,也有连续手势。 一个离散手势,比如一个轻击(tap),只发生一次。一个连续手势,比如捏合(pinching),发生时持续一段时间。 对于离散手势,手势识别就给目标发送一个操作消息。对于连续手势,手势识别则保持发送操作消息给目标直到多点触摸序列结束,如图1-2.
53 | 
54 |
55 |
56 |
57 |
58 | 二、用手势识别响应事件
59 |
60 |
61 | 你需要做三件事来添加一个内建手势识别到应用程序:
62 |
63 |
64 | 1. 创建并配置一个手势识别实例. 该步骤包括分配一个目标,操作,以及有时候分配手势指定的各种属性(比如 numberOfTapsRequired).
65 | 2. 把手势识别连接到一个视图。
66 | 3. 实现处理手势的操作方法。
67 |
68 |
69 | 1、使用界面生成器(Interface Builder)来添加一个手势识别到应用程序
70 |
71 |
72 | 在Xcode里的界面生成器中,添加手势识别到应用程序跟你添加任何对象到用户界面(add any object to your user interface)是一样的---就是从对象库中拖拉手势识别到一个视图。完成该操作后,手势识别自动连接到那个视图。 你可以检查手势识别被连接到了哪个视图,并且如果必要,在nib 文件中更改该连接(change the connection in the nib file)。
73 |
74 |
75 | 当你创建完手势识别对象后,你需要创建并连接一个操作方法(create and connect an action)。任何时候手势识别(gesture recognizer)识别手势时都将调用该方法。如果你需要在该操作方法外面引用手势识别,你还应该为手势识别创建并连接一个输出口 (create and connect an outlet)。你的代码应该跟列表1-1相似.
76 |
77 |
78 | ```
79 | @interface APLGestureRecognizerViewController ()
80 | @property (nonatomic, strong) IBOutlet UITapGestureRecognizer *tapRecognizer;
81 | @end
82 |
83 | @implementation
84 | - (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer
85 | // Will implement method later...
86 | }
87 | @end
88 | ```
89 |
90 |
91 | 2、通过程序添加一个手势识别
92 |
93 |
94 | 你可以通过分配和初始化一个具体(concrete)的UIGestureRecognizer 子类的实例来创建一个手势识别,比如 UIPinchGestureRecognizer 。 当你初始化手势识别时,指定一个目标对象和一个操作选择器(selector),正如列表1-2中所示,通常目标对象就是视图的视图控制器。
95 |
96 |
97 | 如果你通过程序创建一个手势识别,你需要调用addGestureRecognizer: 方法来把它连接到视图。 列表1-2 创建了一个单个轻击手势识别,指定识别该手势需要一次轻击(tap),然后把手势识别对象连接到一个视图。 通常,你在视图控制器的 viewDidLoad 方法里创建手势识别,如列表1-2所示。
98 |
99 |
100 |
101 | 列表1-2 通过程序创建一个单一的轻击手势识别
102 |
103 | ```
104 | - (void)viewDidLoad {
105 | [super viewDidLoad];
106 |
107 | // Create and initialize a tap gesture
108 | UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
109 | initWithTarget:self action:@selector(respondToTapGesture:)];
110 |
111 | // Specify that the gesture must be a single tap
112 | tapRecognizer.numberOfTapsRequired = 1;
113 |
114 | // Add the tap gesture recognizer to the view
115 | [self.view addGestureRecognizer:tapRecognizer];
116 |
117 | // Do any additional setup after loading the view, typically from a nib
118 | }
119 | ```
120 | 3、响应离散手势
121 |
122 |
123 | 当你创建一个手势识别时,你把该识别(recognizer)连接到一个操作方法。使用该操作方法来响应手势识别的手势。列表1-3 提供了一个响应一个离散手势的例子。 当用户轻击了带有手势识别的视图时,视图控制器显示一个图像视图(image view)表示"Tap"发生。 showGestureForTapRecognizer: 方法决定了视图中手势的位置,该位置从recognizer的locationInView: 特性中获取,然后把图片显示到该位置。
124 |
125 |
126 | 注意: 接下来的3段代码例子取自 Simple Gesture Recognizers 样本代码工程,你可以查看它以获得更多上下文。
127 |
128 |
129 |
130 | 列表1-3 处理一个双击手势
131 | ```
132 | - (IBAction)showGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer {
133 | // Get the location of the gesture
134 | CGPoint location = [recognizer locationInView:self.view];
135 |
136 | // Display an image view at that location
137 | [self drawImageForGestureRecognizer:recognizer atPoint:location];
138 |
139 | // Animate the image view so that it fades out
140 | [UIView animateWithDuration:0.5 animations:^{
141 | self.imageView.alpha = 0.0;
142 | }];
143 | }
144 | ```
145 |
146 | 每个手势识别都有它自己的特性集。比如,如列表1-4中,showGestureForSwipeRecognizer: 方法使用点击(swipe)手势识别的direction 特性来决定用户是向左清扫还是向右。 然后,它使用该值来让图像从点击方向消失。
147 |
148 |
149 |
150 | 列表1-4 响应一个向左或向右清扫手势
151 | ```
152 | // Respond to a swipe gesture
153 | - (IBAction)showGestureForSwipeRecognizer:(UISwipeGestureRecognizer *)recognizer {
154 | // Get the location of the gesture
155 | CGPoint location = [recognizer locationInView:self.view];
156 |
157 | // Display an image view at that location
158 | [self drawImageForGestureRecognizer:recognizer atPoint:location];
159 |
160 | // If gesture is a left swipe, specify an end location
161 | // to the left of the current location
162 | if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
163 | location.x -= 220.0;
164 | } else {
165 | location.x += 220.0;
166 | }
167 |
168 | // Animate the image view in the direction of the swipe as it fades out
169 | [UIView animateWithDuration:0.5 animations:^{
170 | self.imageView.alpha = 0.0;
171 | self.imageView.center = location;
172 | }];
173 | }
174 | ```
175 | 4、响应连续手势
176 |
177 |
178 | 连续手势允许应用程序在手势发生时响应。 比如,用户可以一边做捏合手势,一边就可以看见缩放,或者允许用户沿着屏幕拖拉一个对象。
179 |
180 |
181 | 列表1-5 以跟手势同样的角度显示一个"Rotate"图片,并且当用户停止旋转时,动画该图片让其在旋转回水平方向的同时消失。 当用户旋转他的手指时,持续调用 showGestureForRotationRecognizer: 方法直到两个手指都拿起。
182 |
183 |
184 |
185 | 列表1-5 响应一个旋转手势
186 | ```
187 | // Respond to a rotation gesture
188 | - (IBAction)showGestureForRotationRecognizer:(UIRotationGestureRecognizer *)recognizer {
189 | // Get the location of the gesture
190 | CGPoint location = [recognizer locationInView:self.view];
191 |
192 | // Set the rotation angle of the image view to
193 | // match the rotation of the gesture
194 | CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]);
195 | self.imageView.transform = transform;
196 |
197 | // Display an image view at that location
198 | [self drawImageForGestureRecognizer:recognizer atPoint:location];
199 |
200 | // If the gesture has ended or is canceled, begin the animation
201 | // back to horizontal and fade out
202 | if (([recognizer state] == UIGestureRecognizerStateEnded) || ([recognizer state] == UIGestureRecognizerStateCancelled)) {
203 | [UIView animateWithDuration:0.5 animations:^{
204 | self.imageView.alpha = 0.0;
205 | self.imageView.transform = CGAffineTransformIdentity;
206 | }];
207 | }
208 |
209 | }
210 | ```
211 |
212 | 每次调用该方法时,图片都在drawImageForGestureRecognizer: 方法中被设置为不透明。 当手势完成时,图片在animateWithDuration: 方法中被设置为透明。showGestureForRotationRecognizer:方法通过检查手势识别的状态来判断手势是否完成。 关于这些状态的更多信息,请看“Gesture Recognizers Operate in a Finite State Machine.”
213 |
214 |
215 |
216 | 三、定义手势识别如何交互
217 |
218 |
219 | 通常情况下,当你把手势识别添加到应用程序时,你需要了解你想要你的识别(recognizers)之间如何发生交互,或者跟应用程序的其它任何触摸事件处理代码如何发生交互。要想完成这些,你首先需要理解更多点关于手势识别如何工作的知识。
220 |
221 |
222 | 1、手势识别在一个有限状态机里操作
223 |
224 |
225 | 手势识别以一种预定义的方式从一个状态过渡到另一个状态。 每种状态都可以根据它们遇到的特定条件(conditions)过渡到几种可能的未来状态中的一种。确切的状态机根据手势识别是离散或是连续会发生变化,正如图1-3中所示。所有的手势识别在一个可能的状态(UIGestureRecognizerStatePossible)中开始, 它们分析接收到的任何多点触摸序列,并且在分析过程中成功识别手势或者识别识别一个手势。失败识别手势意味着手势识别过渡到失败状态 (UIGestureRecognizerStateFailed).
226 |
227 | 
228 |
229 |
230 |
231 |
232 | 当一个离散手势识别识别出它的手势,手势识别从可能状态过渡到被识别状态 (UIGestureRecognizerStateRecognized) , 然后识别完成。
233 |
234 |
235 | 对于连续手势,当手势第一次被识别时,手势识别从可能状态开始(UIGestureRecognizerStateBegan)。然后,它从开始状态过渡到改变状态(UIGestureRecognizerStateChanged), 然后当手势发生时又从改变状态变为改变状态。 当用户的最后一个手指从视图上举起时,手势识别过渡到结束状态(UIGestureRecognizerStateEnded), 识别完成。 注意结束状态是识别完成状态的一个别名。
236 |
237 |
238 | 如果一个连续手势不再适应预期的模式时,它的识别还可以从改变状态过渡到取消状态(UIGestureRecognizerStateCancelled)。
239 |
240 |
241 | 每次手势识别改变状态时,手势识别都给它的目标发送一个操作消息,除非它过渡到失败或取消状态。 然而,一个离散手势识别只在它从可能状态过渡到被识别状态时才发送一个单个操作消息。一个连续手势识别在状态发生改变时发送多个操作消息。
242 |
243 |
244 | 当一个手势识别到达被识别(或结束)状态时,它把状态重置为可能(Possible)状态。把状态重置为可能状态不会触发一个操作消息。
245 |
246 |
247 | 2、跟其它手势识别发生交互
248 |
249 |
250 | 一个视图可以带有多个手势。使用视图的gestureRecognizers 特性来确定视图都带有哪些手势识别。 你还可以分别(respectively)使用addGestureRecognizer: 方法和removeGestureRecognizer: 方法添加和删除视图上的手势识别来动态改变视图如何处理手势。
251 |
252 |
253 | 当视图带有多个手势识别时,你可能想要改变竞争(competing)手势识别是如何接收和分析触摸事件。默认情况下,没有设置哪个手势识别首先接收到第一个触摸,因此每次触摸都可以以不同的顺序传送给手势识别。 你可以重写该默认行为来:
254 |
255 |
256 | 指定一个手势识别应该比另一个手势识别优先分析某个触摸。
257 |
258 | 允许两个手势识别来同时操作。
259 |
260 | 阻止某个手势识别分析某个触摸
261 |
262 | 使用UIGestureRecognizer 类方法,委托方法,以及其子类重写的方法来影响这些行为。
263 |
264 |
265 |
266 | 1)为两个手势识别声明一个特定顺序
267 |
268 |
269 | 想象一下,你想要识别一个快速滑动(swipe)和一个慢速拖动(pan)手势,你想要用这两个手势触发不同的操作。默认情况下,当用户尝试swipe时,该手势会被理解为一个pan。 这是因为swipe(一个离散手势)手势在满足各种必要条件被理解为一个swipe手势之前,首先满足pan(一个连续手势)手势的各种必要条件。
270 |
271 |
272 | 要想视图同时识别swipes 和pans,你想要swipe手势识别在pan手势之前来分析触摸事件。 如果swipe手势识别已经确定某个触摸是一个swipe, 那么pan手势识别就绝没有必要再去分析该触摸。 如果swipe手势识别确定该触摸不是一个swipe, 它过渡到Failed状态,然后pan手势识别应该开始分析该触摸事件。
273 |
274 |
275 | 你可以通过调用手势识别的requireGestureRecognizerToFail: 方法来说明两个手势识别之间这种类型的关系,如列表1-6所示。在该列表中,两个手势识别都被连接到了相同的视图上。
276 |
277 |
278 |
279 | 列表1-6 pan手势识别要求swipe手势识别到达fail状态
280 | ```
281 | - (void)viewDidLoad {
282 | [super viewDidLoad];
283 | // Do any additional setup after loading the view, typically from a nib
284 | [self.panRecognizer requireGestureRecognizerToFail:self.swipeRecognizer];
285 | }
286 | ```
287 |
288 | requireGestureRecognizerToFail: 方法给接收者发送了一个消息,并且指定了一些otherGestureRecognizer,它们必须在接收识别(receiving recognizer)开始工作之前进入Failed 状态。当它等待其它手势识别过渡到Failed状态期间,接收识别(receiving recognizer)始终处于Possible状态。如果其它手势识别失败了,receiving recognizer 开始分析触摸事件并移动到下个状态。换句话说,如果其它手势识别过渡到Recognized 或者 Began状态,receiving recognizer就移动到Failed 状态。 关于状态过渡的更多信息, 请看“Gesture Recognizers Operate in a Finite State Machine.”
289 |
290 |
291 | 注意:如果你的应用程序识别同时支持单击和双击,并且你的单击手势识别不要求双击识别失败,那么即使是用户双击时,你也应该期待在双击操作之前接收单击操作。该行为是有意设计的,因为最好的用户体验通常都开启多种类型的操作。
292 |
293 |
294 | 如果你想要这两种操作不兼容,你的单击识别必须要求双击识别进入失败状态。 然而,这样你的单击操作会滞后用户的输入,因为单击识别会等到双击识别失败后才开始识别。
295 |
296 |
297 |
298 | 2)阻止手势识别分析触摸
299 |
300 |
301 | 你可以通过添加一个委托对象到手势识别来改变手势识别的行为。UIGestureRecognizerDelegate 协议提供了一组方法来阻止手势识别分析触摸。 你可以选择使用协议中的 gestureRecognizer:shouldReceiveTouch: 方法 和 gestureRecognizerShouldBegin: 方法中的一个来使用。
302 |
303 |
304 | 当一个触摸开始时,如果你可以立即确定手势识别是否应该考虑该触摸,使用 gestureRecognizer:shouldReceiveTouch: 方法来实现。每次有新触摸时都调用该方法。 阻止手势识别注意到一个触摸的发生,请返回NO。默认值是YES。该方法不改变手势识别的状态。
305 |
306 |
307 | 列表1-7 使用 gestureRecognizer:shouldReceiveTouch: 委托方法来阻止手势识别接收到来自一个自定义子视图中发生的触摸。
308 |
309 | ```
310 | - (void)viewDidLoad {
311 | [super viewDidLoad];
312 | // Add the delegate to the tap gesture recognizer
313 | self.tapGestureRecognizer.delegate = self;
314 | }
315 |
316 | // Implement the UIGestureRecognizerDelegate method
317 | -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
318 | // Determine if the touch is inside the custom subview
319 | if ([touch view] == self.customSubview){
320 | // If it is, prevent all of the delegate's gesture recognizers
321 | // from receiving the touch
322 | return NO;
323 | }
324 | return YES;
325 | }
326 |
327 | ```
328 |
329 | 如果你需要在确定手势识别是否应该分析一个触摸之前一直等待。使用 gestureRecognizerShouldBegin: 委托方法。 通常,如果你有一个UIView 或 UIControl子类并带有跟手势识别想冲突的自定义触摸事件处理,你可以使用该方法。返回NO,让手势识别立即进入失败状态,允许其他触摸处理来处理。 当手势识别想要过渡到Possible状态以外的状态时,如果手势识别将阻止一个视图或控件接收一个触摸,该方法被调用。
330 |
331 |
332 | 如果你的视图或视图控制器不能成为手势识别委托,你可以使用UIView的fgestureRecognizerShouldBegin:方法 。该方法的签名和实现是一样的。
333 |
334 |
335 |
336 | 3)开启同时手势识别
337 |
338 |
339 | 默认情况下,两个手势识别不能同时识别它们的不同手势。但是,假设你想让用户可以同时捏合并旋转一个视图,你需要改变默认行为,你可以通过调用gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 方法来实现。该方法是 UIGestureRecognizerDelegate 协议的一个可选方法。 当一个手势识别的手势分析可能阻碍另一个手势识别识别它的手势时可以调用该方法,反之亦然。 该方法默认范围NO。当你想让两个手势同时分析它们的手势时,返回YES。
340 |
341 |
342 | 注意:你只在一个手势识别需要开启同时识别功能时才需要实现一个委托并返回YES。 然而,它还意味着返回NO并不一定阻止同时识别功能,因为其他手势识别的委托可能返回了YES。
343 |
344 |
345 |
346 | 4)给两个手势指定一个单向关系
347 |
348 |
349 | 如果你想要控制两个识别(recognizers)是如何交互,但是你需要指定一个单向关系,你可以重写canPreventGestureRecognizer: 或 canBePreventedByGestureRecognizer: 子类方法并返回NO(默认为YES)。 比如,如果你想用一个旋转手势阻止一个捏合手势,但是你又不想捏合手势阻止一个旋转手势,你可以用如下语句指定。
350 | ```
351 | [rotationGestureRecognizer canPreventGestureRecognizer:pinchGestureRecognizer];
352 | ```
353 | 同时,重写旋转手势识别的子类方法来返回NO. 更多管理如何子类化 UIGestureRecognizer的信息,请看 “Creating a Custom Gesture Recognizer.”
354 |
355 |
356 | 如果没有手势需要阻止另外手势,使用gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: ,它在“Permitting Simultaneous Gesture Recognition.” 里描述。 默认情况下,捏合手势阻止旋转手势,或者旋转手势阻止捏合手势,因为两个手势不能同时被识别。
357 |
358 |
359 | 5) 跟别的用户界面控件交互
360 |
361 |
362 | 在iOS 6.0 或以后版本中,默认控件操作方法防止(prevent)重复手势识别的行为。比如,一个按钮的默认操作是一个单击。如果你有一个单击手势识别绑定到一个按钮的父视图上,然后用户点击该按钮,最后按钮的操作方法接收触摸事件而不是手势识别。 它只用于手势识别跟一个控件的默认操作重复时,包括:
363 |
364 |
365 | 单个手指在UIButton, UISwitch, UIStepper, UISegmentedControl, and UIPageControl 上的单击。
366 |
367 | 单个手势在 UISlider 上的快速滑动(swipe),轻扫方向跟slider平行
368 |
369 | 单个手指的在UISwitch 控件上的慢速拖动(pan)手势,方向跟switch平行。
370 |
371 | 如果你有一个这些控件的自定义子类,你想要改变其默认操作,把手势识别直接连接到控件而不是连接到其父视图。然后,手势识别首先接收到触摸事件。一如往常,请确保你已经阅读了 iOS Human Interface Guidelines 文档以确保你的应用程序提供了一个直观的用户体验,特别是当你重写一个标准控件的默认行为时。
372 |
373 |
374 |
375 | 四、手势识别解读(interpret)原始触摸事件
376 |
377 |
378 | 目前位置,你已经学习了关于手势以及应用程序如何识别并响应它们。 然而,要想创建一个自定义手势识别或想控制时手势别如何跟视图的触摸事件处理相交互,你需要更具体地(specifically)思考触摸和事件的方方面面。
379 |
380 |
381 | 1、一个事件包含了单前多点触摸序列的所有触摸
382 |
383 |
384 | 在iOS中,一个触摸是一个手指在屏幕上的存在或运动。 一个手势有一个或多个触摸,它由UITouch 对象表示。比如,一个pinch-close手势有两个触摸--两个手指在屏幕上从相反方向朝着彼此移动。
385 |
386 |
387 | 一个事件包含(encompasses)一个多点触摸序列的所有触摸。 一个多点触摸序列以一个手指触摸屏幕开始,以最后一个手指离开屏幕结束。 当一个手指移动时,iOS给事件触摸对象。一个多点触摸事件由 UIEventTypeTouches 类型的UIEvent 对象表示。
388 |
389 |
390 | 每个触摸对象只跟踪一个手指,并且只在触摸序列期间跟踪。 在序列期间,UIKit跟踪手指并更新触摸对象的各种属性。 这些属性包括触摸阶段(phase),它在视图中的位置,它的前一个位置,以及它的时间戳。
391 |
392 |
393 | 触摸阶段表明一个触摸何时开始,它是移动的还是静止的,以及它何时结束---当手指不再触摸屏幕的时间。 正如图1-4所示,应用程序在任何触摸的每个阶段之间接收事件对象。
394 |
395 |
396 | 图1-4 一个多点触摸序列和触摸阶段
397 |
398 | 
399 |
400 |
401 |
402 | 注意:手指没有鼠标点击精确。 当用户触摸屏幕时,接触的区域实际上是椭圆的,并且会比用户期待的位置稍微篇低。 该接触面会根据手指的尺寸和方向,手指使用时的压力,以及其它因素的不同而发生改变。底层多点触摸系统会替你分析该信息并计算一个单击点,因此你不需要自己写代码来实现它。
403 |
404 |
405 | 2、应用程序在触摸处理方法中接收触摸
406 |
407 |
408 | 在一个多点触摸序列期间,应用程序在新触摸发生或者给出的触摸阶段发生改变时发送这些信息;它调用以下方法:
409 |
410 |
411 | 当一个或多个手指触摸屏幕时调用
412 |
413 | 当一个或多个手势移动时调用
414 |
415 | 当一个或多个手指离开屏幕时调用
416 |
417 | 当触摸序列被系统事件取消时调用,比如有一个来电。
418 |
419 | 每个方法都跟一个触摸阶段相关联;比如,touchesBegan: 方法跟 UITouchPhaseBegan 方法相关联。 触摸对象的阶段(phase)被存储在其phase 特性里。
420 |
421 |
422 | 注意: 这些方法跟手势识别状态没有关联,比如UIGestureRecognizerStateBegan 和 UIGestureRecognizerStateEnded等。 手势识别器状态严格表示手势识别器自身的阶段,不表示正在被识别的触摸对象阶段。
423 |
424 |
425 | 五、调节触摸到视图的传递
426 |
427 |
428 | 可能有时候你想要在手势识别器之前接收到一个触摸。但是在你可以改变触摸到视图的传递路径之前,你需要理解其默认行为。在简单情况下,当一个触摸发生时,触摸对象从UIApplication对象传递到UIWindow对象。 然后,窗口首先把触摸发送给触摸发生的视图上关联的任何手势识别器,而不是先发送给视图对象自身。
429 |
430 | 
431 |
432 | 1、手势识别器首先识别一个触摸
433 |
434 |
435 |
436 | 窗口延迟把触摸对象传递给视图,这样手势识别器就可以首先分析触摸。延迟期间,如果手势识别器识别出一个触摸手势,然后窗口就绝不会再把触摸对象传递给视图,并且还取消任何先前传递给视图的任何触摸对象,这些触摸对象都是被识别序列的一部分。比如,如果你有一个手势识别器用来识别一个离散手势,该手势要求一个双手指的触摸,该触摸就会被解释成两个单独的触摸对象。 当触摸发生时,触摸对象从英语程序对象传递到触摸发生视图的窗口对象,然后发生以下序列,请看图1-6。
437 |
438 | Figure 1-6 Sequence of messages for touches
439 |
440 | 图1-6 触摸消息序列
441 | 
442 |
443 |
444 |
445 | 1. 窗口在Began 阶段发送两个触摸对象---通过 touchesBegan:withEvent: 方法---给手势识别器。 手势识别器还不能识别该手势,因此它的状态是Possible. 窗口发送这些同样的触摸给手势识别器相关联的视图。
446 |
447 |
448 | 2. 窗口在Moved阶段发送两个触摸对象---通过touchesMoved:withEvent: 方法---- 给手势识别器。 识别器任然不能侦测该手势,状态还是Possible。 窗口然后发送这些触摸到相关联的视图。
449 |
450 |
451 | 3. 窗口在Ended阶段发送一个触摸对象--- 通过touchesEnded:withEvent: 方法---给手势识别器。 虽然该触摸对象对于手势来说信息还不够,但是窗口还是把该对象扣住(withhold)不发送给视图。
452 |
453 |
454 | 4. 窗口在Ended阶段发送另一个触摸。 手势识别器这是可以识别出该手势,因此把状态设置为Recognized. 就在第一个操作信息被发送之前,视图调用touchesCancelled:withEvent: 方法来使先前在Began 和Moved阶段发送的触摸对象无效(invalidate)。触摸在Ended阶段被取消。
455 |
456 |
457 |
458 |
459 | 现在假设手势识别器在最后一步确定它正在分析的多点触摸序列不是它的手势。它把状态设置为UIGestureRecognizerStateFailed.。 然后窗口在Ended阶段发送这两个触摸对象给相关联的视图---通过touchesEnded:withEvent: 消息。
460 |
461 |
462 | 一个连续手势的手势识别器遵循一个相似的序列,除了它更有可能在触摸对象到达Ended 阶段之前就识别出它的手势。一旦识别出它的手势,它把状态设置为 UIGestureRecognizerStateBegan (而不是Recognized). 窗口把多点触摸序列中的所有子序列触摸对象发送给手势识别器,而不是发送到附属的(attached)视图。
463 |
464 |
465 | 2、影响到视图的各个触摸的传递
466 |
467 |
468 | 你可以改变 UIGestureRecognizer 特性的几个值来改变默认传递路径,让它们以特定的方式传递。如果你改变这些特性的默认值,以下行为将发生变化:
469 |
470 |
471 |
472 | delaysTouchesBegan(默认为NO)---正常情况下,窗口在Began 和 Moved 阶段把触摸对象发送给视图和手势识别器。 把delaysTouchesBegan设置为YES,使得窗口不会在Began阶段把触摸对象发送给视图。 这样做确保一个手势识别器识别它的手势时,没有把部分触摸事件传递给相连的视图。 设置该特性时请谨慎,因为它会使你的界面反应迟钝。
473 |
474 |
475 | delaysTouchesEnded(默认为YES)---当该特性被设置为YES时,它确保视图不会完成一个动作,而该动作是手势可能想在稍候取消的。当一个手势识别器正在分析一个触摸事件时,窗口不会不会在Ended阶段传递触摸对象到相连的视图。如果一个手势识别器识别出它的手势,则触摸对象被取消。 如果手势识别器没有识别出它的手势,窗口通过一个touchesEnded:withEvent:消息把这些对象传递给视图。设置该特性为NO,允许视图和手势识别器可以同时在Ended阶段分析触摸对象。
476 |
477 | 例如,设想一个视图有一个点击手势识别器,它要求双击,然后用户双击了该视图。 把特性设置为YES后,视图获得touchesBegan:withEvent:, touchesBegan:withEvent:, touchesCancelled:withEvent:, 以及 touchesCancelled:withEvent: 信息序列。如果该特性被设置为NO,视图获得以下信息序列:touchesBegan:withEvent:, touchesEnded:withEvent:, touchesBegan:withEvent:, andtouchesCancelled:withEvent: ,它表示在touchesBegan:withEvent: 消息中,视图可以识别一个双击。
478 | If a gesture recognizer detects a touch that it determines is not part of its gesture, it can pass the touch directly to its view. To do this, the gesture recognizer callsignoreTouch:forEvent: on itself, passing in the touch object.
479 |
480 | 如果一个手势识别器侦测到一个不属于该手势的触摸,它可以把该触摸直接传递给它的视图。 要想实现它,手势识别器对自己调用ignoreTouch:forEvent:方法,它在触摸对象中传递。
481 |
482 |
483 | 六、创建一个自定义手势识别器
484 |
485 |
486 | 要想实现一个自定义手势识别器,首先在Xcode里创建一个 UIGestureRecognizer 的子类。然后,在子类的头文件中加入以下import指令。
487 |
488 | ```
489 | #import
490 | ```
491 |
492 |
493 | 下一步,从UIGestureRecognizerSubclass.h中拷贝以下方法声明到你的头文件;这些是你在子类中需要重写的方法。
494 |
495 | ```
496 | - (void)reset;
497 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
498 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
499 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
500 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
501 | ```
502 |
503 |
504 | 这些方法跟早先在“An App Receives Touches in the Touch-Handling Methods.” 中所描述的相关触摸事件处理有完全相同的签名和行为。 在所有这些需要重写的方法中,你必须调用父类的实现,即使该方法有一个null实现。
505 |
506 |
507 | 注意:UIGestureRecognizerSubclass.h中的 state 特性目前是readwrite状态,而不是readonly,就跟它在UIGestureRecognizer.h中一样。 你的子类可以通过给特性分配 UIGestureRecognizerState 常量(constants)来改变其状态。
508 |
509 |
510 | 1、为自定义手势识别器实现触摸事件处理方法
511 |
512 |
513 | 一个自定义手势识别器实现的核心是四个方法: touchesBegan:withEvent:, touchesMoved:withEvent:,touchesEnded:withEvent:, and touchesCancelled:withEvent:. 在这些方法中,你通过设置一个手势识别器的状态,把低层触摸事件解析为手势识别。列表1-8创建了一个离散单击勾选(checkmark)手势的手势识别器。 它记录了手势的中心点---即勾的上升开始点---这样客户就可以获取这个值。
514 |
515 |
516 | 该例子只有一个单一视图,但是大多是应用程序都有多个视图。一般来说,你应该把触摸位置转换为屏幕的坐标系,这样你就可以正确的识别出跨越(span)多个视图的手势。
517 |
518 |
519 |
520 | 列表1-8 一个勾(checkmark)手势识别器的实现
521 |
522 | ```
523 | #import
524 |
525 | // Implemented in your custom subclass
526 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
527 | [super touchesBegan:touches withEvent:event];
528 | if ([touches count] != 1) {
529 | self.state = UIGestureRecognizerStateFailed;
530 | return;
531 | }
532 | }
533 |
534 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
535 | [super touchesMoved:touches withEvent:event];
536 | if (self.state == UIGestureRecognizerStateFailed) return;
537 | UIWindow *win = [self.view window];
538 | CGPoint nowPoint = [touches.anyObject locationInView:win];
539 | CGPoint nowPoint = [touches.anyObject locationInView:self.view];
540 | CGPoint prevPoint = [touches.anyObject previousLocationInView:self.view];
541 |
542 | // strokeUp is a property
543 | if (!self.strokeUp) {
544 | // On downstroke, both x and y increase in positive direction
545 | if (nowPoint.x >= prevPoint.x && nowPoint.y >= prevPoint.y) {
546 | self.midPoint = nowPoint;
547 | // Upstroke has increasing x value but decreasing y value
548 | } else if (nowPoint.x >= prevPoint.x && nowPoint.y <= prevPoint.y) {
549 | self.strokeUp = YES;
550 | } else {
551 | self.state = UIGestureRecognizerStateFailed;
552 | }
553 | }
554 | }
555 |
556 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
557 | [super touchesEnded:touches withEvent:event];
558 | if ((self.state == UIGestureRecognizerStatePossible) && self.strokeUp) {
559 | self.state = UIGestureRecognizerStateRecognized;
560 | }
561 | }
562 |
563 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
564 | [super touchesCancelled:touches withEvent:event];
565 | self.midPoint = CGPointZero;
566 | self.strokeUp = NO;
567 | self.state = UIGestureRecognizerStateFailed;
568 | }
569 |
570 | ```
571 |
572 | 离散手势和连续手势的状态过渡是不一样的,正如在 “Gesture Recognizers Operate in a Finite State Machine.” 中所描述。 当你创建一个自定义手势识别器时,你通过给它分配响应的状态来表明(indicate)它是离散手势或是连续手势。比如,列表1-8 中的复选标记(checkmark)手势识别器,它不会把状态设置为Began 或者 Changed,因为它是离散手势。
573 |
574 |
575 | 当你子类化一个手势识别器时,最重要的事情是正确地设置手势识别器的state。 为了手势识别器的的交互如预期,iOS需要了解一个手势识别器的状态。比如,如果你想要实现同时识别或者要求一个手势识别器失败,iOS需要了解识别器的当前状态。
576 |
577 |
578 | 关于创建自定义手势识别器的更多信息,请看 WWDC 2012: Building Advanced Gesture Recognizers.
579 |
580 |
581 | 2. 重置手势识别器的状态
582 |
583 |
584 | 如果你的手势识别器过渡到Recognized/Ended, Canceled, 或者Failed状态,UIGestureRecognizer 类刚好在手势识别器过渡回Possible状态前调用reset 方法。
585 |
586 |
587 | 实现reset 方法来重置任何内部状态,这样你的识别器能准备进行识别手势的一个新的尝试,正如列表1-9所示。当手势识别器从该方法返回后,它将不再接收任何在进行的触摸更新。
588 |
589 |
590 |
591 | 列表1-9 重置一个手势识别器
592 | ```
593 | - (void)reset {
594 | [super reset];
595 | self.midPoint = CGPointZero;
596 | self.strokeUp = NO;
597 | }
598 |
599 | ```
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/discrete_vs_continuous_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/discrete_vs_continuous_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/event_touch_time_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/event_touch_time_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/events.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/events.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/gestureRecognizer_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/gestureRecognizer_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/gr_state_transitions_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/gr_state_transitions_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/hit_testing_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/hit_testing_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/iOS_responder_chain_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/iOS_responder_chain_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/path_of_touches_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/path_of_touches_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/imgs/recognize_touch_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/interaction/imgs/recognize_touch_2x.png
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/summary.md:
--------------------------------------------------------------------------------
1 | 本篇内容将围绕iOS中事件及其传递机制进行学习和分析。在iOS中,事件分为三类:
2 |
3 | * 触控事件(单点、多点触控以及各种手势操作)
4 | * 传感器事件(重力、加速度传感器等)
5 | * 远程控制事件(远程遥控iOS设备多媒体播放等)
6 |
7 | 
8 |
9 | 这三类事件共同构成了iOS设备丰富的操作方式和使用体验,本次就首先来针对第一类事件:触控事件,进行学习和分析。
10 |
11 | 我们通常不是简单的把View布局到屏幕上就完事了。我们还需要提供能力让用户能与这些View进行交互。而在UIKit提供的框架中主要的就是触摸事件,当然还有摇晃了等其他一些事情,我们这里先不考虑,主要关于来自屏幕的事件和对其处理方式。
12 |
13 | 触摸事件是指我们对用户的手指触击屏幕以及在屏幕上移动时的一个抽象。从程序层面上讲就是,在用户发生上述行为时,系统不断发送给我们App的那些事件对象,然后按照特定的路径传递给我们App中的一些对象来处理。处理这些事件的对象主要有两类一个是UIViewController的子类和UIView的子类。在IOS中,我们用UITouch对象来表示一个触摸,而用UIEvent对象来描述一个事件。UIEvent事件对象中包含与一些列与用户操作相关的所有UITouch触摸对象,同时还可以提供与特定窗口相关联的触摸对象。其实,在实际的使用过程中,在UITouch和UIEvent两个对象中,我们使用比较多的一个对象是UITouch。所以我们这里先了解一下UITouch的都包含了那些用户触摸的数据。然后我们再来看看系统式如何传递这些事件并处理他们的。
14 |
15 | ## 1. [触摸事件](touch.md)
16 | ## 2. [事件传递](delivery.md)
17 | ## 3. [手势](gestures.md)
18 |
--------------------------------------------------------------------------------
/articles/Chapter0/interaction/touch.md:
--------------------------------------------------------------------------------
1 | #触摸事件响应相关函数
2 |
3 |
4 | #####UITouch对象解析
5 |
6 | 通过阅读[UITouch Class Reference](https://developer.apple.com/library/ios/documentation/uikit/reference/UITouch_Class/Reference/Reference.html),我们看到在一个UITouch对象中主要是存储了与触摸用户触摸相关的信息:位置信息和时间信息。
7 |
8 | ```
9 | //获取位置信息
10 | – locationInView:
11 | – previousLocationInView:
12 | //获取时间信息,只读属性
13 | timestamp property
14 | ....
15 | //其他
16 | ```
17 | 如果我们来对触摸进行抽象的话,也会主要在触摸对象中存储着两类信息。因为对于一个事件来说,大家公认的要素是:时间地点人物故事情节。在用户触摸屏幕这个事情上,人物是用户,故事情节就是开发者在app安排给用户的一些逻辑,这些都是更高层的抽象要关心的事情。而只有时间和地点(位置)是一个触摸对象必须关心的。有了这两方面的信息我们就能够确定,用户在什么时间触摸或触击了屏幕的哪个位置。位置新的的表示使用的CGPoint,对头,就是我们在集合布局框架中介绍的存储点信息的对象。但是UITouch对象存储的知识整个触摸或者触击时间的片段信息或者说状态信息。如果我们要完整的描述一个事件,我们需要一些列的UITouch对象。
18 |
19 | 假设一个事件有三个过程:从用户手指点到屏幕,在屏幕上移动,再到从屏幕上移开。那么这三个过程最少对对应三个UITouch对象。这就要说到我们是怎样处理触摸事件的了。
20 |
21 | #####处理触摸事件
22 |
23 | UIKit使用UIResponder作为响应对象,来响应系统传递过来的时间并进行处理。UIApplication、UIViewController、UIView、和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类。
24 |
25 | ```
26 | – touchesBegan:withEvent:
27 | – touchesMoved:withEvent:
28 | – touchesEnded:withEvent:
29 | – touchesCancelled:withEvent:
30 |
31 | ```
32 |
--------------------------------------------------------------------------------
/articles/Chapter0/layout/coordinate.md:
--------------------------------------------------------------------------------
1 | UIKit中的坐标是基于这样的坐标系统:以左上角为坐标的原点,原点向下和向右为坐标轴正向。坐标值由浮点数来表示,内容的布局和定位因此具有更高的精度,还可以支持与分辨率无关的特性。图2-3显示了这个相对于屏幕的坐标系统,这个坐标系统同时也用于UIWindow和UIView类。视图坐标系统的方向和Quartz及Mac OS X使用的缺省方向不同,选择这个特殊的方向是为了使布局用户界面上的控件及内容更加容易。
2 |
3 | 
4 |
5 | 您在编写界面代码时,需要知道当前起作用的坐标系统。每个窗口和视图对象都维护一个自己本地的坐标系统。视图中发生的所有描画都是相对 于视图本地的坐标系统。但是,每个视图的边框矩形都是通过其父视图的坐标系统来指定,而事件对象携带的坐标信息则是相对于应用程序窗口的坐标系统。为了方 便,UIWindow和UIView类都提供了一些方法,用于在不同对象之间进行坐标系统的转换。
6 |
7 | 虽然Quartz使用的坐标系统不以左上角为原点,但是对于很多Quartz调用来说,这并不是问题。在调用视图的drawRect:方法之前,UIKit会自动对描画环境进行配置,使左上角成为坐标系统的原点,在这个环境中发生的Quartz调用都可以正确地在视图中描画。您唯一需要考虑不同坐标系统之间差别的场合是当您自行通过Quartz建立描画环境的时候。
--------------------------------------------------------------------------------
/articles/Chapter0/layout/gemotry.md:
--------------------------------------------------------------------------------
1 | ##几何布局框架
2 |
3 | 在UIKit的几何布局模型中核心的一个数据结构是:```CGRect```,它确定了一个View(或者Layer,我们这里先只考虑View的情况,想不详细展开来说其他的)在父View中坐标系的绝对位置。
4 |
5 | 那让我们来看一下CGRect的定义:
6 |
7 | ```
8 | /* Points. */
9 |
10 | struct CGPoint {
11 | CGFloat x;
12 | CGFloat y;
13 | };
14 | typedef struct CGPoint CGPoint;
15 |
16 | /* Sizes. */
17 |
18 | struct CGSize {
19 | CGFloat width;
20 | CGFloat height;
21 | };
22 | typedef struct CGSize CGSize;
23 |
24 | /* Rectangles. */
25 |
26 | struct CGRect {
27 | CGPoint origin;
28 | CGSize size;
29 | };
30 | typedef struct CGRect CGRect;
31 | ```
32 |
33 | 我们发现其实一个CGRect中包含了一个原点(point)和一组宽高的信息(size)。其实一个CGRect就是描述了一个长方形的块,就像下图的红色方块一样的东西,我们的每一个View在坐标系中都会被表示为一个长方形的块状物。
34 |
35 | 比如我们有一个位置是{{10,10},{20,20}}的View:
36 |
37 | ```
38 | UIView* aView = [UIView new];
39 | aView.frame = CGRectMake(0, 0 , 100 ,100)
40 | ```
41 | 在它的父类的坐标系中展示如下图:
42 | 
43 |
44 | 我们能够发现红色的View的frame信息所描述的几何位置,其实是其在父View坐标系中的绝对位置。死死的写在那里的。所以像UIKit这样的布局模型又叫绝对布局模型,如果你用过jave的Swing或者c++的QT,你可能会觉得这种绝对布局模型好麻烦,好啰嗦。没有布局管理器的概念,什么都是绝对的。但是只能说各有各的好处把。QT之类的有布局管理器的开发复杂界面的确方便,但是像在iphone这样的手机设备上,机器屏幕有限、设备性能有限,用绝对布局模型还是比较合适。苹果在IOS5之后也引入了一些相对布局的东西(autolayout)正好这里有篇文章是说其性能的[Auto Layout Performance on iOS](http://floriankugler.com/blog/2013/4/21/auto-layout-performance-on-ios)。读过之后你能发现自动布局在复杂界面情况下的性能的确比较差的。所以像UIKit这种比较原始的绝对布局在性能上还是有优势的。
45 |
46 | 扯回来,通过上图我们能够发现,UIKit的坐标系是一个二维平面坐标系,以左上角为原点,x轴横向扩展,y轴纵向向下扩展。y轴的防线可能和我们以前上学的时候,学的坐标系有点不太一样。这个估计是考虑在ios屏幕上布局的时候我们一般都是从上往下布局,y轴向下方便我们布局吧。既然知道了UIKit的坐标系统是一个二维平面坐标系统,那么我们以前学的很多几何知识就能够在这个坐标系统中尽情使用了。这里知识点太多不一而足,也是埋个伏笔,知道我们在些TableView的时候会用到很多几何上的知识。
47 |
48 | 同时,你可以把整个UIKit的View布局系统看成一个递归的系统,一个view在父view中布局,父view又在其父view中布局,最后直到在UIWindow上布局。这样递归的布局开来,就能构建起我们看到的app的界面。
49 |
--------------------------------------------------------------------------------
/articles/Chapter0/layout/howlayout.md:
--------------------------------------------------------------------------------
1 | #如何布局
2 |
3 | ##布局的时候我们都需要做些什么事情
4 | 布局顾名思义,就是确定一个View的位置。也就是说我们要在布局中做的事情用一句话说就是:确定UIView的frame属性的值。给每一个UIView和其子类的实例确定frame的属性值。
5 |
6 | ##1、 初始化函数 ```- (id)initWithFrame:(CGRect)aRect```
7 |
8 | objc构建一个对象使用的是两段式,首先分配内存```alloc```然后```init```,这样的好处就是将内存操作和初始化操作解耦合,让我们能够在初始化的时候对对象做一些必要的操作。这是个很好的思路,我们在做很多事情的时候都可以使用这种两段式的思路。比如布局一个UIView,我们可以分成两部,初始化必要的子view和变量,然后在合适的时机进行布局。
9 |
10 | 而这个两段式的第一步就是:
11 |
12 | ```
13 | - (id)initWithFrame:(CGRect)aRect
14 | ```
15 |
16 | 这个函数是无论你用什么初始化函数都会被调用的一个,比如你用```[UIView new]```或者```[[UIView alloc] init]```都会调用initWithFrame这个函数(有些UIView的子类有特殊情况,比如UITableViewCell,怀疑apple对其做过特殊处理),所以你要是对一个view的变量有初始化的操作尽量往```initWithFrame```里面放还是非常合适的。
17 | 这样能够保证,以后在使用的时候所有的变量都被正确的初始化过。而我们一般会在```initWithFrame```中做些什么呢?
18 |
19 | 1. 添加子View
20 | 2. 初始化属性变量
21 | 3. 其他一些共用操作
22 |
23 | 所以我们一般会看到这样的代码
24 |
25 | ```
26 | - (instancetype) initWithFrame:(CGRect)arect
27 | {
28 | self = [super WithFrame:arect];
29 | if (!self) {
30 | return nil;
31 | }
32 | [self commitInit];
33 | <#init data#>
34 | return self;
35 | }
36 |
37 | - (void) commomInit
38 | {
39 | <#common init data#>
40 | }
41 | ```
42 |
43 | 在初花的时候将一些共用的初始化操作独立成一个函数```commomInit```然后再其中做上面说的事情,这样做的好处就是将初始化的代码集中到一起,如果你在实现的一个其他的什么initWithXXX的时候,直接调用commonInit就可以了。
44 |
45 | 不得不说的是,千万不要被这个函数的名称withFrame给忽悠了,以为这个函数使用布局用的。在代码逻辑比较清晰的工程中,几乎很少看到在这个函数中进行界面布局的工作。因为UIKit给你提供了一个专门的函数layoutSubViews来干这个事情。而且,在这个函数中做的界面布局的工作,是一次性编码,你的界面布局没有任何复用性,如果父View的大小变了之后,这个View还是傻傻的保持原来的模样。同时也会造成,初始化函数臃肿,导致维护上的困难。
46 |
47 | ##2、```layoutSubviews```和```setNeedsLayout```
48 |
49 | 上面说了一些```initWithFrame```的事情,告诫了千万不要在里面做界面布局的事情,那应该在什么地方做呢?
50 |
51 | ```
52 | layoutSubviews
53 | ```
54 |
55 | 就是这个地方,这是苹果提供给你专门做界面布局的函数。
56 |
57 | 我们来看一下文档:
58 |
59 | ```
60 | The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews.
61 |
62 | Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
63 |
64 | You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.
65 | ```
66 |
67 | 苹果都说了这个是子类化View的时候布局用的。那我们最好是老老实实的在里面做布局的工作。
68 | #####如何布局
69 |
70 | 这是个比较有意思的话题,因为可能很多人认为很简单,绝对布局嘛就是写一些死数字嘛,直接写CGRectMake(10,10,20,20)这样的坐标不就行了。如果你真这样认为,那么下面的话可能对你有帮助。
71 |
72 | 首先,尽量不要在布局的时候直接写死数字,比较稳妥的变法是使用常亮或者宏定义,甚至你定义一个临时变量也都ok,这样代码的可维护性就会变得比较好。
73 |
74 | 其次,谁说绝对布局的框架不能写成相对布局的方式。Apple提供了一个```CGGeometry.h```的文件,里面定义了大量的方便几何布局的函数。比如CGRectGetMaxX用来获取一个View的最大x坐标。你可能会问这有什么用?我们来看段代码:
75 |
76 | ```
77 | _imageView.frame = CGRectMake(0, 0, width, height);
78 | _textLabel.frame = CGRectMake(CGRectGetMaxX(_imageView.frame), 0, CGRectGetWidth(self.frame) - CGRectGetMaxX(_imageView.frame), CGRectGetHeight(self.frame));
79 | ```
80 | 下面那个_textLabel的布局就是在_imageView的大小而确定的。这不就是一些布局管理器做的事情吗,这不就是相对布局的概念嘛。所以我们完全可以使用UIKit的几何坐标系统完成一些相对布局的事情,而且也推荐这样做。
81 | #####什么时候布局
82 |
83 | 这个就看功能需要了,不过有一点是肯定的就是不要直接调用layoutSubviews函数。UIKit和runtime是捆绑很密切的,apple为了防止界面重新布局过于频繁,所以只在runloop合适的实际来做布局的工作。里面具体的细节,可以google。
84 |
85 | 一般你需要重新布局的时候调用```setNeedsLayout```标记一下,“我需要重新布局了”。就行了,系统会在下次runloop合适的时机给你布局。
86 |
87 | 
--------------------------------------------------------------------------------
/articles/Chapter0/layout/imgs/coordinate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/layout/imgs/coordinate.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/layout/imgs/update_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/layout/imgs/update_circle.png
--------------------------------------------------------------------------------
/articles/Chapter0/layout/layout.md:
--------------------------------------------------------------------------------
1 | 《核心动画编程》的某个翻译版本把UIKit的布局模型翻译成了几何布局模型,这个词非常贴切,原始的英文是“struts and springs”。字面翻译就是结构和弹簧。其实说白了就是一种绝对布局模型,这种布局模型的核心数据就是一个对象的几何属性。所以翻译成几何布局模型还是比较贴切的。
2 |
3 | ## 1. [坐标系](coordinate.md)
4 | ## 2. [平面内布局](gemotry.md)
5 | ## 3. [Z-Order布局](zorder.md)
6 | ## 4. [如何布局](howlayout.md)
7 |
--------------------------------------------------------------------------------
/articles/Chapter0/layout/zorder.md:
--------------------------------------------------------------------------------
1 | #Z-Order布局
2 |
3 | 不知道读者有没有试图想过当我们的一个程序真正运行起来的时候,那些被我们实例化的UIView和他们的子类们是以怎样的形态出现在我们的屏幕上的。前面我们介绍了IOS的几何布局框架,而通过这些框架组合起来的UIView和其子类们组成了什么东西?
4 |
5 | 是一个树形结构,或者准确说是一个倒金字塔结构。我们先看一张3D的DZTableView在只有一个Cell时候的结构图:
6 | 
7 | 通过这张图我们能够非常明显的看到这是一个一层叠一层结构。最先面的是UIWindow的实例,我们的DZTableVIew还有其他一些自定义的控件们,一层叠一层的堆在了UIWindow的实例上面。我们再换个角度看一下:
8 | 
9 |
10 | 这张图是个典型的树结构的图,一层层展开就是之后就是我们的DZTableView。通过这两张图比较形象的展示,出DZTableview最后在空间上成了一个什么东西。是的,是“空间”。用这两张图的主要目的就是,希望读者能够在自己的脑海中构建起对于UIView层次数Z-order(z轴)的概念。因为除了除了我布局UIView的各种子类的实例对象的时候,除了布局其二维属性```frame```等,其实我们还需要布局他们的3维属性,即Z-order顺序。
11 |
12 | 那我们来看看UIKit给我们提供了哪些函数来做这个事情:
13 |
14 | ```
15 | – addSubview:
16 | – bringSubviewToFront:
17 | – sendSubviewToBack:
18 | – removeFromSuperview
19 | – insertSubview:atIndex:
20 | – insertSubview:aboveSubview:
21 | – insertSubview:belowSubview:
22 | – exchangeSubviewAtIndex:withSubviewAtIndex:
23 | – isDescendantOfView:
24 |
25 | //属性部分
26 | superview property
27 | subviews property
28 | window property
29 | ```
30 |
31 | 这些函数通过他们的名字很容易理解他们的意思。```addSubView:```直接将一个View附加在当前View上面。而这个附加过程,可以暂时先简单的理解成一个堆栈的PUSH操作。先附加的View被压在下面,后附加的View在最上面。在暂时把其他函数抛在一遍的情况下,和对堆栈的操作一样,我们是通过UIView的添加顺序(LILO)来控制视图的Z-Order的。但是和堆栈不同的是,UIView没有提供POP操作。因为实际上,解释一个视图中其子视图的比较好的模型是可随机访问的数组,类似于NSArray。不同的是,在父视图中没有直接删除子视图操作。删除操作下放给了我子视图。如果一个要移除一个视图,我们需要调用子视图的```removeFromSuperview```方法,来将其从父视图的层次数种移除。然后其他视图就像你在一堆积木中,抽了比较下面的一根一样,在其下放的保持原先的顺序不动,在其上方的自动下落,添补被抽离的积木造成的空缺,z-order的顺序自动减一。
32 |
33 | 我曾经思考过,为什么UIKit在设计的时候,不直接在父视图中完成所有的子视图层次数的操作,而是把删除的操作放到了子视图中。刚开始从设计的角度去考虑,像增删改查这些基本的操作不应该放在一起吗?放在一起的话,能够降低子视图和父视图之间的耦合。全部都在父视图中做了,子视图只是一个被统治而已。这样的模型比较简单,也比较好理解。后来在世界的编程实现中,慢慢体会到了apple那群牛逼的工程师的用心良苦。个人认为这绝对是他们对实际编程经验的一个提炼。我们在附加视图的时候,一半都是有了一个父视图的实例有了一个子视图的实例,然后将子视图附加在父视图上,而这个过程一半都是在父视图的类的某些成员函数中进行的。在父视图的成员函数中进行,我们能够非常方便的获取当前视图(父视图)和其子视图的实例,然后调用```addSubView```函数。而在删除的时候,我们一般都是在子视图中进行的。直接在子视图的类的某个函数中,当检测到子视图满足一定的状态的时候,将其删除。而在子视图中相对来说获取父视图的实例编程是比较啰嗦的:
34 |
35 | ```
36 | //假设UIView存在一个函数removeSubView:那么这个过程是
37 | [self.superView removeSubView:self]
38 | //而removeSubView大概要做这么几个事情
39 |
40 | ....
41 | - (void) removeSubView:(UIView*)a
42 | {
43 | //确定是否有这个子视图
44 | if([self isContentSubView:a])
45 | {
46 | //找到子视图的Z-order
47 | int index = [self indexOfSubView:a];
48 | //删除掉
49 | [self removeSubViewAtIndex:index];
50 | }
51 | }
52 | ```
53 |
54 | 何必要获取一遍父视图的实例进行删除操作呢,直接把这个过程封装起来,用一句:
55 | ```[self removeFromSuperView]```多好啊。
56 | 而且即使在父视图进行层次数操作的时候,也是有了子视图的实例之后进行操作:
57 | ```[aView removeFromeSuperView]```。这种封装,应该属于实用性的一种封装。细细品味,很有味道。
58 |
59 | 而其他的调整子视图的Z-Order的函数,通过函数名字我们也很容易理解。
60 |
61 | 1. ```insertSubview:atIndex:```直接将一个子视图加到特定的位置。
62 | 2. ```insertSubview:aboveSubview:```将一个子视图加到一个相对位置,在特定视图的上方。
63 | 3. ```insertSubview:belowSubview:```将一个子视图加到一个相对位置,在特定视图的下方。
64 | 4. ```exchangeSubviewAtIndex:withSubviewAtIndex:```交换两个特定位置的视图
65 | 5. ```bringSubviewToFront```将一个视图挪到最上方。
66 | 6. ```sendSubviewToBack```将一个视图挪到最下方。
67 |
68 | 而我们其实在调整视图的Z-order的时候,是否想过调整Z-order用什么用呢?想当然的一个答案是,视图不就是层次结构的嘛,有个Z-order也很正常。但是,如果我们能够完全在二维平面中,完成对各种视觉结构的展示,就用不到三维结构。而偏偏我们这个世界是三维的,xyz三个轴才能确定一个物体。看一下那些优秀的游戏引擎把基本上都是直接用3D的模型来描述一个视觉对象。这种强3D的模型,能够接近真实的描述视觉结构,渲染出更加丰富多彩的虚拟世界。而UIKit中核心动画部分也是使用了类似于3D信息比较强的3D模型。所以,使用3D的模型来描述视觉结构师再正常不过的事情了,只是,在UIView这个层次上强3D的模型对于渲染界面来说,意义不是很大,只需要简单的保留一个z-order就OK了。这样利用xyz三个维度构建出来的UIKit的视图模型才能比较真实的拟合我们看到真实世界的例子。
69 |
70 | 举个例子,比如透明度这个事情。比如我们为了实现给一个UILabel加一个图片背景,我们会怎么做?
71 |
72 | ```
73 | UIImageView* imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"a"]];
74 | [self.view addSubview:imgView];
75 |
76 | UILabel* label = [UILabel new];
77 | //将Label的背景设置成透明的,这样就能够看到label下面的图片了
78 | label.backgroundColor = [UIColor clearColor];
79 | [self.view addSubview:label];
80 | ```
81 |
82 | 我们会把Label的背景设置成透明的,这样label下放的图片自然就会显示出来。这和我们在真实3D世界中,通过玻璃看窗外一个道理。玻璃叠在了窗外的景色上面,但是当光纤映射到我们眼中时,我们看到的是一副图像。同样Label的例子在我们严重也是一个整体的图像。虽然在使用者严重他们不会在意你有Label了,label有透明度了。label后面还有图像了。他们只关心看在严重的整个图像看起来是个什么样子,但是对于我们开发者来说。我们需要一个恰当的模型来描述这种结构,而对于UIKit来说增加一个Z-order足矣,够用了。至于那些复杂的3D结构信息,交给更加底层的核心动画来处理吧。
83 |
--------------------------------------------------------------------------------
/articles/Chapter0/scrollview.md:
--------------------------------------------------------------------------------
1 | # UISCrollView详解
2 |
3 | UITableView的父类是UIScrollView。当然我们要实现一个TableView也需要继承自UIScrollView,那么我们就需要看一下UIScrollView的一些属性和方法。
4 |
5 | 可能你很难相信,UIScrollView和一个标准的UIView差异并不大,scroll view确实会多一些方法,但这些方法只是UIView一些属性的表面而已。因此,要想弄懂UIScrollView是怎么工作之前,你需要了解 UIView,特别是视图渲染过程的两步。
6 |
7 |
8 |
9 | ## 光栅化和组合
10 |
11 | 渲染过程的第一部分是众所周知的光栅化,光栅化简单的说就是产生一组绘图指令并且生成一张图片。比如绘制一个圆角矩形、带图片、标题居中的UIButtons。这些图片并没有被绘制到屏幕上去;取而代之的是,他们被自己的视图保持着留到下一个步骤用。
12 |
13 | 一旦每个视图都产生了自己的光栅化图片,这些图片便被一个接一个的绘制,并产生一个屏幕大小的图片,这便是上文所说的组合。视图层级(view hierarchy)对于组合如何进行扮演了很重要的角色:一个视图的图片被组合在它父视图图片的上面。然后,组合好的图片被组合到父视图的父视图图片上 面,就这样。。。最终视图层级最顶端是窗口(window),它组合好的图片便是我们看到的东西了。
14 |
15 | 概念上,依次在每个视图上放置独立分层的图片并最终产生一个图片,单调的图像将会变得更容易理解,特别是如果你以前使用过像Photoshop这样的工具。我们还有另外一篇文章详细解释了像素是如何绘制到屏幕上去的。
16 |
17 | 现在,回想一下,每个视图都有一个bounds和frame。当布局一个界面时,我们需要处理视图的frame。这允许我们放置并设置视图的大小。 视图的frame和bounds的大小总是一样的,但是他们的origin有可能不同。弄懂这两个工作原理是理解UIScrollView的关键。
18 |
19 | 在光栅化步骤中,视图并不关心即将发生的组合步骤。也就是说,它并不关心自己的frame(这是用来放置视图的图像)或自己在视图层级中的位置(这 是决定组合的顺序)。这时视图只关心一件事就是绘制它自己的content。这个绘制发生在每个视图的drawRect:方法中。
20 |
21 | 在drawRect:方法被调用前,会为视图创建一个空白的图片来绘制content。这个图片的坐标系统是视图的bounds。几乎每个视图 bounds的origin都是{0,0}。因此,当在删格化图片左上角绘制一些东西的时候,你都会在bounds的origin({x:0,y:0}) 处绘制。在一个图片右下角的地方绘制东西的时候,你都会绘制在{x:width, y:height}处。如果你的绘制超出了视图的bounds,那么超出的部分就不属于删格化图片的部分了,并且会被丢弃。
22 |
23 | 
24 |
25 | 在组合的步骤中,每个视图将自己光栅化图片组合到自己父视图的光栅化图片上面。视图的frame决定了自己在父视图中绘制的位置,frame的 origin表明了视图光栅化图片左上角相对父视图光栅化图片左上角的偏移量。所以,一个origin为{x:20,y:15}的frame所绘制的图片 左边距其父视图20点,上边距父视图15点。因为视图的frame和bounds矩形的大小总是一样的,所以光栅化图片组合的时候是像素对齐的。这确保了 光栅化图片不会被拉伸或缩小。
26 |
27 | 
28 |
29 | 记住,我们才仅仅讨论了一个视图和它父视图之间的组合操作。一旦这两个视图被组合到一起,组合的结果图片将会和父视图的父视图进行组合。。。这是一个雪球效应。
30 |
31 | 考虑一下组合图片背后的公式。视图图片的左上角会根据它frame的origin进行偏移,并绘制到父视图的图片上:
32 |
33 | ```
34 | CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;
35 |
36 | CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;
37 | ```
38 | 我们可以通过几个不同的frames看一下:
39 |
40 |
41 | 这样做是有道理的。我们改变button的frame.origin后,它会改变自己相对紫色父视图的位置。注意,如果我们移动button直到它 的一部分已经在紫色父视图bounds的外面,当光栅化图片被截去时这部分也将会通过同样的绘制方式被截去。然而,技术上讲,因为iOS处理组合方法的原 因,你可以将一个子视图渲染在其父视图的bounds之外,但是光栅化期间的绘制不可能超出一个视图的bounds。
42 |
43 | Scroll View的Content Offset
44 |
45 | 现在,我们所讲的跟UIScrollView有什么关系呢?一切都和它有关!考虑一种我们可以实现的滚动:我们有一个拖动时frame不断改变的视 图。这达到了相同的效果,对吗?如果我拖动我的手指到右边,那么拖动的同时我增大视图的origin.x,瞧,这货就是scroll view。
46 |
47 | 当然,在scroll view中有很多具有代表性的视图。为了实现这个平移功能,当用户移动手指时,你需要时刻改变每个视图的frames。当我们提出组合一个view的光栅化图片到它父视图什么地方时,记住这个公式:
48 |
49 | ```
50 | CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;
51 |
52 | CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;
53 | ```
54 | 我们减少Superview.bounds.origin的值(因为他们总是0)。但是如果他们不为0呢?我们用和前一个图例相同的frames,但是我们改变了紫色视图bounds的origin为{-30,-30}。得到下图:
55 |
56 | 
57 |
58 |
59 | 现在,巧妙的是通过改变这个紫色视图的bounds,它每一个单独的子视图都被移动了。事实上,这正是一个scroll view工作的原理。当你设置它的contentOffset属性时:它改变scroll view.bounds的origin。事实上,contentOffset甚至不是实际存在的。代码看起来像这样:
60 |
61 | - (void)setContentOffset:(CGPoint)offset
62 | {
63 | CGRect bounds = [self bounds];
64 | bounds.origin = offset;
65 | [self setBounds:bounds];
66 | }
67 | 注意:前一个图例,只要足够的改变bounds的origin,button将会超出紫色视图和button组合成的图片的范围。这也是当你足够的移动scroll view时,一个视图会消失!
68 |
69 | 世界之窗:Content Size
70 |
71 | 现在,最难的部分已经过去了,我们再看看UIScrollView另一个属性:contentSize。scroll view的content size并不会改变其bounds的任何东西,所以这并不会影响scroll view如何组合自己的子视图。反而,content size定义了可滚动区域。scroll view的默认content size为{w:0,h:0}。既然没有可滚动区域,用户是不可以滚动的,但是scroll view任然会显示其bounds范围内所有的子视图。
72 |
73 | 当content size设置为比bounds大的时候,用户就可以滚动视图了。你可以认为scroll view的bounds为可滚动区域上的一个窗口:
74 | 
75 |
76 |
77 | 当content offset为{x:0,y:0}时,可见窗口的左上角在可滚动区域的左上角处。这也是content offset的最小值;用户不能再往可滚动区域的左边或上边移动了。那儿没啥,别滚了!
78 |
79 | content offset的最大值是content size和scroll view size的差。这也在情理之中:从左上角一直滚动到右下角,用户停止时,滚动区域右下角边缘和滚动视图bounds的右下角边缘是齐平的。你可以像这样记 下content offset的最大值:
80 |
81 | ```
82 | contentOffset.x = contentSize.width - bounds.size.width;
83 | contentOffset.y = contentSize.height - bounds.size.height;
84 | ```
85 | 用Content Insets对窗口稍作调整
86 |
87 | contentInset属性可以改变content offset的最大和最小值,这样便可以滚动出可滚动区域。它的类型为UIEdgeInsets,包含四个值: {top,left,bottom,right}。当你引进一个inset时,你改变了content offset的范围。比如,设置content inset顶部值为10,则允许content offset的y值达到10。这介绍了可滚动区域周围的填充。
88 |
89 |
90 | 
91 |
92 | 这咋一看好像没什么用。实际上,为什么不仅仅增加content size呢?除非没办法,否则你需要避免改变scroll view的content size。想要知道为什么?想想一个table view(UItableView是UIScrollView的子类,所以它有所有相同的属性),table view为了适应每一个cell,它的可滚动区域是通过精心计算的。当你滚动经过table view的第一个或最后一个cell的边界时,table view将content offset弹回并复位,所以cells又一次恰到好处的紧贴scroll view的bounds。
93 |
94 | 当你想要使用UIRefreshControl实现拉动刷新时发生了什么?你不能在table view的可滚动区域内放置UIRefreshControl,否则,table view将会允许用户通过refresh control中途停止滚动,并且将refresh control的顶部弹回到视图的顶部。因此,你必须将refresh control放在可滚动区域上方。这将允许首先将content offset弹回第一行,而不是refresh control。
95 |
96 | 但是等等,如果你通过滚动足够多的距离初始化pull-to-refresh机制,因为table view设置了content inset,这将允许content offset将refresh control弹回到可滚动区域。当刷新动作被初始化时,content inset已经被校正过,所以content offset的最小值包含了完整的refresh control。当刷新完成后,content inset恢复正常,content offset也跟着适应大小,这里并不需要为content size做数学计算。(这里可能比较难理解,建议看看EGOTableViewPullRefresh这样的类库就应该明白了)
97 |
98 | 如何在自己的代码中使用content inset?当键盘在屏幕上时,有一个很好的用途:你想要设置一个紧贴屏幕的用户界面。当键盘出现在屏幕上时,你损失了几百个像素的空间,键盘下面的东西全都被挡住了。
99 |
100 | 现在,scroll view的bounds并没有改变,content size也并没有改变(也不需要改变)。但是用户不能滚动scroll view。考虑一下之前一个公式:content offset的最大值并不同于content size和bounds的大小。如果他们相等,现在content offset的最大值是{x:0,y:0}.
101 |
102 | 现在开始出绝招,将界面放入一个scroll view。scroll view的content size仍然和scroll view的bounds一样大。当键盘出现在屏幕上时,你设置content inset的底部等于键盘的高度。
103 |
104 |
105 | 
106 |
107 | 这允许在content offset的最大值下显示滚动区域外的区域。可视区域的顶部在scroll view bounds的外面,因此被截取了(虽然它在屏幕之外了,但这并没有什么)。
108 |
109 | 但愿这能让你理解一些滚动视图内部工作的原理,你对缩放感兴趣?好吧,我们今天不会谈论它,但是这儿有一个有趣的小窍门:检查 viewForZoomingInScrollView:方法返回视图的transform属性。你将再次发现scroll view只是聪明的利用了UIView已经存在的属性。
110 |
--------------------------------------------------------------------------------
/articles/Chapter0/summary.md:
--------------------------------------------------------------------------------
1 | # UIKit给我们提供的基础
2 |
3 | 又重复了一遍,工欲善其事必先利其器。那么我们就看一下UIKit为我们提供了那些好用的工具让我们来实现一个TableView(当然不是子类化一个UITableView这么简单)。
4 |
5 | 这里会牵扯到一个另外一个问题,可能有些读者会问可不可以从最最底层的开始做起,来实现一个TableView呢,比如从写一个图形界面库开始。这个从技术上来说,完全可以实现,但是仔细想想在Apple为我们提供了UIKit之后,如果我们不是写游戏的话,貌似完全没有必要重新造这个轮子啊。当然你要写游戏的话,那令当别论,请出门左转有开源的Cocoa2d,作者要是针对从最底层开始构建感兴趣可以看一下Cocoa2d的开源代码,想必肯定大有收获。
6 |
7 | 对于实现一个主要在应用中使用的TableView来说,就没有必要重新造这个轮子。从研究UIKit为我们提供的一些对象和功能开始,就可以构建起来TableView,这也是我们文章一开始的目的——通过实现TableView来深入理解UIKit编程。
8 |
9 | 在研究的过程中,我们也会探讨一些UIKit在设计这个图形界面库的时候一些设计动机。但是,这里必须提醒读者的是,很多探讨只是我和一些朋友探讨的结果,虽然在我们看来是对的。但是可能苹果当初并不是这么想的,我还是写出来,也是做了一些思想斗争的,毕竟谁都想只把大家都认为是正确的东西提供给大家。我这样做是为了给读者提供另一种视角来理解UIKit这个非常牛的库。其中不对的地方,当然非常欢迎各位指正。
10 |
11 | ## 1. [UIKit概述](uikit.md)
12 | ## 2. [布局](./layout/layout.md)
13 | ## 3. [交互](interaction/summary.md)
14 | ## 4. [UISCrollView详解](scrollview.md)
15 |
--------------------------------------------------------------------------------
/articles/Chapter0/uikit.md:
--------------------------------------------------------------------------------
1 | UIKitk框架提供一系列的Class(类)来建立和管理IOS应用程序的用户界面(UI)接口、应用程序对象、事件控制、绘图模型、窗口、视图和用于控制触摸屏等的接口。可以理解成是苹果提供给开发和来操纵程序和界面的一个API库。
2 |
3 | 其类图如下所示:
4 | 
5 |
6 | ## 1. [UIApplication](./uikit/application.md)
7 |
8 | ## 2. [窗口和视图](./uikit/windowAview.md)
9 | ## 3. [动画](./animation/animation.md)
10 |
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/application.md:
--------------------------------------------------------------------------------
1 | UIApplication类提供了对运行在IOS设备上的app集中控制和调度的机制。每一个IOS app必须有一个而且只能有一个UIApplication或者其子类的实例。当程序启动的时候,会调用```UIApplicationMain```函数,在这个函数中会创建一个UIapplication类的单例,这个单例在整个IOS系统中就是你的App的抽象。之后你就能够通过```shareApplication```方法来调用该单例。
2 |
3 | UIApplication对象的主要工作是处理用户事件的路由。它也会给UIcontrol对象分发动作消息。另外,UIApplication还维护了当前App打开的窗口的列表。所以,你通过它能够取到你App中任何一个View。
4 |
5 | 这个app实例还实现了一个delegate,接受各种各样程序运行时的事件,比如:程序启动、低内存警告、程序崩溃等等。
6 |
7 | 程序还能通过```openURL:```方法来接受和处理一个邮件或者图片文件。比如一个以Email开头的URL将能够唤起Email程序来展示这个邮件。
8 |
9 | UIApplication的编程接口让你能够管理一些硬件指定的行为。比如:
10 |
11 |
12 | * 控制App来响应设备方向变化
13 | * 暂时终止接受触摸事件
14 | * 打开或者关闭接近用户脸部的感应
15 | * 注册远程消息通知
16 | * 打开或者关闭undo-redo UI
17 | * 决定你的程序是否能够支持某一类的URL
18 | * 扩展程序能力,让app能够在后台运行
19 | * 发布或者取消本地通知
20 | * 接受运程控制事件
21 | * 执行程序级别的复位操作
22 |
23 | UIApplication必须实现UIApplicationDelegate协议来实现他的一些协议。
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/architecture.md:
--------------------------------------------------------------------------------
1 | 视图和窗口架构
2 | 视图和窗口展示了应用的用户界面,同时负责界面的交互。UIKit和其他系统框架提供了很多视图,你可以就地使用而几乎不需要修改。当你需要展示的内容与标准视图允许的有很大的差别时,你也可以定义自己的视图。
3 |
4 | 不管你是使用系统的视图还是创建自己的视图,你需要理解UIView和UIWindow类所提供的基本结构。这些类提供了复杂的方法来管理视图的布局和展示。理解这些方法的工作非常重要,使你在应用发生改变时可以确认视图有合适的行为。
5 |
6 |
7 | 视图架构
8 |
9 | 大部分你想要可视化操作都是由视图对象-即UIView类的实例-来进行的。一个视图对象定义了一个屏幕上的一个矩形区域,同时处理该区域的绘制和触屏事件。一个视图也可以作为其他视图的父视图,同时决定着这些子视图的位置和大小。UIView类做了大量的工作去管理这些内部视图的关系,但是需要的时候你也可以定制默认的行为。
10 |
11 | 视图与Core Animation层联合起来处理着视图内容的解释和动画过渡。每个UIKit框架里的视图都被一个层对象支持(通常是一个CALayer类的实例),它管理管理着后台的视图存储和处理视图相关的动画。然而,当你需要对视图的解释和动画行为有更多的控制权时,你可以使用层。
12 |
13 | 为了理解视图和层之间的关系,我们可以借助于一些例子。图1-1显示了ViewTransitions样例程序的视图层次及其对底层Core Animation层的关系。应用中的视图包括了一个window(同时也是一个视图),一个通用的表现得像一个容器视图的UIView对象,一个图像视图,一个控制显示用的工具条,和一个工具条按钮(它本身不是一个视图但是在内部管理着一个视图)。(注意这个应用包含了一个额外的图像视图,它是用来实现动画的)。为了简化,同时因为这个视图通常是被隐藏的,所以没把它包含在下面的图中。每个视图都有一个相应的层对象,它可以通过视图礶r属性被访问。(因为工具条按钮不是一个视图,你不能直接访问它的层对象。)在它们的层对象之后是Core Animation的解释对象,最后是用来管理屏幕上的位的硬件缓存。
14 |
15 | 
16 |
17 |
18 |
19 |
20 |
21 |
22 | 使用Core Animation的层对象有很重要的性能意义。一个视图对象的绘制代码需要尽量的少被调用,当它被调用时,其绘制结果会被Core Animation缓存起来并在往后可以被尽可能的重用。重用已经解释过的内容消除了通常需要更新视图的开销昂贵的绘制周期。内容的重用在动画中特别重要,我们可以使用已有的内容,这样比创建新的内容开销更小。
23 |
24 |
25 | 视图层次和子视图管理
26 |
27 | 除了提供自己的内容之外,一个视图也可以表现得像一个容器。当一个视图包含其他视图时,就在两个视图之间创建了一个父子关系。在这个关系中孩子视图被当作子视图,父视图被当作超视图。创建这样一个关系对应用的可视化和行为都有重要的意义。
28 |
29 | 在视觉上,子视图隐藏了父视图的内容。如果子视图是完全不透明的,那么子视图所占据的区域就完全的隐藏了父视图的相应区域。如果子视图是部分透明的,那么两个视图在显示在屏幕上之前就混合在一起了。每个父视图都用一个有序的数组存储着它的子视图,存储的顺序会影响到每个子视图的显示效果。如果两个兄弟子视图重叠在一起,后来被加入的那个(或者说是排在子视图数组后面的那个)出现在另一个上面。
30 |
31 | 父子视图关系也影响着一些视图行为。改变父视图的尺寸会连带着改变子视图的尺寸和位置。在这种情况下,你可以通过合适的配置视图来重定义子视图的尺寸。其他会影响到子视图的改变包括隐藏父视图,改变父视图的alpha值,或者转换父视图。
32 |
33 | 视图层次的安排也会决定着应用如何去响应事件。在一个具体的视图内部发生的触摸事件通常会被直接发送到该视图去处理。然而,如果该视图没有处理,它会将该事件传递给它的父视图,在响应者链中以此类推。具体视图可能也会传递事件给一个干预响应者对象,像视图控制器。如果没有对象处理这个事件,它最终会到达应用对象,此时通常就被丢弃了。
34 |
35 |
36 |
37 |
38 | 视图绘制周期
39 |
40 | UIView类使用一个点播绘制模型来展示内容。当一个视图第一次出现在屏幕前,系统会要求它绘制自己的内容。在该流程中,系统会创建一个快照,这个快照是出现在屏幕中的视图内容的可见部分。如果你从来没有改变视图的内容,这个视图的绘制代码可能永远不会再被调用。这个快照图像在大部分涉及到视图的操作中被重用。
41 |
42 | 如果你确实改变了视图内容,也不会直接的重新绘制视图内容。相反,使用setNeedsDisplay或者setNeedsDisplayInRect:方法废止该视图,同时让系统在稍候重画内容。系统等待当前运行循环结束,然后开始绘制操作。这个延迟给了你一个机会来废止多个视图,从你的层次中增加或者删除视图,隐藏,重设大小和重定位视图。所有你做的改变会稍候在同一时间反应。
43 |
44 | 注意:改变一个视图的几何结构不会自动引起系统重画内容。视图的contentMode属性决定了改变几何结构应该如果解释。大部分内容模式在视图的边界内拉伸或者重定位了已有快照,它不会重新创建一个新的快照。获取更多关于内容模式如果影响视图的绘制周期,查看 content modes
45 |
46 | 当绘制视图内容的时候到了时,真正的绘制流程会根据视图及其配置改变。系统视图通常会实现私有的绘制方法来解释它们的视图,(那些相同的系统视图经常开发接口,好让你可以用来配置视图的真正表现。)对于定制的UIView子类,你通常可以覆盖drawRect:方法并使用该方法来绘制你的视图内容。也有其他方法来提供视图内容,像直接在底部的层设置内容,但是覆盖drawRect:时最通用的技术。
47 |
48 |
49 | 内容模式
50 |
51 | 视图的内容模式控制着视图如何回收内容来响应视图几何结构的变化,也控制着是否需要回收内容。当一个视图第一次显示时,它通常会解释内容,其结果会被底层的层级树捕获为一张位图。在那之后,改变视图的几何结构不会导致重新创建位图。相反,视图中contentMode属性的值决定着这张位图是否该被拉伸,以适应新的边界或者只是简单的被放到角落或者视图的边界。
52 |
53 | 视图的内容模式在你进行如下操作时被应用:
54 |
55 | 改变视图frame或者bounds矩形的宽度或者高度时。
56 |
57 | 赋值给视图的transform属性,新的转换包括一个放缩因子。
58 |
59 | 大部分视图的contentMode值是UIViewContentModeScaleToFiill,它使视图的内容被放缩到适合新框架的值。Figure 1-2展示了使用其他可用的内容模式的结果。正如你在图中所看到的那样,不是所有的内容模式都可以填充视图的范围,可以的模式可能会扭曲内容。
60 |
61 | 内容模式很好的支持了视图的内容回收,但是当你想视图在放缩和重设尺寸的操作中重绘你也可以用UIViewContentModeRedraw内容模式。设置这个值绘强制系统调用视图的drawRect:方法来响应几何结构的变化。通常来讲,你应该尽可能的避免使用这个模式,同时你不应该在标准的系统视图中使用这个模式。
62 |
63 | 获取更多骨干与可用的内容模式,查看UIView Class Reference
64 |
65 | 
66 |
67 |
68 | 拉伸视图
69 |
70 | 你可以指定视图的某部分为可拉伸的,以便当视图的尺寸改变时只有可拉伸的部分被影响到。可拉伸的部分通常给按钮或者其他的部分为重复模式的视图。由你指定的可拉伸区域允许沿着两条或者其中一条轴拉伸。当然,当一个视图沿着两条轴拉伸的时候,视图的边界必须也定义了一个重复的模式来避免任何的扭曲。Figure1-3展示了这种扭曲在视图里是怎么表现自己的。每个视图里的原始像素的颜色都自我复制,以便可以填充更大视图的相应区域。
71 |
72 | 
73 |
74 |
75 |
76 | 你可以用contentStretch属性来定义一个视图的可拉伸区域。这个属性的值一个边的值被标准化为0.0到1.0之间的矩形。当拉伸这个视图时,系统将视图的当前边界值和放缩因子乘以标准值,以便决定哪些像素需要被拉伸。使用标准值可以减轻每次改变视图的边界值都更新contentStretch属性的需要。
77 |
78 | 视图的内容模式也在决定如何视图的可拉伸区域的使用中扮演着重要的角色。只有当内容模式可能绘引起视图内容放缩的时候可拉伸区域才会被使用。这意味这你的可拉伸视图只被UIViewContentModeScaleToFill, UIViewContentModeScaleAspectFit和UIViewContentModeScaleAspectFill内容模式。如果你指定了一个将内容弹到边界或者角落的内容模式(这样就没有真正的放缩内容),这个视图会忽视可拉伸区域。
79 |
80 | 注意:当需要创建一个可拉伸UIImage对象作为视图的背景时,使用contentStretch属性是推荐的。可拉伸视图完全被Core Animation层处理,这样性能通常更好。
81 |
82 |
83 |
84 | 嵌入式动画支持
85 |
86 | 使用层对象来支持视图的其中一个利益是你可以轻松的用动画处理视图相关的改变。动画是与用户进行信息交流的一个有用的方法,而且应该总是在进行应用设计的过程中考虑使用动画。UIView类的很多属性是动画化的-也就是,可以半自动的从一个值动画的变化到另一个值。为了实现这样一个动画,你需要做的只是:
87 | 1 告诉UIKit你想要实现一个动画
88 | 2 改变这个属性的值
89 | 在一个UIView对象中有以下的动画化属性:
90 | frame - 你可以使用这个来动画的改变视图的尺寸和位置
91 | bounds - 使用这个可以动画的改变视图的尺寸
92 | center - 使用这个可以动画的改变视图的位置
93 | transform - 使用这个可以翻转或者放缩视图
94 | alpha - 使用这个可以改变视图的透明度
95 | backgroundColor - 使用这个可以改变视图的背景颜色
96 | contentStretch - 使用这个可以改变视图内容如何拉伸
97 |
98 | 动画的一个很重要的地方是用于从一组视图到另一组视图的过渡。通常来说,会用一个视图控制器来管理关系到用户界面的主要变更的动画。例如,涉及到从高层到底层信息的导航的界面,通常会使用一个导航控制器来管理视图的过渡,这些视图显示了数据的每一个连续层面。然而,你也可以使用动画来创建两组视图的过渡,而不是视图控制器。当你想用一个系统提供的视图控制器无法支持的导航方案时你可能会这样做。
99 |
100 | 除了用UIKit类可以创建动画外,你也可以用Core Animation层来创建动画。在更低层你有更多的在时间或者动画属性上的控制权。
101 |
102 | 获取更多关于如何创建一个基于视图的动画,查看 Animations
103 | 获取更多关于使用Core Animation创建动画的信息,查看Core Animation Programming Guide和Core Animation Cookbook.
104 |
105 |
106 | 视图几何结构和坐标系统
107 |
108 | UIKit的默认坐标系统把原点设置在左上角,两条轴往下和右扩展。做标志被表示为浮点数,这样允许内容的精确布局和定位而不管底层的屏幕。Figure1-4展示了相对于屏幕的坐标系统。除了屏幕坐标系统窗口和视图也定义了它们自己的本地坐标系统,这样允许你指定相对于视图或者窗口原点的坐标而不是屏幕。
109 |
110 | 
111 |
112 |
113 |
114 | 因为每个视图和窗口都定义了它自己的本地坐标系统,你需要留意在任何时间内是哪个坐标系统在起作用。每次绘制或者改变一个视图都是基于一个坐标系统的。在某些绘制中会基于视图本身的坐标系统。在某些几何结构变更中是基于父视图的坐标系统的。UIWindow和UIView类都包含了帮助你从一个坐标系统转换到另一个的方法。
115 |
116 | 重要:一些iOS技术定义了默认的坐标系统,它们的原点和方向与UIKit的不同。;例如,Core Graphics和OpenGL ES的坐标系统是原点在可视区域的左下角,而y轴往上递增。当绘制或者创建内容时,你的代码应该考虑到一些不同并且适应坐标值。
117 |
118 |
119 |
120 | frame, bounds和center属性之间的关系
121 |
122 | 视图对象使用frame, bounds和center属性来跟踪它的尺寸和位置:
123 | frame属性包含了frame矩形,指定了在父视图坐标系统中该视图的尺寸和位置。
124 | center属性包含了在父视图坐标系统中的已知中心点。
125 | bounds属性包含了边界矩形,指定了在视图本地坐标系统中视图的尺寸。
126 | 主要使用center和frame属性来控制当前视图的几何结构。例如,当在运行时构建你的视图层次或者改变视图的尺寸或者位置时你可以使用这些属性。如果你只是要改变视图的位置,那么推荐使用center属性。center属性的值永远是可用的,即使添加了放缩或者转换因子到视图的转换矩阵当中。但是对于frame属性却不是,当视图的转换矩形不等于原始矩阵时它被当作时无效的。
127 |
128 | 在绘制的过程中主要使用bounds属性。这个边界矩阵在视图的本地坐标系统被解释。这个矩形的默认原点是(0, 0),它的尺寸也适应frame矩形的尺寸。任何绘制在这个矩形当中的东西都是该视图的可视内容的一部分。如果你改变了bounds矩形的原点,任何你绘制在新矩形的东西都会变成该视图可视内容的一部分。
129 |
130 | Figure1-5展示了一个图像视图的frame和bounds矩形之间的关系。图中,图像视图的右上角被定位在父视图坐标系统的(40, 40),它的矩形尺寸为240x380。对于bounds矩形,原点是(0, 0),矩形尺寸也是240x380。
131 |
132 | 
133 |
134 |
135 | 即使你可以独立的改变frame,bounds和center属性,其中一个改变还是会影响到另外两个属性:
136 | 当你设置了frame属性,bounds属性的尺寸值也改变来适应frame矩形的新尺寸。center属性也会改变为新frame矩形的中心值。
137 | 当你设置了center属性,frame的原点也会相应的改变。
138 | 当你设置了bounds属性,frame属性会改变以适应bounds矩形的新尺寸。
139 | 视图的框架默认不会被它的父视图框架裁剪。这样的化,任何放置在父视图外的子视图都会被完整的解释。你可以改变这种行为,改变父视图的clipsToBounds属性就可以。不管子视图是否在视觉上被裁剪,触屏事件总是发生在目标视图父视图的bounds矩形。换句话说,如果触摸位于父视图外的那部分视图,那么该事件不会被发送到该视图。
140 |
141 |
142 |
143 | 坐标系统转换矩阵
144 |
145 | 坐标系统转换矩阵给改变视图(或者是它的视图)提供了一个轻松和简易的方法。一个仿射转换是一个数学矩阵,它指定了在坐标系统中的点是怎么被映射到另一个坐标系统中的点。你可以对整个视图应用仿射转换,以基于其父视图来改变视图的尺寸,位置或者朝向。你也可以在你的绘制代码中应用仿射转换,以对已解释内容的独立部分实现相同的操控。如何应用仿射转换是基于这样的上下文的:
146 | 为了修改整个视图,可以修改视图transform属性的仿射转换值。
147 |
148 | 为了在视图中的drawRect:方法中修改内容的指定部分,可以修改与当前图形上下文相关的仿射转换。
149 |
150 | 当你想实现动画时,通常可以修改视图的transform属性值。例如,你可以使用这个属性来制作一个视图围绕中心点翻转的动画。你不应该在其父视图的坐标空间中用这个属性来永久的改变你的视图,像修改它的位置和尺寸。对于这种类型的改变,你可以修改视图的frame矩形。
151 |
152 | 注意:当修改视图的transform属性值时,所有的转换都是基于视图的中心点来实现的。
153 |
154 | 在视图的drawRect:方法中,你可以使用仿射转换来定位或者翻转你想要绘制的项目。相对于在视图某些部位中修正对象的位置,我们更倾向于相对于一个固定点去创建对象,通常是(0, 0),同时在绘制之前使用转换来定位对象。这样的话,如果在视图中对象的位置改变了,你要做的只是修改转换矩阵,这样比为对象重新创建新的位置性能更好开销更低。你可以通过使用CGContextGetCTM方法来获取关于图形上下文的仿射转换,同时可以用Core Graphics的相关方法在绘制中来设置或者修改这个转换矩阵。
155 |
156 | 当前转换矩阵(CTM)是一个在任何时候都被使用的仿射矩阵。当操控整个视图的几何结构时,CTM就是视图transform属性的值。在drawRect:方法中,CTM是关于图形上下文的仿射矩阵。
157 |
158 | 每个子视图的坐标系统都是构建在其祖先的坐标系统之上的。所以当你修改一个视图的transform属性,这个改变会影响到视图及其所有的子视图。然而,这些改变只会影响到屏幕上视图的最终解释。因为每个视图都负责绘制自己的内容和对自己的子视图进行布局,所以在绘制和布局的过程中它可以忽略父视图的转换。
159 |
160 | Figure1- 6描述了在解释的时候,两个不同的转换因子是如何在视觉上组合起来的。在视图的drawRect:方法中,对一个形状应用一个45度的转换因子会使该形状翻转指定的角度。另外加上一个45度的转换因子会导致整个形状翻转90度。这个形状对于绘制它的视图来讲仍然只是翻转了45度,但是视图自己的转换让它看起来像使翻转了90度。
161 |
162 | Figure 1-6 翻转一个视图和它的内容
163 | 
164 |
165 |
166 | 重要:如果一个视图的transform属性不是其定义时转换矩阵,那么视图的frame属性是未定义的而且必须被忽略。当对视图应用转换时,你必须使用视图的bounds和center属性来获取视图的位置和尺寸。子视图的frame矩形仍然是有效的,因为它们与视图的bounds相关。
167 |
168 | 获取更多关于在运行时修改视图的transform属性,查看 “Translating, Scaling, and Rotating Views.”获取更多如何在绘制过程中使用转换来定位内容,查看 Drawing and Printing Guide for iOS.
169 |
170 |
171 |
172 | 点与像素
173 |
174 | 在iOS中,所有的坐标值和距离都被指定为使用浮点数,其单元值称为点。点的数量随着设备的不同而不同,而且彼此不相关。要明白关于点的最主要一点是它们提供了一个绘制用的固定框架。
175 |
176 | Table 1-1 列出了不同iOS设备的分辨率(点度量)。前为宽后为长。只要你依照这些屏幕的尺寸来设计用户界面,你的视图就回被相应的设备正确显示。
177 |
178 | Table 1-1
179 |
180 |
181 | Device|Screen dimensions (in points)
182 | ------|-------
183 | iPhone and iPod touch|320 x 480
184 | iPad | 768 x 1024
185 |
186 |
187 | 每一种使用基于点度量系统的设备都定义了一个用户坐标空间。这是几乎在你所有的代码都会用到的标准坐标空间。例如,当你要操控视图的几何结构或者调用Core Graphics方法来绘制内容时会用到点和用户坐标空间。即使有时用户坐标空间里的坐标时直接映射到设备屏幕的像素,你还是永远不应该假设这是永远不变的。相反,你应该记住:
188 | 一个点并不一定对应着屏幕上的一个像素
189 | 在设备层面,所有由你指定的视图上的坐标在某些点上必须被转化成像素。然而,从用户坐标空间上的点到设备坐标空间上的像素通常由系统来处理。UIKit和Core Graphics都主要使用基于向量的绘制模型,所有的坐标值都被指定为使用点。这样,如果你用Core Graphics画了一条曲线,你会用一些值来指定这条曲线,而不管底层屏幕使用怎样的解决方法。
190 |
191 | 当你需要处理图像或者其他基于像素的技术,像OpenGL ES时,iOS会帮你管理这些像素。对于存储为应用程序的束中的资源的静态图像文件,iOS定义了一些约定,可以指定不同像素密度的图像,也可以在加载图像时最大限度的适应当前屏幕的解决方案。视图也提供了关于当前放缩因子的信息,以便你可以适当的调整任何基于像素的绘制代码来适应有更高级解决方案的屏幕。在不同屏幕的解决方案中处理基于像素内容的技术可以在"Supporting High-Resolution Screens"和"Drawing and Printing Guide for iOS"找到描述。
192 |
193 |
194 |
195 | 视图的运行时交互模型
196 |
197 | 当用户和界面进行交互时,或者由代码程序性的改变一些东西时,一系列复杂的事件就会发生在UIKit的内部来处理这些交互。在这个系列中的某些点,UIKit唤出你的视图类,同时给它们一个机会去响应程序的行为。理解这些唤出点对于理解视图在哪里融入系统很重要。Figure 1-7 展示了这些事件的基本序列,从用户触屏开始到图形系统更新屏幕内容来响应结束。同样的事件序列也会发生在任何程序性启动的动作。
198 |
199 | 
200 |
201 |
202 | 以下的步骤分解了图1-7中的事件序列,既解释了在每一步发生了什么,也解释了应用如何响应
203 | 1 用户触屏
204 | 2 硬件报告触摸事件给UIKit框架
205 | 3 UIKit框架将触摸事件打包成UIEvent对象,同时分发给适合的视图。(对于UIKit框架如何提交事件给视图的详细解释,查看 Event Handing Guide for iOS)
206 | 4 视图中的事件处理代码可能进行以下的动作来响应:
207 | 改变视图或者其子视图的属性(frame, bounds, alpha, 等等)
208 | 调用setNeedsLayout方法以标记该视图(或者它的子视图)为需要进行布局更新
209 | 调用setNeedsDisplay或者setNeedsDisplayInRect:方法以标记该视图(或者它的子视图)需要进行重画
210 | 通知一个控制器关于一些数据的更新
211 | 当然,哪些事情要做,哪些方法要被调用是由视图来决定的。
212 | 5 如果一个视图的几何结构改变了,UIKit会根据以下几条规则来更新它的子视图:
213 | a 如果自动重设尺寸的规则在发生作用,UIKit会根据这些规则来调整视图。获取更多关于自动重设尺寸规则如何工作,查看"Handling Layout Changes Automatically Using Autoresizing Rules."
214 | b 如果视图实现了layoutSubviews方法,UIKit会调用它。你可以在你的定制视图中覆盖这个方法同时用它来调整任何子视图的位置和大小。例如,一个提供了巨大滚动区域的视图会需要使用几个子视图作为“瓦块”而不是创建一个不太可能放进内存的巨大视图。在这个方法的实现中,视图会隐藏任何屏幕外的子视图,或者重定位它们然后用来绘制新的可视内容。作为这个流程的一部分,视图的布局代码也可以废止任何需要被重画的视图。
215 | 6 如果任何视图的任何部分被标记为需要重画,UIKit会要求视图重画自身。
216 | 对于显式的定义了drawRect:方法的定制视图,UIKit会调用这个方法。这方法的实现应该尽快重画视图的指定区域,并且不应该再做其他事。不要在这个点上做额外的布局,也不要改变应用的数据模型。提供这个方法仅仅是为了更新视图的可视内容。
217 | 标准的系统视图通常不会实现drawRect:方法,但是也会在这个时候管理它们的绘制。
218 |
219 | 7 任何已经更新的视图会与应用余下的可视内容组合在一起,同时被发送到图形硬件去显示。
220 | 8 图形硬件将已解释内容转化到屏幕上。
221 |
222 | 注意:上面的更新模型主要应用于使用标准系统视图和绘制技术的应用。使用OpenGL ES来绘制的应用通常会配置一个单一的全屏视图和直接绘制相关的OpenGL图像上下文。你的视图还是应该处理触屏事件,但是它是全屏的,毋需给子视图布局或者实现drawRect:方法。获取更多关于使用OpenGL ES的信息,查看 OpenGL ES Programming Guide for iOS.
223 |
224 | 给定之前的一系列步骤,将自己的定制视图整合进去的方法包括:
225 | 事件处理方法:
226 | touchesBegan:withEvent:
227 | touchesMoved:withEvent:
228 | touchesEnded:withEvent:
229 | touchesCancelled:withEvent:
230 | layoutSubviews方法
231 | drawRect:方法
232 | 这些是视图的最常用的覆盖方法,但是你可能不需要覆盖全部。如果你使用手势识别来处理事件,你不需要覆盖事件处理方法。相似的,如果你的视图没有包含子视图或者它的尺寸不会改变,那就没有理由去覆盖layoutSubviews方法。最后,只有当视图内容会在运行时改变,同时你要用UIKit或者Core Graphics等本地技术来绘制时才需要用到drawRect。
233 |
234 | 要记住这些是主要的整合点,但是不仅仅只有这些。UIView类中有些方法是专门设计来给子类覆盖的。你应该到UIView Class Reference中查看这些方法的描述,以便在定制时清楚哪些方法适合给你覆盖。
235 |
236 |
237 |
238 | 有效使用视图的提示
239 |
240 | 当你需要绘制一些标准系统视图不能提供的内容时,定制视图是很有用的。但是你要负责保证视图的性能要足够的高。UIKit会尽可能的优化视图相关的行为,也会帮助你提高性能。然而,考虑一些提示可以帮助到UIKit。
241 |
242 | 重要:在调整绘制代码之前,你应该一直收集与你视图当前性能有关的数据。估量当前性能让你可以确定是否真的有问题,同时如果真的有问题,它也提供一个基线,让你在未来的优化中可以比较。
243 |
244 |
245 | 视图不会总是有一个相应的视图控制器
246 |
247 | 在应用中,视图和视图控制器之间的一对一关系是很少见的。视图控制器的工作是管理一个视图层次,而视图层次经常是包含了多个视图,它们都有自包含特性。对于iPhone应用,每个视图层次通常都填满了整个屏幕,尽管对于iPad应用来说不是。
248 |
249 | 当你设计用户界面的时候,考虑到视图控制器的所扮演的角色是很重要的。视图控制器提供了很多重要的行为,像协调视图的展示,协调视图的剔除,释放内存以响应低内存警告,还有翻转视图以响应界面的方向变更。逃避这些行为会导致应用发生错误。
250 |
251 | 获取更多关于视图控制器的信息,查看 View Controller Programming Guide for iOS
252 |
253 |
254 | 最小化定制的绘画
255 |
256 | 尽管定制的绘画有时是需要的,但是你也应该尽量避免它。真正需要定制绘画的时候是已有的视图类无法提供足够的表现和能力时。任何时候你的内容都应该可以被组装到其他视图,最好结果时组合那些视图对象到定制的视图层次。
257 |
258 | 利用内容模式
259 |
260 | 内容模式可以最小化重画视图要花费的时间。默认的,视图使用UIViewContentModeScaleToFill 内容模式,这个模式会放缩视图的已有内容来填充视图的frame矩形。需要时你可以改变这个模式来调整你的内容,但是应该避免使用UIViewContentModeRedraw内容模式。不管哪个内容模式发生作用,你都可以调用setNeedsDisplay或者setNeedsDisplayInRect:方法来强制视图重画它的内容。
261 |
262 | 可能的话将视图声明为不透明
263 |
264 | UIKit使用opaque属性来决定它是否可以优化组合操作。将一个定制视图的这个属性设置为YES会告诉UIKit不需要解释任何在该视图后的内容。这样可以为你的绘制代码提高性能并且是推荐的。当然,如果你将这个属性设置为YES,你的视图一定要用不透明的内容完全填充它的bounds矩形。
265 |
266 | 滚动时调整视图的绘制行为
267 |
268 | 滚动会导致数个视图在短时间内更新。如果视图的绘制代码没有被适当的调整,滚动的性能会非常的缓慢。相对于总是保证视图内容的平庸,我们更倾向于考虑滚动操作开始时改变视图行为。例如,你可以暂时减少已解释的内容,或者在滚动的时候改变内容模式。当滚动停止时,你可以将视图返回到前一状态,同时需要时更新内容。
269 |
270 |
271 | 不要嵌入子视图来定制控制
272 |
273 | 尽管在技术上增加子视图到标准系统控制对象-继承自UIControl的类-是可行的,你还是永远不应该用这种方法来定制它们。控制对象支持定制,它们有显式并且良好归档的接口。例如,UIButton类包含了设置标题和背景图片的方法。使用已定义好的定制点意味着你的代码总是会正确的工作。不用这些方法,而嵌入一个定制的图像视图或者标签到按钮中去会导致应用出现未预期的结果。
274 |
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/classes.md:
--------------------------------------------------------------------------------
1 | UIView类定义了视图的基本行为,但并不定义其视觉表示。相反,UIKit通过其子类来为像文本框、按键、及工具条这样的标准界面元素定义具体的外观和行为。图2-1显示了所有UIKit视图类的层次框图。除了UIView和UIControl类是例外,这个框图中的大多数视图都设计为可直接使用,或者和委托对象结合使用。
2 |
3 | 图2-1 视图的类层次
4 | 
5 |
6 |
7 | 这个视图层次可以分为如下几个大类:
8 |
9 | 容器
10 |
11 | 容器视图用于增强其它视图的功能,或者为视图内容提供额外的视觉分隔。比如,UIScrollView类可以用于显示因内容太大而无法显示在一个屏幕上的视图。UITableView类是UIScrollView类的子类,用于管理数据列表。表格的行可以支持选择,所以通常也用于层次数据的导航—比如用于挖掘一组有层次结构的对象。
12 |
13 | UIToolbar对象则是一个特殊类型的容器,用于为一或多个类似于按键的项提供视觉分组。工具条通常出现在屏幕的底部。Safari、Mail、和Photos程序都使用工具条来显示一些按键,这些按键代表经常使用的命令。工具条可以一直显示,也可以根据应用程序的需要进行显示。
14 |
15 | 控件
16 |
17 | 控件用于创建大多数应用程序的用户界面。控件是一种特殊类型的视图,继承自UIControl超类,通常用于显示一个具体的值,并处理修改这个值所需要的所有用户交互。控件通常使用标准的系统范式(比如目标-动作模式和委托模式)来通知应用程序发生了用户交互。控件包括按键、文本框、滑块、和切换开关。
18 |
19 | 显示视图
20 |
21 | 控件和很多其它类型的视图都提供了交互行为,而另外一些视图则只是用于简单地显示信息。具有这种行为的UIKit类包括UIImageView、 UILabel、UIProgressView、UIActivityIndicatorView。
22 |
23 | 文本和web视图
24 |
25 | 文本和web视图为应用程序提供更为高级的显示多行文本的方法。UITextView类支持在滚动区域内显示和编辑多行文本;而UIWebView类则提供了显示HTML内容的方法,通过这个类,您可以将图形和高级的文本格式选项集成到应用程序中,并以定制的方式对内容进行布局。
26 |
27 | 警告视图和动作表单
28 |
29 | 警告视图和动作表单用于即刻取得用户的注意。它们向用户显示一条消息,同时还有一或多个可选的按键,用户通过这些按键来响应消息。警告视图和动作表单的功能类似,但是外观和行为不同。举例来说,UIAlertView类在屏幕上弹出一个蓝色的警告框,而UIActionSheet类则从屏幕的底部滑出动作框。
30 |
31 | 导航视图
32 |
33 | 页签条和导航条和视图控制器结合使用,为用户提供从一个屏幕到另一个屏幕的导航工具。在使用时,您通常不必直接创建UITabBar和UINavigationBar的项,而是通过恰当的控制器接口或Interface Builder来对其进行配置。
34 |
35 | 窗口
36 |
37 | 窗口提供一个描画内容的表面,是所有其它视图的根容器。每个应用程序通常都只有一个窗口。更多信息请参见“UIWindow的作用”部分。
38 |
39 | 除了视图之外,UIKit还提供了视图控制器,用于管理这些对象。更多信息请参见“视图控制器的作用”部分。
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/button_scale.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/button_scale.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/drawing_model.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/drawing_model.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/frame_bounds_rects.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/frame_bounds_rects.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/native_coordinate_system.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/native_coordinate_system.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/scale_aspect.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/scale_aspect.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/uikit_classes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/uikit_classes.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/view-layer-store.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/view-layer-store.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/view_classes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/view_classes.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/imgs/xform_rotations.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter0/uikit/imgs/xform_rotations.jpg
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/uiview.md:
--------------------------------------------------------------------------------
1 | ##UIView是作用视图
2 |
3 | 是UIView类的实例,负责在屏幕上定义一个矩形区域。在iPhone的应用程序中,视图在展示用户界面及响应用户界面交互方面发挥关键作用。每个视图对象都要负责渲染视图矩形区域中的内容,并响应该区域中发生的触碰事件。这一双重行为意味着视图是应用程序与用户交互的重要机制。在一个基于模型-视图-控制器的应用程序中,视图对象明显属于视图部分。
4 |
5 | 除了显示内容和处理事件之外,视图还可以用于管理一或多个子视图。子视图是指嵌入到另一视图对象边框内部的视图对象,而被嵌入的视图则被称为父视图或超视图。视图的这种布局方式被称为视图层次,一个视图可以包含任意数量的子视图,通过为子视图添加子视图的方式,视图可以实现任意深度的嵌套。视图在视图层次中的组织方式决定了在屏幕上显示的内容,原因是子视图总是被显示在其父视图的上方;这个组织方法还决定了视图如何响应事件和变化。每个父视图都负责管理其直接的子视图,即根据需要调整它们的位置和尺寸,以及响应它们没有处理的事件。
6 |
7 | 由于视图对象是应用程序和用户交互的主要途径,所以需要在很多方面发挥作用,下面是其中的一小部分:
8 |
9 | 描画和动画
10 |
11 | 视图负责对其所属的矩形区域进行描画。
12 |
13 | 某些视图属性变量可以以动画的形式过渡到新的值。
14 |
15 | 布局和子视图管理
16 |
17 | 视图管理着一个子视图列表。
18 |
19 | 视图定义了自身相对于其父视图的尺寸调整行为。
20 |
21 | 必要时,视图可以通过代码调整其子视图的尺寸和位置。
22 |
23 | 视图可以将其坐标系统下的点转换为其它视图或窗口坐标系统下的点。
24 |
25 | 事件处理
26 |
27 | 视图可以接收触摸事件。
28 |
29 | 视图是响应者链的参与者。
30 |
31 | 在iPhone应用程序中,视图和视图控制器紧密协作,管理若干方面的视图行为。视图控制器的作用是处理视图的装载与卸载、处理由于设备旋转导致的界面旋转,以及和用于构建复杂用户界面的高级导航对象进行交互。更多这方面的信息请参见“视图控制器的作用”部分。
32 |
33 | 本章的大部分内容都着眼于解释视图的这些作用,以及说明如何将您自己的定制代码关联到现有的UIView行为中。
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/viewcontroller.md:
--------------------------------------------------------------------------------
1 | ##视图控制器的作用
2 |
3 | 运行在iPhone OS上的应用程序在如何组织内容和如何将内容呈现给用户方面有很多选择。含有很多内容的应用程序可以将内容分为多个屏幕。在运行时,每个屏幕的背后都是一组视图对象,负责显示该屏幕的数据。一个屏幕的视图后面是一个视图控制器其作用是管理那些视图上显示的数据,并协调它们和应用程序其它部分的关系。
4 |
5 | UIViewController类负责创建其管理的视图及在低内存时将它们从内容中移出。视图控制器还为某些标准的系统行为提供自动响应。比如,在响应设备方向变化时,如果应用程序支持该方向,视图控制器可以对其管理的视图进行尺寸调整,使其适应新的方向。您也可以通过视图控制器来将新的视图以模式框的方式显示在当前视图的上方。
6 |
7 | 除了基础的UIViewController类之外,UIKit还包含很多高级子类,用于处理平台共有的某些高级接口。特别需要提到的是,导航控制器用于显示多屏具有一定层次结构的内容;而页签条控制器则支持用户在一组不同的屏幕之间切换,每个屏幕都代表应用程序的一种不同的操作模式。
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/window.md:
--------------------------------------------------------------------------------
1 | ##UIWindow的作用
2 |
3 | 和Mac OS X的应用程序有所不同,iPhone应用程序通常只有一个窗口,表示为一个UIWindow类的实例。您的应用程序在启动时创建这个窗口(或者从nib文件进行装载),并往窗口中加入一或多个视图,然后将它显示出来。窗口显示出来之后,您很少需要再次引用它。
4 |
5 | 在iPhone OS中,窗口对象并没有像关闭框或标题栏这样的视觉装饰,用户不能直接对其进行关闭或其它操作。所有对窗口的操作都需要通过其编程接口来实现。应用程序可以借助窗口对象来进行事件传递。窗口对象会持续跟踪当前的第一响应者对象,并在UIApplication对象提出请求时将事件传递它。
6 |
7 | 还有一件可能让有经验的Mac OS X开发者觉得奇怪的事是UIWindow类的继承关系。在Mac OS X中,NSWindow的父类是NSResponder;而在iPhone OS中,UIWindow的父类是UIView。因此,窗口在iPhone OS中也是一个视图对象。不管其起源如何,您通常可以将iPhone OS上的窗口和Mac OS X的窗口同样对待。也就是说,您通常不必直接操作UIWindow对象中与视图有关的属性变量。
8 |
9 | 在创建应用程序窗口时,您应该总是将其初始的边框尺寸设置为整个屏幕的大小。如果您的窗口是从nib文件装载得到,Interface Builder并不允许创建比屏幕尺寸小的窗口;然而,如果您的窗口是通过编程方式创建的,则必须在创建时传入期望的边框矩形。除了屏幕矩形之外,没有理由传入其它边框矩形。屏幕矩形可以通过UIScreen对象来取得,具体代码如下所示:
10 |
11 | UIWindow* aWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
12 |
13 | 虽然iPhone OS支持将一个窗口叠放在其它窗口的上方,但是您的应用程序永远不应创建多个窗口。系统自身使用额外的窗口来显示系统状态条、重要的警告、以及位于应用程序窗口上方的其它消息。如果您希望在自己的内容上方显示警告,可以使用UIKit提供的警告视图,而不应创建额外的窗口。
--------------------------------------------------------------------------------
/articles/Chapter0/uikit/windowAview.md:
--------------------------------------------------------------------------------
1 | ##窗口和视图
2 |
3 | 窗口和视图是为iPhone应用程序构造用户界面的可视组件。窗口为内容显示提供背景平台,而视图负责绝大部分的内容描画,并负责响应用户的交互。虽然本章讨论的概念和窗口及视图都相关联,但是讨论过程更加关注视图,因为视图对系统更为重要。
4 |
5 | 视图对iPhone应用程序是如此的重要,以至于在一个章节中讨论视图的所有方面是不可能的。本章将关注窗口和视图的基本属性、各个属性之间的关系、以及在应用程序中如何创建和操作这些属性。本章不讨论视图如何响应触摸事件或如何描画定制内容,有关那些主题的更多信息,请分别参见“事件处理”和“图形和描画”部分。
6 |
7 |
8 | 什么是窗口和视图?
9 |
10 | 和Mac OS X一样,iPhone OS通过窗口和视图在屏幕上展现图形内容。虽然窗口和视图对象之间在两个平台上有很多相似性,但是具体到每个平台上,它们的作用都有轻微的差别。
11 | ### 0. [视图和窗口架构](./architecture.md)
12 | ### 1. [UIWindow的作用](./window.md)
13 | ### 2. [UIView的作用](./uiview.md)
14 | ### 3. [UIKit视图类](./classes.md)
15 | ### 4. [试图控制器](./viewcontroller.md)
16 |
--------------------------------------------------------------------------------
/articles/Chapter1/cell.md:
--------------------------------------------------------------------------------
1 | # 在DZTableViewCell上扩展功能
2 | ## 0、[Cell结构设计](cellfunctions/gemotry.md)
3 | ## 1、[选中态](cellfunctions/selected.md)
4 | ## 2、[手势与功能](cellfunctions/actions.md)
5 | ## 3、[子类化扩展](cellfunctions/subclass.md)
6 |
--------------------------------------------------------------------------------
/articles/Chapter1/cellfunctions/actions.md:
--------------------------------------------------------------------------------
1 | #手势与功能
--------------------------------------------------------------------------------
/articles/Chapter1/cellfunctions/gemotry.md:
--------------------------------------------------------------------------------
1 | #Cell结构设计
--------------------------------------------------------------------------------
/articles/Chapter1/cellfunctions/selected.md:
--------------------------------------------------------------------------------
1 | # 选中态
2 |
3 | 这个应该是所有View的一个基础功能,在很多基于UIView的空间上我们都能看到```setHeightlight```或者```setSelected```之类的函数,用来在用户选中该空间的时候,给用户一个反馈。DZTableViewCell的是```setSelected```。关于选中态主要有两部分的事情,一是选中时机,二是如何表现选中态。
4 |
5 | #### 选中态的判断
6 |
7 | 选中太的判断主要是依靠触摸事件来判断,当用户触摸到cell的时候表示选中,用户手指离开的时候为不选中。于是我们通过重载UIView的一些列触摸事件的响应函数就能够做到对选中态的判断。
8 |
9 | ```
10 | - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
11 | {
12 | [super touchesBegan:touches withEvent:event];
13 | [self setIsSelected:YES];
14 | }
15 |
16 | - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
17 | {
18 | [super touchesEnded:touches withEvent:event];
19 | [self setIsSelected:NO];
20 | }
21 |
22 | - (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
23 | {
24 | [super touchesCancelled:touches withEvent:event];
25 | [self setIsSelected:NO];
26 | }
27 | ```
28 |
29 | #### 选中态的展示
30 | 回归一下刚开始的时候说到的,我们整个DZTableView的UIView数层次。一个Cell的最底层是一个_selectedBackgroudView。这个就是用来展示选中态的。当Cell的选中态改变的时候,我们只要重新布局一下_selectedBackgroudView就可以了。
31 |
32 | ```
33 | - (void) setIsSelected:(BOOL)isSelected
34 | {
35 | if (_isSelected != isSelected) {
36 | _isSelected = isSelected;
37 | [self setNeedsLayout];
38 | }
39 | }
40 | - (void) layoutSubviews
41 | {
42 | ....
43 | if (_isSelected) {
44 | _selectedBackgroudView.frame = _contentView.bounds;
45 | _selectedBackgroudView.hidden = NO;
46 | [_contentView insertSubview:_selectedBackgroudView atIndex:0];
47 | }
48 | else
49 | {
50 | _selectedBackgroudView.hidden = YES;
51 | }
52 | ....
53 | ```
54 |
--------------------------------------------------------------------------------
/articles/Chapter1/cellfunctions/subclass.md:
--------------------------------------------------------------------------------
1 | #子类化扩展
--------------------------------------------------------------------------------
/articles/Chapter1/event.md:
--------------------------------------------------------------------------------
1 | ## 响应和处理事件
2 |
3 | 前面说过一个tableView应该是可交互的,而主要的交互就是能够确认用户点击了哪一个cell。
4 |
5 | ```
6 | - (void) addTapTarget:(id)target selector:(SEL)selecotr
7 | {
8 | self.userInteractionEnabled = YES;
9 | UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:target action:selecotr];
10 | tapGesture.numberOfTapsRequired = 1;
11 | tapGesture.numberOfTouchesRequired = 1;
12 | [self addGestureRecognizer:tapGesture];
13 | }
14 | ...
15 | [self addTapTarget:self selector:@selector(handleTapGestrue:)];
16 | ...
17 | - (void) handleTapGestrue:(UITapGestureRecognizer*)tapGestrue
18 | {
19 | CGPoint point = [tapGestrue locationInView:self];
20 | NSArray* cells = _visibleCellsMap.allValues;
21 | for (DZTableViewCell* each in cells) {
22 | CGRect rect = each.frame;
23 | if (CGRectContainsPoint(rect, point)) {
24 | if ([_actionDelegate respondsToSelector:@selector(dzTableView:didTapAtRow:)]) {
25 | [_actionDelegate dzTableView:self didTapAtRow:each.index];
26 | }
27 | each.isSelected = YES;
28 | _selectedIndex = each.index;
29 | }
30 | else
31 | {
32 | each.isSelected = NO;
33 | }
34 | }
35 | }
36 | ```
37 | 我们在tableview上面加了一个单机的事件UITapGestureRecognizer。然后再相应处处理了一下。主要是获取了用户点击位置,然后找到点击位置上的cell。这样就确认了用户点了哪个cell,在把这个信息传出去就好了。
38 |
--------------------------------------------------------------------------------
/articles/Chapter1/gemotry.md:
--------------------------------------------------------------------------------
1 | #解释一下整个UI的层次架构
2 | 下面这张图大概说明了整个DZTableView的View的结构树。
3 | 
4 |
5 | 整个的TableView分成两个主要的组成部分:DZTableView和DZTableViewCell。这个结构和UITableView的结构是类似的。
6 |
7 | DZTableView是tableView的主体部分,主要负责整个tableview的布局和渲染。而DZTableViewCell则是被布局和渲染的对象。DZTableView只是实现了y轴上纵向布局的tableView,没有分组。而我们通常看到的很多很炫的右滑删除等效果则是在DZTableViewCell上扩展得来的。
8 |
9 | DZTableViewCell最基础的类主要有三个层次:
10 |
11 | 1. 负责渲染转中状态的selectedBackgroudView
12 | 2. 负责渲染和控制滑动效果的actionsView,actionsView上面各种功能的对象是DZCellActionItem
13 | 3. 负责渲染Cell主体内容的contentVIew。
14 |
15 |
16 | 而完成一个TableView主要的工作就是在UISCrollView上对cell进行合理的布局。
--------------------------------------------------------------------------------
/articles/Chapter1/images/TableView datasource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter1/images/TableView datasource.png
--------------------------------------------------------------------------------
/articles/Chapter1/images/aim.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter1/images/aim.jpeg
--------------------------------------------------------------------------------
/articles/Chapter1/images/flightWeight:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter1/images/flightWeight
--------------------------------------------------------------------------------
/articles/Chapter1/images/tableView_tree_3d.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter1/images/tableView_tree_3d.jpg
--------------------------------------------------------------------------------
/articles/Chapter1/interface.md:
--------------------------------------------------------------------------------
1 | ## 接口和数据获取
2 | 通过上面的阐述我们已经把DZTableView的框架搭起来了,实现了一个TableView的布局方式,还有cell的重用。但是还有一个非常关键的问题,tableView布局信息的数据怎么来,还有我们应该向外给提供者调用什么样的接口。
3 |
4 | 这个问题,貌似苹果已经做得很好了。而DZTableView要做的就是尽可能的让接口和苹果的保持一致,这样对于使用者而言,没有太大的学习成本。
5 |
6 | ### 数据获取
7 |
8 | ```
9 | @class DZTableView;
10 | @class DZTableViewCell;
11 | @class DZPullDownView;
12 | @protocol DZTableViewSourceDelegate
13 | - (NSInteger) numberOfRowsInDZTableView:(DZTableView*)tableView;
14 | - (DZTableViewCell*) dzTableView:(DZTableView*)tableView cellAtRow:(NSInteger)row;
15 | - (CGFloat) dzTableView:(DZTableView*)tableView cellHeightAtRow:(NSInteger)row;
16 | @end
17 | ```
18 |
19 | ### 点击等事件响应
20 |
21 | ```
22 | @class DZTableView;
23 | @class DZTableViewCell;
24 | @protocol DZTableViewActionDelegate
25 |
26 | - (void) dzTableView:(DZTableView*)tableView didTapAtRow:(NSInteger)row;
27 | - (void) dzTableView:(DZTableView *)tableView deleteCellAtRow:(NSInteger)row;
28 | - (void) dzTableView:(DZTableView *)tableView editCellDataAtRow:(NSInteger)row;
29 |
30 | @end
31 |
32 | ```
33 | ### DZTableView的成员方法
34 |
35 | ```
36 | - (DZTableViewCell*) dequeueDZTalbeViewCellForIdentifiy:(NSString*)identifiy;
37 | - (void) reloadData;
38 | - (void) insertRowAt:(NSSet *)rowsSet withAnimation:(BOOL)animation;
39 | - (void) removeRowAt:(NSInteger)row withAnimation:(BOOL)animation;
40 |
41 | - (void) manuSelectedRowAt:(NSInteger)row;
42 | ```
43 |
--------------------------------------------------------------------------------
/articles/Chapter1/shareCell.md:
--------------------------------------------------------------------------------
1 |
2 | ## Cell的重用
3 |
4 | 
5 | 在使用UITableView的时候我们应该熟悉这样的接口:
6 |
7 | ```
8 | - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
9 |
10 | //ios6
11 | - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath
12 | ```
13 |
14 | 在要使用一个Cell的时候我们先去看看tableView中有没有可以重用的cell,如果有就用这个可以重用的cell,只有在没有的时候才去创建一个Cell。这就是享元模式。
15 |
16 | 享元模式可以理解成,当细粒度的对象数量特别多的时候运行的代价会相当大,此时运用共享的技术来大大降低运行成本。比较突出的表现就是内容有效的抑制内存抖动的情况发生,还有控制内存增长。它的英文名字是flyweight,让重量飞起来。哈哈。名副其实,在一个TableView中Cell是一个可重复使用的元素,而且往往需要布局的cell数量很大。如果每次使用都创建一个Cell对象,系统的内容抖动会非常明显,而且系统的内存消耗也是比较大的。突然一想,享元模式只是给对象实例共享提供了一个比较霸道的名字吧。
17 |
18 | 一个典型的享元模式的UML图示例如下:
19 | 
20 |
21 | 而在DZTableView中的实现中,享元模式中Cell的实例的存储和共享主要是在tableView中完成的。
22 |
23 | ```
24 | NSMutableSet* _cacheCells;
25 | NSMutableDictionary* _visibleCellsMap;
26 | ```
27 | 我们定义了两个用来存储两种不同类型的cell的容器:
28 |
29 | 1. _cacheCells 存储不再使用过程中,可以被复用的cell
30 | 2. _visibleCellsMap 按照键值对的方式存储了在使用中的cell。key是cell的顺序信息,即是自上而下的第几个cell。
31 |
32 | 而我们获取一个cell的函数如下:
33 |
34 | ```
35 | - (DZTableViewCell*) _cellForRow:(NSInteger)rowIndex
36 | {
37 | DZTableViewCell* cell = [_visibleCellsMap objectForKey:@(rowIndex)];
38 | if (!cell) {
39 | cell = [_dataSource dzTableView:self cellAtRow:rowIndex];
40 | DZCellActionItem* deleteItem = [DZCellActionItem buttonWithType:UIButtonTypeCustom];
41 | deleteItem.backgroundColor = [UIColor redColor];
42 | [deleteItem addTarget:self action:@selector(deleteCellOfItem:) forControlEvents:UIControlEventTouchUpInside];
43 | [deleteItem setTitle:@"删除" forState:UIControlStateNormal];
44 | deleteItem.edgeInset = UIEdgeInsetsMake(0, 10, 0, 260);
45 | DZCellActionItem* editItem = [DZCellActionItem buttonWithType:UIButtonTypeCustom];
46 | editItem.edgeInset = UIEdgeInsetsMake(0, 80, 0, 180);
47 | editItem.backgroundColor = [UIColor greenColor];
48 | [editItem setTitle:@"编辑" forState:UIControlStateNormal];
49 | [editItem addTarget:self action:@selector(editCellOfItem:) forControlEvents:UIControlEventTouchUpInside];
50 | cell.actionsView.items = @[deleteItem,editItem ];
51 | }
52 | return cell;
53 | }
54 | ```
55 |
56 | 我们分几种情况来说明一下在布局cell的时候cell的重用问题。
57 | ### 已经在界面上的cell
58 | 对于已经在界面的cell我们很明显没有必要去重新构建,甚至没有必要去数据源去要。直接获取到相应的cell就好了。
59 |
60 | ```
61 | DZTableViewCell* cell = [_visibleCellsMap objectForKey:@(rowIndex)];
62 | ```
63 |
64 | ### 没有在界面上的cell
65 | 对于没有在界面的cell,我们就需要去数据那里去要:
66 |
67 | ```
68 | cell = [_dataSource dzTableView:self cellAtRow:rowIndex];
69 | ```
70 |
71 | 数据源在处理这个请求的时候就是按照上面我们说的享元模式的规则来了:
72 |
73 | ```
74 | - (DZTableViewCell*) dzTableView:(DZTableView *)tableView cellAtRow:(NSInteger)row
75 | {
76 | static NSString* const cellIdentifiy = @"detifail";
77 | DZTypeCell* cell = (DZTypeCell*)[tableView dequeueDZTalbeViewCellForIdentifiy:cellIdentifiy];
78 | if (!cell) {
79 | cell = [[DZTypeCell alloc] initWithIdentifiy:cellIdentifiy];
80 | }
81 | NSString* text = _timeTypes[row];
82 | return cell;
83 | }
84 | ```
85 | 先去看看tableView中有没有可以重用的cell,有就用,没有就新建。但是tableView是怎么知道有可以重用的cell的呢。
86 | #### DZTableView 可重用cell的cache
87 | 首先我们看一下获取重用cell的函数:
88 |
89 | ```
90 | - (DZTableViewCell*) dequeueDZTalbeViewCellForIdentifiy:(NSString*)identifiy
91 | {
92 | DZTableViewCell* cell = Nil;
93 | for (DZTableViewCell* each in _cacheCells) {
94 | if ([each.identifiy isEqualToString:identifiy]) {
95 | cell = each;
96 | break;
97 | }
98 | }
99 | if (cell) {
100 | [_cacheCells removeObject:cell];
101 | }
102 | return cell;
103 | }
104 | ```
105 |
106 | 很明显我们去_cacheCells中检查有没有特定identifiy的cell存在,如果有就说明有可重用的cell。这是一个直接获取的过程,那么久必然会存在往里面放cell的过程。
107 |
108 |
109 | ```
110 | - (void) layoutNeedDisplayCells
111 | {
112 | ...
113 | [self cleanUnusedCellsWithDispalyRange:displayRange];
114 | ...
115 | }
116 | - (void) cleanUnusedCellsWithDispalyRange:(NSRange)range
117 | {
118 | NSDictionary* dic = [_visibleCellsMap copy];
119 | NSArray* keys = dic.allKeys;
120 | for (NSNumber* rowIndex in keys) {
121 | int row = [rowIndex intValue];
122 | if (!NSLocationInRange(row, range)) {
123 | DZTableViewCell* cell = [_visibleCellsMap objectForKey:rowIndex];
124 | [_visibleCellsMap removeObjectForKey:rowIndex];
125 | [self enqueueTableViewCell:cell];
126 | }
127 | }
128 | }
129 | ```
130 | 我们在布局完cell的时候,回去清理界面上无用的cell。同时把这些cell放入可重用cell的容器中。等待下次使用的时候,复用。
131 |
132 | #### DZTableViewCell相关
133 | 当然,如果只是DZTableView单方面的想去重用cell是不肯能的。我们需要对DZTableViewCell做一些处理,才能够让这套享元模式运转起来。上面的代码中我们已经看到了,我们为DZTableViewCell添加了一些属性:
134 |
135 | ```
136 | //DZTableViewCell_private.h
137 | @interface DZTableViewCell ()
138 | @property (nonatomic, strong) NSString* identifiy;
139 | @property (nonatomic, assign) NSInteger index;
140 | @end
141 | ```
142 | identifiy标识了这个cell的种类。方便我们复用同一种类的cell。因为DZTableViewCell上可能会存在多种不同种类的cell,如果没有标识的重用起来就不知道获取到的cell是否能够适应特定的种类了。
143 |
144 | 还有一个index信息,这个是cell的顺序信息,主要是为了方便定位cell的位置用的。
145 |
146 | 值得注意的是这个定义是以Catogory的方式,定义在DZTableViewCell_private.h文件中的,而该文件只在DZTableView.mm中被引用,这样就避免了上面这些属性暴露给使用者,方式使用者使用方式不当导致的问题。或句话说,这些都是私有变量。必须被保护起来。
147 |
148 | 同时我们还定义和实现了一个函数:
149 |
150 | ```
151 | - (void) prepareForReused;
152 | ....
153 | - (void) prepareForReused
154 | {
155 | _index = NSNotFound;
156 | [self setIsSelected:NO];
157 | }
158 | ```
159 | 既然我们要复用一个Cell,那么就得在复用之前把Cell清理干净把,不然带着老数据去使用,用着用着就乱了,你就不知道cell的数据是对的还是错的了。
160 |
--------------------------------------------------------------------------------
/articles/Chapter1/subclassScrollView.md:
--------------------------------------------------------------------------------
1 |
2 | ##子类化UIScrollView实现对Cell的布局
3 |
4 | 解释一下为什么要从UIScrollView继承来完成TableView。这个和TableView的功能是密切相关的。TableView是一种内容数量大小不确定的布局方式,于是其需要在有限的屏幕(640*960)内展示无限的内容,而有这个功能的类就是UIScrollView。所以DZTableView从UIScrollView继承而来。
5 |
6 | ```
7 | @interface DZTableView : UIScrollView
8 | ```
9 | 然后我们来看一下怎样去布局。分析一下,一个纵向的TableView布局的话,基本上是一个Cell接一个cell在纵向上确定他们的frame就能够布局出来了。那么我们的主要任务就是确定cell的位置。
10 |
11 | 为了确定cell的位置我们定义了一些变量:
12 |
13 | ```
14 | typedef map DZCellYoffsetMap;
15 | typedef vector DZCellHeightVector;
16 | .....
17 | DZCellHeightVector _cellHeights;
18 | DZCellYoffsetMap _cellYOffsets;
19 | ```
20 | _cellHeights存储了所有cell的高度,而_cellYOffsets存储了每一个cell在y轴方向上的坐标。每一个cell在横向上是以填满为准的。即从View的最左侧开始布局(x=0)一直到最右侧右侧(width=view的宽度)。所以一般一个cell的绝对位置就是:
21 |
22 | ```
23 | - (CGRect) _rectForCellAtRow:(int)rowIndex
24 | {
25 | if (rowIndex < 0 || rowIndex >= _numberOfCells) {
26 | return CGRectZero;
27 | }
28 | float cellYoffSet = _cellYOffsets.at(rowIndex);
29 | float cellHeight = _cellHeights.at(rowIndex);
30 | return CGRectMake(0, cellYoffSet - cellHeight, CGRectGetWidth(self.frame), cellHeight);
31 | }
32 | ```
33 | 开始提到的几个关键的临时变量实在reduceContentSize函数中初始化的
34 |
35 | ```
36 | - (void) reduceContentSize
37 | {
38 | _numberOfCells = [_dataSource numberOfRowsInDZTableView:self];
39 | _cellYOffsets = DZCellYoffsetMap();
40 | _cellHeights = DZCellHeightVector();
41 | float height = 0;
42 | for (int i = 0 ; i < _numberOfCells; i ++) {
43 | float cellHeight = (_dataSourceReponse.funcHeightRow? [_dataSource dzTableView:self cellHeightAtRow:i] : kDZTableViewDefaultHeight);
44 | _cellHeights.push_back(cellHeight);
45 | height += cellHeight;
46 | _cellYOffsets.insert(pair(i, height));
47 | }
48 | if (height < CGRectGetHeight(self.frame)) {
49 | height = CGRectGetHeight(self.frame) + 2;
50 | }
51 | height += 10;
52 | CGSize size = CGSizeMake(CGRectGetWidth(self.frame), height);
53 |
54 | [self setContentSize:size];
55 | [self reloadPiceGradientColor];
56 | }
57 | ```
58 |
59 | 这样一来我们就能够确认每一个cell的在TableView中的绝对位置,以后无论是正常情况下的布局,或者在增加或者删除cell时的布局,就比较简单了。直接调用```_rectForCellAtRow```函数获取cell的frame,然后布局就ok了。
60 |
--------------------------------------------------------------------------------
/articles/Chapter1/summary.md:
--------------------------------------------------------------------------------
1 | #实现TableView
2 | 好了在上面的工作准备的差不多了之后,我们大概了解了UIKit给我们提供了一些什么基础的工具。貌似我们就可以大刀阔斧的开始搞了。不对,等等,貌似我们缺了掉什么。好像是设计模式相关的东西,比如享元模式、责任链模式等等。这些东西就在我们用的时候,说一下吧。读者也可以照一本设计模式的书放在身边,以备不时之需。
3 |
4 | 项目相关的代码可以从:[DZTableView](https://github.com/yishuiliunian/DZTableView)获取。
5 |
6 | 先看个效果图:
7 |
8 | 
9 |
10 |
11 | 先说一下我们都实现了些什么东西:
12 |
13 | 1. 基本的TableView对Cell的布局
14 | 2. Cell的增加和删除
15 | 3. 右滑出现删除和编辑菜单
16 | 4. 下拉输入并新建一个cell
17 |
18 | 废话不多说开始干活!!!
19 |
20 | ## 1. [解释一下整个UI的层次架构](gemotry.md)
21 | ## 2. [子类化UIScrollView实现对Cell的布局](subclassScrollView.md)
22 | ## 3. [Cell的重用](shareCell.md)
23 | ## 4. [响应和处理事件](event.md)
24 | ## 5. [在DZTableViewCell上扩展功能](cell.md)
25 | ## 6. [接口和数据获取](interface.md)
26 |
--------------------------------------------------------------------------------
/articles/Chapter2/controldata.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/controldata.md
--------------------------------------------------------------------------------
/articles/Chapter2/custome/custome.md:
--------------------------------------------------------------------------------
1 | 在 iOS 5.0 以前,视图控制器容器只属于苹果系统所有,苹果不建议你自定义视图控制器容器。实际上,在视图控制器编程指南这一章中明确告知我们不要使用它。以前苹果公司对于视图控制器容器的总体描述是“一个管理整个屏幕内容的视图控制器”,而现在的描述是“一个包含本身视图内容的单元集合”。为什么苹果不希望我们自定义像tab bar controllers 和navigation controllers这样的视图控制器容器呢?更准确的说,下面这条语句会带来什么问题:
2 |
3 | ```
4 | [viewControllerA.view addsubView:viewControllerB.view]
5 | ```
6 | 
8 | UIWindow作为应用程序的根视图,负责监听和分发屏幕旋转和视图布局等消息。在上图中,ViewController的view插入到根视图控制器当中的一个视图中作为其子视图,那么这个view就不会再接收到UIWindow传来的消息,像viewWillAppear:这种方法就不会被调用。
9 |
10 | 在ios5.0以前我们自定义的视图控制器容器,将会持有子类视图控制器的一个引用,并且需要我们手动的传递在父类视图控制器中调用的事件消息给子类视图控制器,准确地完成这项工作太难了!
11 |
12 |
13 | 实例详解:
14 |
15 | 小时候你在沙滩玩耍时,你的父母是否曾告诉过你:如果你用你的小铁锹一直挖,一直挖,最终你能挖到地球对面的中国去(作者在美国)。我父母曾这样给我说过,我做了一个叫Tunnel的应用来检验一下这个观点。你可以附加上GitHub Repo并运行这个程序,这样可以更好的帮助你理解这个实例。
16 |
17 | 
18 |
19 | 如果想获得当前位置相对面的地点(地球的对面),移动拿着铁锹的那个小人,地图会告知你准确的出口位置。触摸一下雷达按钮,地图就会翻转并显示确切的地名。
20 |
21 | 在当前的屏幕上,有两个地图视图控制器,每个都需要处理拖拽,标注位置,还有更新地图这些事件。翻转页面后会呈现对面地点的两个地图视图控制器。所有的这些视图控制器都存在于一个父类视图控制器容器中,这些视图控制器持有各自的视图,以确保视图的布局和翻转可以正确的进行。
22 |
23 | 根视图控制器拥有两个视图容器,添加两个的目的就是为了更方便各自容器的子视图进行布局,执行动画等操作,下面我们来解释一下。
24 |
25 | ```
26 | - (void)viewDidLoad
27 | {
28 | [super viewDidLoad];
29 |
30 | //Setup controllers
31 | _startMapViewController = [RGMapViewController new];
32 | [_startMapViewController setAnnotationImagePath:@"man"];
33 | [self addChildViewController:_startMapViewController]; // 1
34 | [topContainer addSubview:_startMapViewController.view]; // 2
35 | [_startMapViewController didMoveToParentViewController:self]; // 3
36 | [_startMapViewController addObserver:self
37 | forKeyPath:@"currentLocation"
38 | options:NSKeyValueObservingOptionNew
39 | context:NULL];
40 |
41 | _startGeoViewController = [RGGeoInfoViewController new]; // 4
42 | }
43 | ```
44 |
45 | _startMapViewController用来显示开始的位置,先实例化并用标注图片初始化。
46 |
47 | 1. _startMapViewController作为根视图控制器的一个子视图,添加到其中,子类中的willMoveToParentViewController:方法将会自动执行。
48 |
49 | 2. 上面的子类视图作为第一个视图容器的视图的子视图添加到其中。
50 |
51 | 3. 子视图收到一个通知,它现在有了一个父视图。
52 |
53 | 4. 进行地理位置编码的子视图控制器被初始化,但是现在还没有被插入到任何视图或控制器结构中。
54 |
55 | 布局
56 |
57 | 根视图控制器定义的两个视图容器分别决定了它们的子视图控制器的大小。子类视图控制器不知道自己将会添加到哪个视图容器中,所以它们的size大小必须可变的。
58 |
59 |
60 | ```
61 | - (void) loadView
62 | {
63 | mapView = [MKMapView new];
64 | mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
65 | [mapView setDelegate:self];
66 | [mapView setMapType:MKMapTypeHybrid];
67 |
68 | self.view = mapView;
69 | }
70 | ```
71 |
72 | 现在它们会以父类视图的bounds来布局,这样增加了子类视图的复用性,如果我们把它推入导航控制器栈中,它们依然会正确的布局。
73 |
74 | 转换效果
75 |
76 | Apple提供的视图控制器容器的API是如此的详细,以至于我们几乎可以以我们能想到的任意方式来初始化容器或者执行各种动画。Apple还提供了方便使用的block方法来交换当前屏幕上的两个视图。
77 |
78 |
79 | transitionFromViewController:toViewController:
80 | 上面这个方法为我们揭示了许多细节
81 |
82 | ```
83 | - (void) flipFromViewController:(UIViewController*) fromController
84 | toViewController:(UIViewController*) toController
85 | withDirection:(UIViewAnimationOptions) direction
86 | {
87 | toController.view.frame = fromController.view.bounds; // 1
88 | [self addChildViewController:toController]; //
89 | [fromController willMoveToParentViewController:nil]; //
90 |
91 | [self transitionFromViewController:fromController
92 | toViewController:toController
93 | duration:0.2
94 | options:direction | UIViewAnimationOptionCurveEaseIn
95 | animations:nil
96 | completion:^(BOOL finished) {
97 |
98 | [toController didMoveToParentViewController:self]; // 2
99 | [fromController removeFromParentViewController]; // 3
100 | }];
101 | }
102 | ```
103 |
104 | 1.我们添加的toViewController执行动画之前,fromViewController会收到一个将会移除的通知,如果fromViewController是容器视图等级的视图,那么此时控制器的viewWillDisappear:方法将会被调用。
105 |
106 | 2.toViewController接收到来自父类视图的消息,其对应的方法会被调用。
107 |
108 | 3.fromViewController被移除。
109 |
110 | 上面的这个方法,可以自动转换出旧的视图并替换成新的视图。如果你想实现你自己的转换方法,并且想让每一个时刻都只能显示一个视图,你必须对旧的视图调用removeFromSuperview方法,新的视图调用addSubview:方法。调用方法的次序如果出错将会导致UIViewControllerHierarchyInconsistency警告。比如在你添加视图之前调用视图的didMoveToParentViewController:将会出错。
111 |
112 | 为了能够使用 UIViewAnimationOptionTransitionFlipFromTop动化序列,我们必须把子类视图添加到视图容器控制器上,而不是添加到根视图控制器上,否则的化,执行动画将会导致整个根视图翻转。
113 |
114 | 通信
115 |
116 | 视图控制器应该是能够复用,包含当前视图的一个实体,子类视图也应该遵循这一经验法则。为了遵循这一法则,父类视图控制器应该只负责两个任务:布局子类视图控制器的根视图,通过自身暴露的API接口与子类视图控制器通信,不应该直接修改子类视图的树形结构和状态。
117 |
118 | 在Tunnel应用实例中,父类视图控制器会适时监听map view controllers当中的currentLocation属性。
119 |
120 | ```
121 | [_startMapViewController addObserver:self
122 | forKeyPath:@"currentLocation"
123 | options:NSKeyValueObservingOptionNew
124 | context:NULL];
125 | ```
126 |
127 | 在地图旁边拿着铁锹的小男孩移动时,相应的属性就会变化,这时父类视图控制器将新的位置相对的(地球对面)的地点传递给另一个地图。
128 |
129 | 类似地,当你点击雷达按钮,父类视图控制器在新的子类视图控制器中设置与以前位置对应的地理位置。
130 |
131 | ```
132 | [_startGeoViewController setLocation:_startMapViewController.currentLocation];
133 | [_targetGeoViewController setLocation:_targetMapViewController.currentLocation];
134 | ```
135 | 尽量选择独立的方法来完成子类视图与父类视图的通信(KVO, 通知中心, 代理模式),这与上面提到的通信法则一致,即子类视图控制器应该独立,并且具有复用性。在我们的例程中,我们可以把一个视图控制器压入到导航控制器栈中,但是通信接口仍然选择相同的API。
136 |
137 |
--------------------------------------------------------------------------------
/articles/Chapter2/custome/imgs/tunnel-screenshot@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/custome/imgs/tunnel-screenshot@2x.png
--------------------------------------------------------------------------------
/articles/Chapter2/custome/imgs/view-insertion@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/custome/imgs/view-insertion@2x.png
--------------------------------------------------------------------------------
/articles/Chapter2/layoutviews.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/layoutviews.md
--------------------------------------------------------------------------------
/articles/Chapter2/mainview.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/mainview.md
--------------------------------------------------------------------------------
/articles/Chapter2/mvc/imgs/500px-MVC-Process.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/mvc/imgs/500px-MVC-Process.png
--------------------------------------------------------------------------------
/articles/Chapter2/mvc/imgs/thirdLevel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter2/mvc/imgs/thirdLevel.jpg
--------------------------------------------------------------------------------
/articles/Chapter2/mvc/mvc.md:
--------------------------------------------------------------------------------
1 | MVC模式最早由Trygve Reenskaug在1978年提出[1] ,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件设计模式。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:
2 |
3 | * (控制器Controller)- 负责转发请求,对请求进行处理。
4 | * (视图View) - 界面设计人员进行图形界面设计。
5 | * (模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
6 |
7 | 除了将应用程序划分为三种组件,模型 - 视图 - 控制器(MVC)设计定义它们之间的相互作用。[2]
8 |
9 | * 模型(Model) 用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。(比较:观察者模式(软件设计模式))
10 | * 视图(View)能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。
11 | * 控制器(Controller)起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。
12 |
13 | 
--------------------------------------------------------------------------------
/articles/Chapter2/mvc/thirdLevel.md:
--------------------------------------------------------------------------------
1 | 三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)。区分层次的目的即为了“高内聚,低耦合”的思想。
2 |
3 | 1. 表现层(UI):通俗讲就是展现给用户的界面,即用户在使用一个系统的时候他的所见所得。
4 | 2. 业务逻辑层(BLL):针对具体问题的操作,也可以说是对数据层的操作,对数据业务逻辑处理。
5 | 3. 数据访问层(DAL):该层所做事务直接操作数据库,针对数据的增添、删除、修改、查找等。
6 |
7 | 
8 |
9 |
10 | ##表示层
11 |
12 | 位于最外层(最上层),最接近用户。用于显示数据和接收用户输入的数据,为用户提供一种交互式操作的界面。
13 | 业务逻辑层
14 |
15 | ##业务逻辑层(Business Logic Layer)
16 | 无疑是系统架构中体现核心价值的部分。它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也即是说它是与系统所应对的领域(Domain)逻辑有关,很多时候,也将业务逻辑层称为领域层。例如Martin Fowler在《Patterns of Enterprise Application Architecture》一书中,将整个架构分为三个主要的层:表示层、领域层和数据源层。作为领域驱动设计的先驱Eric Evans,对业务逻辑层作了更细致地划分,细分为应用层与领域层,通过分层进一步将领域逻辑与领域逻辑的解决方案分离。
17 | 业务逻辑层在体系架构中的位置很关键,它处于数据访问层与表示层中间,起到了数据交换中承上启下的作用。由于层是一种弱耦合结构,层与层之间的依赖是向下的,底层对于上层而言是“无知”的,改变上层的设计对于其调用的底层而言没有任何影响。如果在分层设计时,遵循了面向接口设计的思想,那么这种向下的依赖也应该是一种弱依赖关系。因而在不改变接口定义的前提下,理想的分层式架构,应该是一个支持可抽取、可替换的“抽屉”式架构。正因为如此,业务逻辑层的设计对于一个支持可扩展的架构尤为关键,因为它扮演了两个不同的角色。对于数据访问层而言,它是调用者;对于表示层而言,它却是被调用者。依赖与被依赖的关系都纠结在业务逻辑层上,如何实现依赖关系的解耦,则是除了实现业务逻辑之外留给设计师的任务。
18 | 数据层
19 |
20 | ##数据访问层:
21 | 有时候也称为是持久层,其功能主要是负责数据库的访问,可以访问数据库系统、二进制文件、文本文档或是XML文档。
22 | 简单的说法就是实现对数据表的Select,Insert,Update,Delete的操作。如果要加入ORM的元素,那么就会包括对象和数据表之间的mapping,以及对象实体的持久化。
--------------------------------------------------------------------------------
/articles/Chapter2/summary.md:
--------------------------------------------------------------------------------
1 | # 视图控制器DZTableViewController
2 |
3 | 在第一章中,我们简单说了一下ViewController在整个UIKit中的作用。简单归纳一下就是:
4 |
5 | * 创建和管理视图。
6 | * 管理视图上显示的数据。
7 | * 设备方向变化,调整视图大小以适应屏幕。
8 | * 负责视图和模型之间的数据及请示的传递。
9 |
10 | 大家都熟悉的[MVC架构](mvc/mvc.md)中,ViewController正式处于C层。起到了对View层和Model层的组织作用,同时控制应用程序的流程。并且会处理用户事件,对之做出相应。
11 | 从另外一个角度——[三层架构](mvc/thirdLevel.md)来看,ViewController承载着我们整个程序绝大部分的业务逻辑。于是,在我们实际的编程实践中,会发现很多逻辑控制的代码其实都写在了ViewController生命周期的各个函数中。
12 |
13 | ## 1. [ViewController的生命周期]()
14 | ## 2. [自定义视图控制器](custome/custome.md)
15 |
16 |
--------------------------------------------------------------------------------
/articles/Chapter3/subclass.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/articles/Chapter3/subclass.md
--------------------------------------------------------------------------------
/articles/Chapter3/summary.md:
--------------------------------------------------------------------------------
1 | # DZTableView的可扩展性探讨
2 |
3 | ## 1. [什么是可扩展性](./topic.md)
4 |
5 |
6 | 既然我们要实现一个类似于UITableView一样非常通用的组件,也就要求DZTableView的可扩展性就要好一点。这包括:
7 |
8 | 1. 属性的可配置型
9 | 2. 功能上的扩展性,方便子类化
10 |
11 | 为了展示这个,特意做了右滑删除,还有下拉新建cell的功能。因为本文的主要目的是通过自己构建一个TableView来解释IOS UI编程。所以就不详细展开讨论。看一下代码大概就能明白了。
12 |
--------------------------------------------------------------------------------
/articles/Chapter3/topic.md:
--------------------------------------------------------------------------------
1 | # 什么是可扩展性
2 |
3 | 我们要理解可扩展性这个东西,最好的一个方法就是给他下个定义。因为我一直坚信一句老话:you can say it, you know it。只有当你能够准确定义一个东西并且描述它的时候,你才能够真正说你理解了它。于是我们查了一些资料来找关于可扩展性的定义。
4 |
5 | 1. Wiki上说:可扩放性(Scalability)是指问题规模和处理器数目之间的函数关系([Wiki](http://zh.wikipedia.org/wiki/%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7))。这个怎么看也觉得和我们想要讨论的问题,有点不太搭边。
6 | 2. 百度百科上说:设计良好的代码允许更多的功能在必要时可以被插入到适当的位置中。这样做的目的的是为了应对未来可能需要进行的修改,而造成代码被过度工程化地开发。
7 | 可扩展性可以通过软件框架来实现:动态加载的插件、顶端有抽象接口的认真设计的类层次结构、有用的回调函数构造以及功能很有逻辑并且可塑性很强的代码结构。
8 | 可扩展性是软件设计的原则之一,它以添加新功能或修改完善现有功能来考虑软件的未来成长。可扩展性是软件拓展系统的能力。
9 | 简单地说,可扩展性就是关于如何处理更大规模的业务。比如,Web应用程序就是允许更多的人使用你的服务。如果你不能弄清楚如何提高性能的同时向外扩展,没关系。只要你能处理更大规模的用户,即使是存在多个单点故障也没有问题。组合的可扩展性要求要满足用户不断发展的要求,还要满足因技术发展需要而实现的扩展和升级的需求。 [百度百科](http://baike.baidu.com/view/476360.htm)。
10 | 这个解释貌似开始靠点谱了,但是有种大杂烩的感觉。像是还有些什么东西不能够说出来。
11 |
12 | 在苦苦寻找后,最后发现没能够发现比较合适的资料来解释可扩展性这个东西。那么就只能够说说我自己的看法了。
13 |
14 | 可扩展性是一种设计概念,代表了我们对未来的一种预想,我们希望在现有的架构或者设计基础上,当未来某些方面发生噶边的时候,我们能够以最小的改动来适应这种变化。我们的改动越小,并且对这种变化的适应性越好,我们就会说这个设计的可扩展性是非常好的。简单来说,就是让当前设计去适应未来不确定的变化。
15 |
16 | 很多时候,让设计有可扩展性是和设计者本身有着非常直接的联系。举个例子来说,我们在UI编程的时候,经常会遇到很多hardcode的代码,把很多控件的坐标用死数字写死。我们对这样的代码嗤之以鼻,因为他们适应不了任何变化。一旦屏幕大小发生改变,甚至是父视图的布局发生改变,就会让整个界面混乱。因为,那些用死数字写死的坐标的控件,真的是死的。无论周围条件发生怎样的改变,他们都无动于衷,呆若木鸡。当我们去问做出这样设计的工程师的时候,他们会非常简单的反问你:需求不就说要展示成这个样子吗?也就是说,他们没有任何对未来的预期。在他们眼中,需求就是最终的模样,之后不会发生任何改变。
17 |
18 | 而与之形成对比的是,在UI编程的时候,有很多人即使在像IOS这种使用绝对布局模型的框架下编程的时候,他们也尽可能把相对布局的观念运用在编程实践。比如一个界面要布局三个控件,他们不是将三个控件的坐标写死,而是尽可能的找到这三个控件布局之间的关系。尽可能的通过描述这种关系来进行布局。比如他们发现着三个控件是居中对齐的,那么他们就会用代码去描述这种居中对齐的关系。他们这样做带来的好处是,当父视图布局或者其他条件发生改变的时候。整个界面会自动调整布局来适应这种变化。当你去问做出这样的设计决策的工程师,为什么要这样做的时候。他们会说:他妈的产品和设计的需求天天变,你现在写代码不考虑这种变化,将来你就得花更多的时间去改代码。
19 |
--------------------------------------------------------------------------------
/imgs/qrcode_for_gh_34347e9f195e_430.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/imgs/qrcode_for_gh_34347e9f195e_430.jpg
--------------------------------------------------------------------------------
/ppt/构建TableView.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/ppt/构建TableView.pdf
--------------------------------------------------------------------------------
/ppt/构建TableView.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yishuiliunian/DZTableView/a25d7080124b8b4c92a47d6b6dfbb3a7c4b2325f/ppt/构建TableView.pptx
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 通过实现一个TableView来理解IOS UI编程
2 | ====
3 |
4 | ---
5 |
6 | 欢迎关注IOSTips微信订阅号:
7 |
8 | 
9 |
10 | ---
11 |
12 | 先说点题外话。我们在日常做和IOS的UI相关的工作的时候,有一个组件的使用频率非常高--UITabelView。于是就要求我们对UITableView的每一个函数接口,每一个属性都了如指掌,只有这样在使用UITableView的时候,我们才能游刃有余的处理各种需求。不然做出来的东西,很多时候只是功能实现了,但是程序效率和代码可维护性都比较差。举个例子,比如在tableView头部要显示一段文字。我见过的最啰嗦的解决方案是这样的:
13 |
14 | 1. 子类化一个UIViewController
15 | 2. 将根View设置成一个UIScrollView
16 | 3. 把头部的Label和TableView加在ScrollView上面
17 | 4. 开始各种调整ScrollView和TableView的delegate调用函数里面的参数,让Label能随着TableView滑动
18 |
19 | 其实如果你熟悉UITableView,那么你几句话就可以搞定
20 |
21 | ```
22 | UILabel* label = [UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 40);
23 | label.text = @"就是一段文字嘛,干嘛大动干戈";
24 | tableVIew.tableHeaderView = label;
25 | ```
26 |
27 | 所谓工欲善其事必先利器,编程语言和各种库其实本质上就是工具而已。你要想用这些工具来实现产品和Leader提出的各种需求。当然,不止是功能上的实现,还包括程序效率,代码质量。特别想着重强调一下代码质量,如果你不想后面维护自己的代码就像噩梦一样,如果你不想一旦新来一个需求就得对代码大刀阔斧的伤筋动骨,如果你不想给后来者埋坑。那么最好就多注意一下。
28 |
29 | 这里的代码质量并不是简简单单的指代码写点注释了,利用Xcode提供的一些像pragam或者#warning来解释代码。《编写可阅读代码的艺术》还有其他一些编程的书籍也都说道,真正高质量的代码,是不需要注释的。一个好的代码从逻辑上和结构上都是清晰的。我看到很多很难维护的代码都是因为逻辑结构混乱,和设计模式滥用导致的程序结构紊乱。分析其原因,就会发现很多时候,是因为写代码的人对所使用的工具(主要是objc和UIKit)不是非常熟悉,于是就写了很多凑出来的临时方案,简单的实现了功能。表面看起来挺好的,但是实际上代码已经外强中,骨子里都乱了。后期维护起来会让人痛不欲生。
30 |
31 | 了解一下UITableView的一些详细的技术细节甚至是UIKit的一些技术细节对于我们写出比较好的代码,比较好的实现任务是很有必要的。我们通过实现自己的TableView来反观UIKit的UITableView,来加深我们对UITableView和UIKit的理解。在这个过程中,我们会碰到非常多非常细节的问题,而这些正是我们需要注意并且掌握的。
32 |
33 | 同时,个人一直觉得对于搞IOS开发来说自己实现一遍TableView就像是一种成人礼一样。你能够通过实现一个UITableView来深入的理解UIKit的一些技术细节,对IOS UI编程所使用到的工具,有比较深入的了解。这样,写程序的时候才不会捉襟见肘。
34 |
35 | 言归正传。开始实现一个TableView。
36 | 
37 |
38 | # 一、[UIKit提供的基础](articles/Chapter0/summary.md)
39 |
40 | # 二、[实现TableView](articles/Chapter1/summary.md)
41 |
42 | # 三、[视图控制器DZTableViewController](articles/Chapter2/summary.md)
43 |
44 | # 四、[DZTableView的可扩展性探讨](articles/Chapter3/summary.md)
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------