preface
This project is suitable for beginners who just contact iOS development to practice. First of all, this open source project uses many excellent open source frameworks. Thanks open source. Let’s take a look at the knowledge points involved in this project:
- Use UICollectionView to build common interface, as well as custom layout
- The realization of transition animation
- Use FMDB to achieve data storage
- Simple animation implementation
- Use Block implementation to encapsulate a commonly used control
- How to package a common controller
- How best to use tripartite libraries such as (AFN…)
I was a rookie,(see my name can be seen), hope the gods in the code structure to give guidance……. Finally, long live open source
Results the preview
The new version. GIF
Home page – featured.gif
Home – Featured – live. GIF
Tutorial 01. GIF
Fair. GIF
Manual. GIF
Home page – dactress.gif
Home – follow.gif
Home page – Activity 01.gif
I. GIF
Tutorial 02. GIF
Home page – Activity 02.gif
Home – Featured -02.gif
The code structure
Snip20160717_1.png
I prefer to structure the code by business, and that’s about it
New Version Features
Ideas and implementation are relatively simple, need to pay attention to the point is to judge whether there is a new version of the logic extracted, directly on the code
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = [GPGuideTool chooseRootViewController];
[self configApper];
[self.window makeKeyAndVisible];
return YES;
}Copy the code
Judgment logic
+ (UIViewController *)chooseRootViewController {UIViewController *rootVc = nil; NSDictionary *dict = [NSBundle mainBundle].infoDictionary; / / get the latest version number nsstrings * curVersion = dict [@ "CFBundleShortVersionString"]; / / get the last version number nsstrings * lastVersion = [GPUserDefaults objectForKey: GPVersionKey]; The latest version number / / before lastVersion if ([curVersion isEqualToString: lastVersion]) {/ / version number equal rootVc = [[GPAdViewController alloc]init]; }else{// Have the latest version number // save to preferences [[NSUserDefaults standardUserDefaults] setObject:curVersion forKey:GPVersionKey]; rootVc = [[GPNewFeatureController alloc]init]; } return rootVc; }Copy the code
New feature interface implementation
- (instancetype) init {/ / water layout UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc] init]; // Set the size of the cell layout.itemSize = [UIScreen mainScreen].bounds.size; / / set each row spacing layout. MinimumLineSpacing = 0; / / set of each cell spacing layout. MinimumInteritemSpacing = 0; / / set the scroll direction layout. ScrollDirection = UICollectionViewScrollDirectionHorizontal; return [self initWithCollectionViewLayout:layout]; } - (void)viewDidLoad { [super viewDidLoad]; [self setUpCollectionView]; } // initialize CollectionView - (void)setUpCollectionView {// Register cell [self. CollectionView registerNib:[UINib nibWithNibName:NSStringFromClass([GPNewFeatureCell class]) bundle:nil] forCellWithReuseIdentifier:reuseIdentifier]; / / cancel spring effect self. CollectionView. Bounces = NO. / / cancel indicator shows the self. CollectionView. ShowsHorizontalScrollIndicator = NO; / / open the paging model self. CollectionView. PagingEnabled = YES; } #pragma mark - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 5; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { GPNewFeatureCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; NSString *imageName = [NSString stringWithFormat:@"newfeature_0%ld_736",indexPath.item + 1]; cell.image = [UIImage imageNamed:imageName]; return cell; } #pragma mark - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *) indexPath {the if (indexPath. Row = = 4) {/ / root controller of switching window jump [UIApplication sharedApplication]. KeyWindow. The rootViewController = [[GPAdViewController alloc]init]; CATransition *anim = [CATransition animation]; anim.type = @"rippleEffect"; anim.duration = 1; [[UIApplication sharedApplication].keyWindow.layer addAnimation:anim forKey:nil]; }}Copy the code
The request data
Methods a
Simply wrap it on top of the AFN
+(void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObj))success failure:(void (^)(NSError *))failure { AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (success) { success(responseObject); } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (failure) { failure(error); } }]; } +(void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObj))success failure:(void (^)(NSError *))failure { AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager]; [mgr POST:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (success) { success(responseObject); } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (failure) { failure(error); } }]; }Copy the code
Way 2
On the basis of the above encapsulation again
+ (void)getWithUrl:(NSString *)url param:(id)param resultClass:(Class)resultClass success:(void (^)(id))success failure:(void (^)(NSError *))failure { NSDictionary *params = [param mj_keyValues]; [GPHttpTool get:url params:params success:^(id responseObj) { if (success) { id result = [resultClass mj_objectWithKeyValues:responseObj[@"data"]]; success(result); } } failure:^(NSError *error) { if (failure) { failure(error); } }]; } + (void)getMoreWithUrl:(NSString *)url param:(id)param resultClass:(Class)resultClass success:(void (^)(id))success failure:(void (^)(NSError *))failure { NSDictionary *params = [param mj_keyValues]; [GPHttpTool get:url params:params success:^(id responseObj) { if (success) { id result = [resultClass mj_objectArrayWithKeyValuesArray:responseObj[@"data"]]; success(result); } } failure:^(NSError *error) { if (failure) { failure(error); } }]; } + (void)postWithUrl:(NSString *)url param:(id)param resultClass:(Class)resultClass success:(void (^)(id))success failure:(void (^)(NSError *))failure { NSDictionary *params = [param mj_keyValues]; [GPHttpTool post:url params:params success:^(id responseObj) { if (success) { id result = [resultClass mj_objectWithKeyValues:responseObj]; success(result); } } failure:^(NSError *error) { if (failure) { failure(error); }}]; }Copy the code
Home page
Snip20160718_2.png
The overall layout of the home page is realized by UICollection View. Here only the key codes are posted. The specific codes can be downloaded and viewed
The scrollable title bar on the home page is covered in several places, so it can be encapsulated in a simple way. Here I will do a simple encapsulation. In open source projects, I use an excellent tripartite method here:
#pragma mark - (NSMutableArray *)btnArray {if (! _btnArray) { _btnArray = [[NSMutableArray alloc] init]; } return _btnArray; } - (instancetype)initWithChildControllerS:(NSArray *)titleArray { if (self = [super init]) { self.titleArray = titleArray; [self layout]; } return self; } - (void)layout { UIButton *lastBtn = nil; for (int i = 0; i < self.titleArray.count; i ++) { UIButton *btn = [[UIButton alloc]init]; [btn setTitle:self.titleArray[i] forState:UIControlStateNormal]; Alpha [BTN setTitleColor: [UIColor colorWithWhite: 1:0.5] forState: UIControlStateNormal]; [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected]; btn.userInteractionEnabled = NO; [self.btnArray addObject:btn]; [self addSubview:btn]; if (lastBtn) { btn.sd_layout .leftSpaceToView(lastBtn,40) .topSpaceToView(lastBtn,0) .bottomSpaceToView(lastBtn,0) .widthIs(40); }else{ btn.sd_layout .leftSpaceToView(self,0) .topSpaceToView(self,0) .bottomSpaceToView(self,0) .widthIs(40); } lastBtn = btn; } [self setupAutoWidthWithRightView:lastBtn rightMargin:0]; } // change button state -(void)changeSelectBtn:(UIButton *) BTN {self.previousbtn = self.currentbtn; self.currentBtn = btn; self.previousBtn.selected = NO; self.currentBtn.selected = YES; } / / update button state - (void) updateSelecterToolsIndex: (NSInteger) index {UIButton * selectBtn = self btnArray [index]; [self changeSelectBtn:selectBtn]; }Copy the code
Scrollable view
- (instancetype)initWithChildControllerS:(NSArray *)vcArray selectBlock:(selecBlock)selecB
{
if (self = [super init]) {
self.selecB = selecB;
self.backgroundColor = [UIColor whiteColor];
self.pagingEnabled = YES;
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.delegate = self;
self.childVcArray = vcArray;
[self layout];
}
return self;
}
- (void)layout
{
UIView *lastView = nil;
for (UIViewController *viewVc in self.childVcArray) {
[self addSubview:viewVc.view];
if (lastView) {
viewVc.view.sd_layout
.widthIs(SCREEN_WIDTH)
.heightIs(SCREEN_HEIGHT)
.leftSpaceToView(lastView,0);
}else{
viewVc.view.sd_layout
.widthIs(SCREEN_WIDTH)
.heightIs(SCREEN_HEIGHT)
.leftSpaceToView(self,0);
}
lastView = viewVc.view;
}
[self setupAutoContentSizeWithRightView:lastView rightMargin:0];
}
-(void)updateVCViewFromIndex:(NSInteger )index
{
[self setContentOffset:CGPointMake(index*SCREEN_WIDTH, 0) animated:YES];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
int page = (scrollView.contentOffset.x + SCREEN_WIDTH / 2) / SCREEN_WIDTH;
self.selecB(page);
}Copy the code
The simple idea for endless scrolling is to swap images and paste codes when scrolling to the right or left
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. CGFloat w = self.view.frame.size.width; CGFloat h = self.view.frame.size.height; // Initialize scrollView_scrollView. pagingEnabled = YES; _scrollView.contentSize = CGSizeMake(w * 3, 0); _scrollView.contentOffset = CGPointMake(w, 0); _scrollView.showsHorizontalScrollIndicator = NO; _scrollView.delegate = self; UIImageView *visibleView = [[UIImageView alloc] init]; _visibleView = visibleView; _visibleView.image = [UIImage imageNamed:@"00"]; _visibleView.frame = CGRectMake(w, 0, w, h); _visibleView.tag = 0; [_scrollView addSubview:_visibleView]; UIImageView *reuseView = [[UIImageView alloc] init]; _reuseView = reuseView; _reuseView.frame = self.view.bounds; [_scrollView addSubview:_reuseView]; } - (void) scrollViewDidScroll: (scrollView UIScrollView *) {/ / get the offset CGFloat offsetX = scrollView. ContentOffset. X. CGFloat w = scrollView.frame.size.width; CGRect f = _reuseView.frame; // 1. NSInteger index = 0; If (offsetX > _visibleView. Frame. Origin. X) {/ / is displayed in the most the right side f.o rigin, x = scrollView. The contentSize. Width - w; index = _visibleView.tag + 1; if (index >= kCount) index = 0; } else {// display f.oregin.x = 0; index = _visibleView.tag - 1; if (index < 0) index = kCount - 1; } // Set the reused view _reuseview.frame = f; _reuseView.tag = index; NSString *icon = [NSString stringWithFormat:@"0%ld", index]; _reuseView.image = [UIImage imageNamed:icon]; / / 2. Scroll to the left or the right of the image the if (offsetX < = 0 = "" | | =" "offsetX =" "> = w * 2) {/ / 2.1. Swap intermediate and recycled Pointers UIImageView *temp = _visibleView; _visibleView = _reuseView; _reuseView = temp; _visibleView.frame = _reuseview. frame; // 2.3 Initialize scrollView.contentOffset = CGPointMake(w, 0); }}Copy the code
Sliding animation
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { CATransform3D rotation; / / 3 d rotation / / rotation = CATransform3DMakeTranslation (0, 50, 20); Rotation = CATransform3DMakeRotation (M_PI_4, 0.0, 0.7, 0.4); Rotation = CATransform3DScale(Rotation, 0.8, 0.8, 1); Rotation. The m34 = 1.0/1000; cell.layer.shadowColor = [[UIColor redColor]CGColor]; cell.layer.shadowOffset = CGSizeMake(10, 10); cell.alpha = 0; cell.layer.transform = rotation; [UIView beginAnimations:@"rotation" context:NULL]; // Rotation duration [UIView setAnimationDuration:0.6]; cell.layer.transform = CATransform3DIdentity; cell.alpha = 1; cell.layer.shadowOffset = CGSizeMake(0, 0); [UIView commitAnimations]; }Copy the code
Transition animation about transition animation, there are a lot of big god to write blog, here I directly posted some address, interested can see, meow god, WR god; Login screen
Snip20160718_8.png
The login interface is a Swift course of Jake Lin, which I re-implemented with OC
#pragma mark - 生命周期
- (void)viewDidLoad {
[super viewDidLoad];
[self setupView];
[self setupAnimtion];
[self addEventBar];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self nextAnimtion];
}
#pragma mark - 初始化
- (void)setupView
{
self.navigationController.navigationBarHidden = YES;
UIActivityIndicatorView *acView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.acView = acView;
UIImageView *snipImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Warning"]];
snipImageView.hidden = YES;
[self.view addSubview:snipImageView];
self.snipImageView = snipImageView;
}
- (void)addEventBar
{
GPEventBtn *eventBtn = [[GPEventBtn alloc]init];
[eventBtn setImage:[UIImage imageNamed:@"activity_works_Btn"] forState:UIControlStateNormal];
[eventBtn sizeToFit];
eventBtn.transform = CGAffineTransformMakeScale(2, 2);
[eventBtn showEventButCenter:CGPointMake(SCREEN_WIDTH * 0.5 , SCREEN_HEIGHT - GPEventScale * eventBtn.width)];
eventBtn.transform = CGAffineTransformMakeScale(2, 2);
[eventBtn addTarget:self action:@selector(dismissVc) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:eventBtn];
[self.view bringSubviewToFront:eventBtn];
eventBtn.hidden = YES;
self.eventBtn = eventBtn;
}
#pragma mark - 动画
- (void)setupAnimtion
{
self.buble1.transform = CGAffineTransformMakeScale(0, 0);
self.buble2.transform = CGAffineTransformMakeScale(0, 0);
self.buble3.transform = CGAffineTransformMakeScale(0, 0);
self.buble4.transform = CGAffineTransformMakeScale(0, 0);
self.buble5.transform = CGAffineTransformMakeScale(0, 0);
self.logo.centerX-= self.view.width;
self.dot.centerX -= self.view.width/2;
UIView *paddingUserView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 30, self.userName.height)];
self.userName.leftView = paddingUserView;
self.userName.leftViewMode = UITextFieldViewModeAlways;
UIView *paddingPassView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 30, self.password.height)];
self.password.leftView = paddingPassView;
self.password.leftViewMode = UITextFieldViewModeAlways;
UIImageView *userImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"User"]];
userImageView.x = 5;
userImageView.y = 5;
[self.userName addSubview:userImageView];
UIImageView *passImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Key"]];
passImageView.x = 5;
passImageView.y = 5;
[self.password addSubview:passImageView];
self.userName.centerX -= self.view.width;
self.password.centerX -= self.view.width;
self.loginBtn.centerX -= self.view.width;
}
- (void)nextAnimtion
{
[UIView animateWithDuration:0.3 delay:0.3 usingSpringWithDamping:0.4 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.buble1.transform = CGAffineTransformMakeScale(1, 1);
self.buble2.transform = CGAffineTransformMakeScale(1, 1);
self.buble3.transform = CGAffineTransformMakeScale(1, 1);
} completion:nil];
[UIView animateWithDuration:0.3 delay:0.4 usingSpringWithDamping:0.4 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.buble4.transform = CGAffineTransformMakeScale(1, 1);
self.buble5.transform = CGAffineTransformMakeScale(1, 1);
} completion:nil];
[UIView animateWithDuration:0.5 delay:0.5 usingSpringWithDamping:0.6 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.logo.centerX += self.view.width;
} completion:nil];
[UIView animateWithDuration:0.4 delay:0.6 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.userName.centerX += self.view.width;
} completion:nil];
[UIView animateWithDuration:0.4 delay:0.7 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.password.centerX += self.view.width;
} completion:nil];
[UIView animateWithDuration:0.4 delay:0.8 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.loginBtn.centerX += self.view.width;
} completion:nil];
[UIView animateWithDuration:3 delay:1 usingSpringWithDamping:0.1 initialSpringVelocity:0.6 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.dot.centerX += self.view.width * 0.4;
} completion:nil];
}
#pragma mark - 内部方法
- (IBAction)loginBtnClick:(UIButton *)sender {
self.loginBtn.enabled = NO;
self.acView.center = CGPointMake(0, 0);
[self.acView startAnimating];
[self.loginBtn addSubview:self.acView];
self.snipImageView.center = self.loginBtn.center;
self.loginPoint = self.loginBtn.center;
[UIView animateWithDuration:0.3 animations:^{
self.loginBtn.centerX -= 30;
}completion:^(BOOL finished) {
[UIView animateWithDuration:1.5 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.loginBtn.centerX += 30;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
self.loginBtn.centerY += 90;
[self.acView removeFromSuperview];
}completion:^(BOOL finished) {
[UIView transitionWithView:self.snipImageView duration:0.3 options:UIViewAnimationOptionTransitionFlipFromTop animations:^{
self.snipImageView.hidden = NO;
}completion:^(BOOL finished) {
[UIView transitionWithView:self.snipImageView duration:3 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.snipImageView.hidden = YES;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 animations:^{
self.loginBtn.center = self.loginPoint;
}completion:^(BOOL finished) {
self.loginBtn.enabled = YES;
}];
}];
}];
}];
}];
}];
}
- (void)dismissVc
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.loginBtn removeFromSuperview];
[UIView transitionWithView:self.eventBtn duration:0.5 options:UIViewAnimationOptionTransitionFlipFromTop animations:^{
self.eventBtn.hidden = NO;
} completion:nil];
}Copy the code
Snip20160718_9.png
Chat interface construction, using SDAutoLayout, interested can learn about it
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { [self setupView]; } return self; } - (void)setupView { self.selectionStyle = UITableViewCellSelectionStyleNone; _iconImageView = [UIImageView new]; [self.contentView addSubview:_iconImageView]; _container = [UIView new]; [self.contentView addSubview:_container]; _label = [MLEmojiLabel new]; _label.delegate = self; _label. The font = [UIFont systemFontOfSize: 16.0 f]; _label.numberOfLines = 0; _label.textInsets = UIEdgeInsetsMake(0, 0, 0, 0); _label.isAttributedContent = YES; [_container addSubview:_label]; _messageImageView = [UIImageView new]; [_container addSubview:_messageImageView]; _containerBackgroundImageView = [UIImageView new]; [_container insertSubview:_containerBackgroundImageView atIndex:0]; _maskImageView = [UIImageView new]; [self setupAutoHeightWithBottomView:_container bottomMargin:0]; / / set containerBackgroundImageView fill the parent view _containerBackgroundImageView. Sd_layout. SpaceToSuperView (UIEdgeInsetsMake (0, 0, 0, 0)); } - (void)setModel:(GPChatData *)model { _model = model; _label.text = model.text; / / based on the model set cell left or right float float style [self setMessageOriginWithModel: model]; If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) {// If (model.imagename) clearAutoWidthSettings]; self.messageImageView.hidden = NO; self.messageImageView.image = [UIImage imageNamed:model.imageName]; // Set image constraint CGFloat standardWidthHeightRatio = kMaxChatImageViewWidth/kMaxChatImageViewHeight; // Set image constraint CGFloat standardWidthHeightRatio = kMaxChatImageViewWidth/kMaxChatImageViewHeight; CGFloat widthHeightRatio = 0; UIImage *image = [UIImage imageNamed:model.imageName]; CGFloat h = image.size.height; CGFloat w = image.size.width; if (w > kMaxChatImageViewWidth || w > kMaxChatImageViewHeight) { widthHeightRatio = w / h; if (widthHeightRatio > standardWidthHeightRatio) { w = kMaxChatImageViewWidth; h = w * (image.size.height / image.size.width); } else { h = kMaxChatImageViewHeight; w = h * widthHeightRatio; } } self.messageImageView.size_sd = CGSizeMake(w, h); _container.sd_layout.widthIs(w).heightIs(h); / / set the container for messageImageView bottomView highly adaptive [_container setupAutoHeightWithBottomView: self. MessageImageView bottomMargin:kChatCellItemMargin]; / / container in accordance with the maskImageView tailoring self. Container. Layer. The mask = self. MaskImageView. Layer; __weak typeof(self) weakself = self; [_containerBackgroundImageView setDidFinishAutoLayoutBlock:^(CGRect frame) { // After the frame to determine _containerBackgroundImageView set size is equal to the size of containerBackgroundImageView maskImageView weakself.maskImageView.size_sd = frame.size; }]; } else if (model.text) {// Remove mask [_container.layer.mask removeFromSuperlayer]; self.messageImageView.hidden = YES; / / used when clear display pictures _containerBackgroundImageView didFinishAutoLayoutBlock _containerBackgroundImageView.didFinishAutoLayoutBlock = nil; _label.sd_resetLayout .leftSpaceToView(_container, kLabelMargin) .topSpaceToView(_container, kLabelTopMargin) .autoHeightRatio(0); Adaptive / / / / set the label vertically set label transverse adaptive [_label setSingleLineAutoResizeWithMaxWidth: kMaxContainerWidth]; / / the container label as rightView width adaptive [_container setupAutoWidthWithRightView: _label rightMargin: kLabelMargin]; / / the container label as bottomView highly adaptive [_container setupAutoHeightWithBottomView: _label bottomMargin: kLabelBottomMargin]; } } - (void)setMessageOriginWithModel:(GPChatData *)model { if (model.messageType == GPMessageTypeSendToOthers) { self.iconImageView.image = [UIImage imageNamed:@"001"]; Self.iconimageview.sd_resetlayout.rightspacetoview (self.contentView, kChatCellItemMargin) .topSpaceToView(self.contentView, kChatCellItemMargin) .widthIs(kChatCellIconImageViewWH) .heightIs(kChatCellIconImageViewWH); _container.sd_resetLayout.topEqualToView(self.iconImageView).rightSpaceToView(self.iconImageView, kChatCellItemMargin); _containerBackgroundImageView.image = [[UIImage imageNamed:@"SenderTextNodeBkg"] stretchableImageWithLeftCapWidth:50 topCapHeight:30]; } else if (model.messageType == GPMessageTypeSendToMe) { self.iconImageView.image = [UIImage imageNamed:@"003"]; Self.iconimageview.sd_resetlayout. leftSpaceToView(self.contentView, kChatCellItemMargin) .topSpaceToView(self.contentView, kChatCellItemMargin) .widthIs(kChatCellIconImageViewWH) .heightIs(kChatCellIconImageViewWH); _container.sd_resetLayout.topEqualToView(self.iconImageView).leftSpaceToView(self.iconImageView, kChatCellItemMargin); _containerBackgroundImageView.image = [[UIImage imageNamed:@"ReceiverTextNodeBkg"] stretchableImageWithLeftCapWidth:50 topCapHeight:30]; } _maskImageView.image = _containerBackgroundImageView.image; } #pragma mark - MLEmojiLabelDelegate - (void)mlEmojiLabel:(MLEmojiLabel *)emojiLabel didSelectLink:(NSString *)link withType:(MLEmojiLabelLinkType)type { if (self.didSelectLinkTextOperationBlock) { self.didSelectLinkTextOperationBlock(link, type); }}Copy the code
Focus on this interface, there is a pull-down spring effect, custom flow layout to achieve
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { CGFloat offsetY = self.collectionView.contentOffset.y; NSArray *attrsArray = [super layoutAttributesForElementsInRect:rect]; CGFloat collectionViewFrameHeight = self.collectionView.frame.size.height; CGFloat collectionViewContentHeight = self.collectionView.contentSize.height; CGFloat ScrollViewContentInsetBottom = self.collectionView.contentInset.bottom; CGFloat bottomOffset = offsetY + collectionViewFrameHeight - collectionViewContentHeight - ScrollViewContentInsetBottom; CGFloat numOfItems = [self.collectionView numberOfItemsInSection:nil]; for (UICollectionViewLayoutAttributes *attr in attrsArray) { if (attr.representedElementCategory == UICollectionElementCategoryCell) { CGRect cellRect = attr.frame; if (offsetY <= 0)="" {="" cgfloat="" distance="fabs(offsetY)" 8; ="" cellrect.origin.y="" +="offsetY" *="" (cgfloat)(attr.indexpath.section="" 1); ="" }else="" if="" (bottomoffset=""> 0 ){ CGFloat distance = bottomOffset / 8; cellRect.origin.y += bottomOffset - distance *(CGFloat)(numOfItems - attr.indexPath.section); } attr.frame = cellRect; } } return attrsArray; }Copy the code
Snip20160718_14.png
Also use UICoolectionView layout, more code can see the source code
# pragma mark - initialization - (instancetype) init {/ / water layout UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc] init]; layout.itemSize =[UIScreen mainScreen].bounds.size; layout.minimumLineSpacing = 0; layout.minimumInteritemSpacing = 0; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; return [self initWithCollectionViewLayout:layout]; } - (void)setupNav { self.collectionView.showsHorizontalScrollIndicator = NO; self.collectionView.pagingEnabled = YES; self.collectionView.bounces = NO; self.collectionView.backgroundColor = [UIColor whiteColor]; UIButton *disBtn = [[UIButton alloc]init]; [disBtn setImage:[UIImage imageNamed:@"Image"] forState:UIControlStateNormal]; disBtn.frame = CGRectMake(5, 25, 20, 20); [disBtn addTarget:self action:@selector(disBtnClick) forControlEvents:UIControlEventTouchUpInside]; [self.collectionView addSubview:disBtn]; } - (void)regisCell { [self.collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([GPDaRenStepOneCell class]) bundle:nil] forCellWithReuseIdentifier:OneIdentifier]; [self.collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([GPDaRenStepTwoCell class]) bundle:nil] forCellWithReuseIdentifier:TwoIdentifier]; [self.collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([GPDaRenStepThreeCell class]) bundle:nil] forCellWithReuseIdentifier:TheerIdentifier]; } - (void)loadData {// 1. Add NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"c"] = @"Course"; params[@"a"] = @"CourseDetial"; params[@"vid"] = @"18"; params[@"id"] = self.tagCpunt; __weak typeof(self) weakSelf = self; [GPHttpTool get:HomeBaseURl params:params success:^(id responder) {weakSelf. PicData = [GPDaRenPicData mj_objectWithKeyValues:responder[@"data"]]; weakSelf.stepArray = weakSelf.picData.step; weakSelf.stepToolsArray = weakSelf.picData.tools; weakSelf.stepMaetasArray = weakSelf.picData.material; weakSelf.stepPicArray = weakSelf.picData.step; [weakSelf.collectionView reloadData]; } failure:^(NSError *error) {[SVProgressHUD showErrorWithStatus:@" # "];}]; } #pragma mark - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 3; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { NSInteger row = 1; if (section == 2) { row = self.stepArray.count; } return row; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *collecTionCell = nil; if (indexPath.section == 0) { GPDaRenStepOneCell *oneCell = [collectionView dequeueReusableCellWithReuseIdentifier:OneIdentifier forIndexPath:indexPath]; oneCell.picData = self.picData; collecTionCell = oneCell; }else if (indexPath.section == 1){ GPDaRenStepTwoCell *twoCell = [collectionView dequeueReusableCellWithReuseIdentifier:TwoIdentifier forIndexPath:indexPath]; twoCell.toolsArray = self.stepToolsArray; twoCell.materiaArray = self.stepMaetasArray; collecTionCell = twoCell; }else{ GPDaRenStepThreeCell *threeCell = [collectionView dequeueReusableCellWithReuseIdentifier:TheerIdentifier forIndexPath:indexPath]; threeCell.sumNum = self.stepPicArray.count; threeCell.currentNum = indexPath.row + 1; threeCell.setpData = self.stepPicArray[indexPath.row]; threeCell.setpBtnClick = ^{ [self setpPicBtnClick]; }; collecTionCell = threeCell; } return collecTionCell; } # pragma mark - internal methods - (void) disBtnClick {[self dismissViewControllerAnimated: YES completion: nil]; } - (void)setpPicBtnClick { XWCoolAnimator *animator = [XWCoolAnimator xw_animatorWithType:XWCoolTransitionAnimatorTypePageFlip]; GPDaRenPicsController *picsVc = [[GPDaRenPicsController alloc]init]; picsVc.stepDataArray = self.stepPicArray; picsVc.picData = self.picData; [self xw_presentViewController:picsVc withAnimator:animator]; } -(void)scroolCollection:(NSNotification *)ifno { NSLog(@"%@",ifno.userInfo[@"pic"]); NSIndexPath *indexPath = ifno.userInfo[@"pic"]; CGPoint point = CGPointMake((indexPath.row + 2) * SCREEN_WIDTH, 0); [self.collectionView setContentOffset:point]; }Copy the code
Home page – Activity 01.gif
Home page – Activity 02.gif
The active interface seems to be a lot, but it is actually a child controller made by two UICollectionView, which is similar to the interface of friends circle. It is necessary to pay attention to off-screen rendering. Setting rounded corners directly will cause serious lag
#pragma mark - life cycle - (void)viewDidLoad {[super viewDidLoad]; [self regisCell]; [self configThame]; [self loadData]; Self. title = @" my work "; } - (void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; [SVProgressHUD dismiss]; } #pragma mark - (NSMutableArray *)picUrlS {if (! _picUrlS) { _picUrlS = [[NSMutableArray alloc] init]; } return _picUrlS; } - (NSMutableArray *)laudUrlS { if (! _laudUrlS) { _laudUrlS = [[NSMutableArray alloc] init]; } return _laudUrlS; } - (NSMutableArray *)sizeArray { if (! _sizeArray) { _sizeArray = [[NSMutableArray alloc] init]; } return _sizeArray; } #pragma mark - initialize - (void)regisCell {[self.tableView registerClass:[GPTimeLineHeadCell class] forCellReuseIdentifier:HeadCell]; [self.tableView registerClass:[GPTimeLineEventCell class] forCellReuseIdentifier:EventCell]; [self.tableView registerClass:[GPTimeLineApperCell class] forCellReuseIdentifier:ApperCell]; [self.tableView registerClass:[GPTimeLIneCommentCell class] forCellReuseIdentifier:CommentCell]; } - (void)configThame { self.view.backgroundColor = [UIColor whiteColor]; } #pragma mark - (void)loadData {// 1. NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"c"] = @"HandCircle"; params[@"a"] = @"info"; params[@"vid"] = @"18"; params[@"item_id"] = self.circleID; __weak typeof(self) weakSelf = self; [GPHttpTool get:HomeBaseURl params:params success:^(id responseObj) {weakself. timeLineData = [GPTimeLineData mj_objectWithKeyValues:responseObj[@"data"]]; / / scratchable latex pictures for (GPTimeLinePicData * PicData in weakSelf. TimeLineData. PIC) {[weakSelf. PicUrlS addObject: PicData. Url]; } / / only the size of a picture GPTimeLinePicData * picFistData = weakSelf timeLineData. PIC. FirstObject; [weakSelf.sizeArray addObjectsFromArray:@[picFistData.width,picFistData.height]]; / / thumb up head for (GPTimeLineLaudData * laudData in weakSelf. TimeLineData. Laud_list) {[weakSelf laudUrlS AddObject: laudData. Avatar];} / / comment weakSelf.com mentS = weakSelf.timeLineData.com ment; [weakSelf. TableView reloadData]; } failure:^(NSError *error) {[SVProgressHUD showErrorWithStatus:@"];}]; } # pragma mark - internal methods # pragma mark - the Table view data source - (NSInteger) numberOfSectionsInTableView: (UITableView *)tableView { return 4; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger sectionRow = 1; if (section == 3) { sectionRow = self.commentS.count; } return sectionRow; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { GPTimeLineHeadCell *headLineCell = [tableView dequeueReusableCellWithIdentifier:HeadCell]; headLineCell.sizeArray = self.sizeArray; headLineCell.timeLineData = self.timeLineData; headLineCell.picUrlArray = self.picUrlS; return headLineCell; }else if(indexPath.section == 1){ GPTimeLineEventCell *timeEventCell = [tableView dequeueReusableCellWithIdentifier:EventCell]; timeEventCell.lineData = self.timeLineData; timeEventCell.EventBtnClick = ^{ [self eventBtnClcik]; }; timeEventCell.backgroundColor = [UIColor whiteColor]; return timeEventCell; }else if (indexPath.section == 2){ GPTimeLineApperCell *timeApperCell = [tableView dequeueReusableCellWithIdentifier:ApperCell]; timeApperCell.laudnum = self.timeLineData.laud_num; timeApperCell.laudArray = self.laudUrlS; return timeApperCell; }else{ GPTimeLIneCommentCell *timeCommentCell = [tableView dequeueReusableCellWithIdentifier:CommentCell]; timeCommentCell.commentData = self.timeLineData.comment[indexPath.row]; return timeCommentCell; } } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [self cellHeightForIndexPath:indexPath cellContentViewWidth:SCREEN_WIDTH]; } #pragma mark - internal method - (void)eventBtnClcik {GPLoginController *loginVc = [UIStoryboard storyboardWithName:@"GPLoginController" bundle:nil].instantiateInitialViewController; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:loginVc]; self.transition = [[HYBEaseInOutTransition alloc] initWithPresented:^(UIViewController *presented, UIViewController *presenting, UIViewController *source, HYBBaseTransition *transition) { HYBEaseInOutTransition *modal = (HYBEaseInOutTransition *)transition; modal.animatedWithSpring = YES; } dismissed:^(UIViewController *dismissed, HYBBaseTransition *transition) { // do nothing }]; nav.transitioningDelegate = self.transition; [self presentViewController:nav animated:YES completion:NULL]; }Copy the code
The tutorial
Snip20160718_15.png
Snip20160718_16.png
- (void)viewDidLoad { [super viewDidLoad]; [self addNavTitleView]; [self addChildVc]; [self addConterView]; // [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(setGes) name:@"dawang" object:nil]; } - (void)dealloc{ [[NSNotificationCenter defaultCenter]removeObserver:self]; } #pragma mark - initialize - (void)addNavTitleView {__weak Typeof (self) weakSelf = self; GPNavTitleView *titleView = [[GPNavTitleView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH * 0.6, 44) block:^(UIButton *button) { [weakSelf.containView updateVCViewFromIndex:button.tag]; }]; self.titleView = titleView; self.navigationItem.titleView = titleView; } // Add child controller - (void)addChildVc {self.picVc = [[GPTutorialPicController alloc]init]; self.videoVc = [[GPTutoriaVideoController alloc]init]; self.subVc = [[GPTutoriSubController alloc]init]; self.chidVcArray = @[self.picVc,self.videoVc,self.subVc]; [self addChildViewController:self.picVc]; [self addChildViewController:self.videoVc]; [self addChildViewController:self.subVc]; } // Add a container - (void)addConterView {__weak Typeof (self) weakSelf = self; self.containView = [[GPContainerView alloc]initWithChildControllerS:self.chidVcArray selectBlock:^(int index) { [weakSelf.titleView updateSelecterToolsIndex:index]; }]; [self.view addSubview:self.containView]; self.containView.sd_layout.spaceToSuperView(UIEdgeInsetsZero); }Copy the code
Hand ring
Snip20160718_17.png
Here I use FMDB because I saved the moved data locally
+ (void) the initialize {/ / 1. Get the database file path nsstrings * doc = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString *filename = [doc stringByAppendingPathComponent:@"handMore.sqlite"]; / / 2. Get the database _db = [FMDatabase databaseWithPath: filename]; NSLog(@"%@",filename); If ([_db open]) {// 4. BOOL result = [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS T_MORE (ID INTEGER PRIMARY KEY AUTOINCREMENT, moreName blob NOT NULL,moreStr blob NOT NULL,Remark text NOT NULL)"]; BOOL zero = [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_zero (id integer PRIMARY KEY AUTOINCREMENT, moreName blob NOT NULL,moreStr blob NOT NULL,Remark text NOT NULL)"]; If (result &&zero) {NSLog(@" success table "); } else {NSLog(@" create table failed "); } } } + (void)saveItemArray:(NSMutableArray *)itemArray remark:(NSString *)remark type:(NSMutableArray *)strArray { NSData *nameData = [NSKeyedArchiver archivedDataWithRootObject:itemArray]; NSData *strData = [NSKeyedArchiver archivedDataWithRootObject:strArray]; [_db executeUpdateWithFormat:@"INSERT INTO t_more (moreName,moreStr,Remark) VALUES (%@, %@,%@)",nameData,strData,remark]; } + (void)saveZeroArray:(NSMutableArray *)itemArray remark:(NSString *)remark type:(NSMutableArray *)strArray { NSData *nameData = [NSKeyedArchiver archivedDataWithRootObject:itemArray]; NSData *strData = [NSKeyedArchiver archivedDataWithRootObject:strArray]; [_db executeUpdateWithFormat:@"INSERT INTO t_zero (moreName,moreStr,Remark) VALUES (%@, %@,%@)",nameData,strData,remark]; } + (BOOL)updateItemArray:(NSArray *)moreNameArray strArray:(NSArray *)moreStrArray remark:(NSString *)remark { NSData *nameData = [NSKeyedArchiver archivedDataWithRootObject:moreNameArray]; NSData *strData = [NSKeyedArchiver archivedDataWithRootObject:moreStrArray]; BOOL isSuccess = [_db executeUpdateWithFormat:@"UPDATE t_more SET moreName = %@,moreStr = %@ WHERE Remark = %@", nameData,strData,remark]; If (isSuccess) {NSLog(@" update successful "); }else{NSLog(@" update failed %@", _db.lasterrorMessage); } return isSuccess; } + (BOOL)updateZeroArray:(NSArray *)moreNameArray strArray:(NSArray *)moreStrArray remark:(NSString *)remark { NSData *nameData = [NSKeyedArchiver archivedDataWithRootObject:moreNameArray]; NSData *strData = [NSKeyedArchiver archivedDataWithRootObject:moreStrArray]; BOOL isSuccess = [_db executeUpdateWithFormat:@"UPDATE t_zero SET moreName = %@,moreStr = %@ WHERE Remark = %@", nameData,strData,remark]; If (isSuccess) {NSLog(@" update successful "); }else{NSLog(@" update failed %@", _db.lasterrorMessage); } return isSuccess; } + (NSMutableArray *)list:(NSString *)name { NSString *sql = [NSString stringWithFormat:@"SELECT * FROM t_more"]; FMResultSet *set = [_db executeQuery:sql]; NSMutableArray *list = [NSMutableArray array]; while (set.next) { NSData *item = [set objectForColumnName:name]; list = [NSKeyedUnarchiver unarchiveObjectWithData:item]; } return list; } + (NSMutableArray *)zeroList:(NSString *)name { NSString *sql = [NSString stringWithFormat:@"SELECT * FROM t_zero"]; FMResultSet *set = [_db executeQuery:sql]; NSMutableArray *list = [NSMutableArray array]; while (set.next) { NSData *item = [set objectForColumnName:name]; list = [NSKeyedUnarchiver unarchiveObjectWithData:item]; } return list; }Copy the code
The fair
This interface is relatively simple, is the simple use of UICollectionView C
- (void)loadNewData { GPFariParmer *parmers = [[GPFariParmer alloc]init]; parmers.c = @"Shiji"; parmers.vid = @"18"; parmers.a = self.product; __weak typeof(self) weakSelf = self; [GPFariNetwork fariDataWithParms:parmers success:^(GPFariData *fariData) { weakSelf.hotArray = [NSMutableArray arrayWithArray:fariData.hot]; weakSelf.bestArray = [NSMutableArray arrayWithArray:fariData.best]; weakSelf.topicBestArray = [NSMutableArray arrayWithArray:fariData.topicBest]; weakSelf.topicArray = [NSMutableArray arrayWithArray:fariData.topic]; GPFariTopicData *topicData = weakSelf.topicArray.lastObject; weakSelf.lastId = topicData.last_id; [weakSelf.collectionView reloadData]; [weakSelf.collectionView.mj_header endRefreshing]; } failuer:^(NSError *error) { [weakSelf.collectionView.mj_header endRefreshing]; [SVProgressHUD showErrorWithStatus:@" failed "];}]; } - (void)loadMoreData { GPFariParmer *parmers = [[GPFariParmer alloc]init]; parmers.c = @"Shiji"; parmers.vid = @"18"; parmers.last_id = self.lastId; parmers.a = @"topicList"; parmers.page = self.page; __weak typeof(self) weakSelf = self; [GPFariNetwork fariMoreDataWithParms:parmers success:^(NSArray *topicDataS) { [weakSelf.topicArray addObjectsFromArray:topicDataS]; [weakSelf.collectionView reloadData]; [weakSelf.collectionView.mj_footer endRefreshing]; } failuer:^(NSError *error) { [weakSelf.collectionView.mj_footer endRefreshing]; }]; } # pragma mark - UICollectionView data sources - (NSInteger) numberOfSectionsInCollectionView: (collectionView UICollectionView *) { return SectionCouton; }Copy the code
my
Snip20160718_18.png
This interface is common in any app, so instead of using static cells, a controller is encapsulated in pure code. Even in other projects, similar interfaces can be built quickly
- (NSMutableArray *)groups { if (_groups == nil) { _groups = [NSMutableArray array]; } return _groups; } - (instancetype)init { return [self initWithStyle:UITableViewStyleGrouped]; } / / return how many group - (NSInteger) numberOfSectionsInTableView tableView: (UITableView *) {return self. Groups. Count; } // Return the number of rows in each group - (NSInteger)tableView (UITableView *)tableView numberOfRowsInSection (NSInteger)section {// Get the current group model GPSettingGroup *group = self.groups[section]; return group.items.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 1. Create a cell GPSettingCell * cell = [GPSettingCell cellWithTableView: tableView style: UITableViewCellStyleValue1]; GPSettingGroup *group = self.groups[indexpath.section]; GPSettingItem *item = group.items[indepath.row]; // 2. Pass model cell.item = item; return cell; } // return the header title of each group - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {// get the group model GPSettingGroup *group = self.groups[section]; return group.header; } - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {// get GPSettingGroup *group = self.groups[section]; return group.footer; } // call - (void)tableView (UITableView *)tableView didSelectRowAtIndexPath (NSIndexPath *)indexPath {// GPSettingGroup *group = self.groups[indepath.section]; GPSettingItem *item = group.items[indepath.row]; if (item.operation) { item.operation(indexPath); return; } if ([item isKindOfClass:[GPSettingArrowItem class]]) {GPSettingArrowItem *arrowItem = (GPSettingArrowItem *)item; if (arrowItem.destVcClass == nil) return; UIViewController *vc = [[arrowitem.destVcClass alloc] init]; [self.navigationController pushViewController:vc animated:YES]; }}Copy the code
#import @interface GPSettingGroup: NSObject /** group header */ @property (nonatomic, copy) NSString *header; / / @property (nonatomic, copy) NSString *footer; /** * line model */ @property (nonatomic, strong) NSMutableArray *items; @endCopy the code
@implementation GPSettingCell + (instancetype)cellWithTableView:(UITableView *)tableView style:(UITableViewCellStyle)cellStyle { static NSString *ID = @"cell"; GPSettingCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) { cell = [[self alloc] initWithStyle:cellStyle reuseIdentifier:ID]; } return cell; } - (void)setItem:(GPSettingItem *)item { _item = item; [self setUpData]; [self setUpAccessoryView]; } // Set data - (void)setUpData {self.textlabel.text = _item.title; self.detailTextLabel.text = _item.subtitle; } // Set the right secondary view - (void)setUpAccessoryView {if ([_item isKindOfClass:[GPSettingArrowItem class]]) {// arrow self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; }else{ self.accessoryView = nil; self.accessoryType = UITableViewCellAccessoryNone; }}Copy the code
One last word
Due to the large amount of code, so or on the source code, source address