这周完成了3Gshare的仿写,学到了不少新的知识,也对之前的知识有了更多更深入的理解,这里来对这个项目的仿写做一下总结。
3Gshare要实现五个主页面:推荐页、搜索页、文章页、活动页、个人页。在进入这些主页面之前,要先完成启动页和登陆注册页面的仿写,这里按照从启动开始注册登录,登录后从左往右依次打开主页面的顺序来依次讲解
目录
启动页
首先是启动页
3Gshare启动页与笔者之前仿写的网易云音乐项目中启动页的思路一致,需要创建一个定时器,在一定时间后,将视图跳转到登录注册的界面,这里就不多赘述了
登录与注册
启动完毕后,程序会跳转到登录界面,但对登录界面的仿写与对注册界面的仿写密不可分,登录时需要获取注册界面注册的账密来判定登录是否成功,所以这里把登录与注册一起来讲解。
登录界面的布局就是两个文本输入框和两个按钮来确认登录和跳转到注册界面,还需要一个自动登录的按钮,这个按钮要实现两种状态的切换,这个通过一个按钮的事件来改变按钮在一般状态下的图片即可。
//自动登录按钮
UIButton* autologin = [UIButton buttonWithType:UIButtonTypeRoundedRect];
autologin.frame = CGRectMake(60, 538, 20, 20);
autologin.imageView.frame = CGRectMake(0, 0, 20, 20);
UIImage* uncheckedImage = [UIImage imageNamed:@"未选中.jpg"];
UIImage* checkImage = [UIImage imageNamed:@"选中.jpg"];
uncheckedImage = [uncheckedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
checkImage = [checkImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[autologin setImage:uncheckedImage forState:UIControlStateNormal];
autologin.tag = 1;
[autologin addTarget:self action:@selector(autoLoginButtonChange:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:autologin];
UILabel* autologinLabel = [[UILabel alloc] init];
autologinLabel.text = @"自动登录";
autologinLabel.frame = CGRectMake(84, 538, 100, 20);
autologinLabel.textColor = [UIColor blueColor];
[self.view addSubview:autologinLabel];
- (void)autoLoginButtonChange:(UIButton*)button {
if (button.tag == 1) {
button.tag = 0;
UIImage* checkedImage = [UIImage imageNamed:@"选中.jpg"];
checkedImage = [checkedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[button setImage:checkedImage forState:UIControlStateNormal];
} else if (button.tag == 0) {
button.tag = 1;
UIImage* uncheckedImage = [UIImage imageNamed:@"未选中.jpg"];
uncheckedImage = [uncheckedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[button setImage:uncheckedImage forState:UIControlStateNormal];
}
}
注册界面与登录界面的布局类似。
讲完两个界面的布局,来重点讲一下两个界面之间的传值
从注册界面讲起,在注册界面输入完账密后,按下注册按钮,将新注册的账密保存到当前视图控制器的属性中,接下来需要将账密传到登录界面,方便之后对登录进行判断,这里需要用到协议传值。而为了保存多套账密,我使用两个可变数组来保存账密数据。
@interface RegisterViewController : UIViewController <UITextFieldDelegate>
@property id<transfor> deligate;
@property UITextField* usernameTextfield;
@property UITextField* passwordTextfield;
@property UITextField* emailTextfield;
@property NSMutableArray* username;
@property NSMutableArray* password;
@end
- (void)confirm {
NSString* inputUsername = self.usernameTextfield.text;
//NSString* inputPassword = self.passwordTextfield.text;
BOOL finish = TRUE;
if ([self.usernameTextfield.text isEqualToString:@""] || [self.passwordTextfield.text isEqualToString:@""]) {
UIAlertController* alertFailed = [UIAlertController alertControllerWithTitle:@"失败!" message:@"用户名或密码不可以为空!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alertFailed addAction:confirm];
[self presentViewController:alertFailed animated:YES completion:nil];
finish = FALSE;
return;
}
for (int i = 0; i < self.username.count; i++) {
if ([inputUsername isEqualToString:self.username[i]]) {
UIAlertController* alertFailed = [UIAlertController alertControllerWithTitle:@"失败!" message:@"该用户名已存在!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alertFailed addAction:confirm];
[self presentViewController:alertFailed animated:YES completion:nil];
return;
}
}
if (finish == TRUE) {
[self.username addObject:self.usernameTextfield.text];
[self.password addObject:self.passwordTextfield.text];
[self.deligate transfor:self.username And:self.password];
UIAlertController* alertFinish = [UIAlertController alertControllerWithTitle:@"成功!" message:@"注册成功!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self.navigationController popViewControllerAnimated:YES];
}];
[alertFinish addAction:confirm];
[self presentViewController:alertFinish animated:YES completion:nil];
}
}
注意到在上述代码中,有一段是在对用户名数组遍历来检查重复注册,但是当注册完成退出当前注册界面后,该视图控制器负责保存用户名的数组属性就已经被销毁了,要如何保存已经注册过的用户名数据呢?只需要将传到登录界面的用户名数组在每一次打开注册界面时再传回注册界面就好了,这里使用属性传值。
RegisterViewController* registerViewController = [[RegisterViewController alloc] init];
registerViewController.deligate = self;
registerViewController.username = [[NSMutableArray alloc] init];
registerViewController.password = [[NSMutableArray alloc] init];
registerViewController.username = self.username;
registerViewController.password = self.password;
[self.navigationController pushViewController:registerViewController animated:YES];
最后在按下确定登陆后,对登录界面的用户名和密码数组遍历并对数组中每一个字符串与文本输入框中字符串做判断即可,如果登录成功,就可以将跳转到主界面,并将根视图改为分栏控制器了。
推荐页
第一个主界面是推荐页,推荐页的布局是一个数据视图,该数据视图有一个滚动视图的单元格和四个图文并茂的单元格,其中一个单元格要实现点击推出界面的效果,并且要实现两个界面点赞数的同步。
重点讲一下两个界面点赞数的传值,我在这里为点赞按钮设置标签,通过标签判断点赞后更新成哪个状态,而对于按钮标签,这里同样要通过属性传值和协议传值两种传值方式来分别实现从前向后传和从后向前传。
HomeBlockCell* cell = [self.tableView dequeueReusableCellWithIdentifier:@"HomeBlockCell"];
cell.titleLabel.text = self.array[indexPath.section - 1][0];
cell.writerLabel.text = self.array[indexPath.section - 1][1];
cell.sortLabel.text = self.array[indexPath.section - 1][2];
cell.timeLabel.text = self.array[indexPath.section - 1][3];
cell.imageView1.image = self.imageArray[indexPath.section - 1];
cell.button1.tag = self.heartTag;
cell.button2.tag = self.eyeTag;
cell.button3.tag = self.shareTag;
if (cell.button1.tag == 1 && indexPath.section == 1) {
[cell.button1 setTitle:@"102" forState:UIControlStateNormal];
UIImage* uncheckedHeartImage = [UIImage imageNamed:@"未点赞.jpg"];
uncheckedHeartImage = [uncheckedHeartImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[cell.button1 setImage:uncheckedHeartImage forState:UIControlStateNormal];
} else {
[cell.button1 setTitle:@"103" forState:UIControlStateNormal];
UIImage* heartImage = [UIImage imageNamed:@"点赞.jpg"];
heartImage = [heartImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[cell.button1 setImage:heartImage forState:UIControlStateNormal];
}
if (cell.button2.tag == 0) {
[cell.button2 setTitle:@"26" forState:UIControlStateNormal];
} else {
cell.button2.tag = 0;
[cell.button2 setTitle:@"27" forState:UIControlStateNormal];
}
if (cell.button3.tag == 0) {
[cell.button3 setTitle:@"20" forState:UIControlStateNormal];
} else {
[cell.button3 setTitle:@"21" forState:UIControlStateNormal];
}
if (indexPath.section == 1) {
[cell.button1 addTarget:self action:@selector(pressHeart:) forControlEvents:UIControlEventTouchUpInside];
[cell.button2 addTarget:self action:@selector(pressEye:) forControlEvents:UIControlEventTouchUpInside];
[cell.button3 addTarget:self action:@selector(pressShare:) forControlEvents:UIControlEventTouchUpInside];
}
cell.backgroundColor = [UIColor whiteColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
- (void)pressHeart:(UIButton*)button {
if (button.tag == 1) {
UIImage* heartImage = [UIImage imageNamed:@"点赞.jpg"];
heartImage = [heartImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[button setImage:heartImage forState:UIControlStateNormal];
button.tag = 0;
[button setTitle:@"103" forState:UIControlStateNormal];
} else {
UIImage* uncheckedHeartImage = [UIImage imageNamed:@"未点赞.jpg"];
uncheckedHeartImage = [uncheckedHeartImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[button setImage:uncheckedHeartImage forState:UIControlStateNormal];
button.tag = 1;
[button setTitle:@"102" forState:UIControlStateNormal];
}
self.heartTag = button.tag;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1) {
DetailViewController* detailViewController = [[DetailViewController alloc] init];
detailViewController.delegate = self;
detailViewController.heartTag = self.heartTag;
detailViewController.eyeTag = self.eyeTag;
detailViewController.shareTag = self.shareTag;
[self.navigationController pushViewController:detailViewController animated:YES];
}
}
详情页的点赞函数:
- (void)pressHeart:(UIButton*)button {
if (button.tag == 1) {
UIImage* heartImage = [UIImage imageNamed:@"点赞.jpg"];
heartImage = [heartImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
button.tag = 0;
[button setImage:heartImage forState:UIControlStateNormal];
[button setTitle:@"103" forState:UIControlStateNormal];
} else {
UIImage* uncheckedHeartImage = [UIImage imageNamed:@"未点赞.jpg"];
uncheckedHeartImage = [uncheckedHeartImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[button setImage:uncheckedHeartImage forState:UIControlStateNormal];
button.tag = 1;
[button setTitle:@"102" forState:UIControlStateNormal];
}
self.heartTag = button.tag;
[self.delegate pass:self.heartTag And:self.eyeTag And:self.shareTag];
}
搜索页
搜索页面的布局是一个button和一个imageView作为分隔线,下面放多个可以切换状态的按钮,需要有两个可弹出的界面,第一个是搜索框输入“大白”时弹出一个搜索结果的界面,第二个是上传界面
关于搜索结果的界面与推荐页数据视图的布局类似,而上传界面有两个比较重要的点,第一是照片墙的传值,要做到显示选择的第一张照片;第二是折叠cell,需要在点击右侧按钮时更新数据视图的大小,并且通过可变数组调整,调整数据视图的数据源,当选中某个cell时,将当前下标的元素删去,再添加到数组首。
照片墙与网易云音乐换头像的写法类似,添加通过数组判断首张图片的代码即可,这里给出折叠cell的代码
- (void)tapfoldButton:(UIButton*)button {
if (!button.tag) {
button.tag = 1;
self.tableView.frame = CGRectMake(250, 210, 110, 120);
} else {
button.tag = 0;
self.tableView.frame = CGRectMake(250, 210, 110, 30);
}
}
文章页
活动页面的布局是一个分栏控件加一个滚动视图,这部分与之前写过的zara的分类界面和网易云音乐的第二个界面的歌单部分类似,比较简单这里就不多说了
活动页
这个界面其实就是一个数据视图,单元格中需要一个图片和一个label来显示文字,这个界面比较简单
个人页
最后一个界面是个人页,这是最麻烦最繁琐的一个界面,要跳转的界面和要实现的功能很多,先说我的上传和我的推荐这两个比较简单的界面
这两个界面其实就是把之前写的界面搬了过来,只需要在点击时触发函数弹出界面就可以了
接下来说我的信息这个界面,这个界面要实现两个比较重要的点,一个是新推荐的,要在退出界面后依旧保存关注按钮的状态,再次点开界面与上一次的状态一致,再一个是私信界面,先弹出一个数据视图,点击后要实现一个聊天室。先讲解新关注的
这个界面要保留按钮的状态有两种思路,一种是通过协议和属性传值,将按钮的状态多次传递,但这里有多个按钮并且跨越三层视图,要实现非常麻烦,所以笔者最后采用了第二种思路也就是将子视图控制器作为父视图控制器的属性强引用,在创建时仿照单例的创建方式,如果为空则新创建一个实例,否则直接弹出之前的实例。
@property (strong, nonatomic)MymessageViewController* mymessageViewController;
@property (strong, nonatomic)SettingViewController* settingViewController;
if (self.mymessageViewController == nil) {
self.mymessageViewController = [[MymessageViewController alloc] init];
@property (strong, nonatomic)NewfansViewController* newfansViewController;
if (self.newfansViewController == nil) {
self.newfansViewController = [[NewfansViewController alloc] init];
接下来讲解私信功能和聊天室
首先点进私信后是一个很基础的数据视图,点击第一个单元格时跳转到私信界面,私信界面看起来复杂,其实就是一个可以实时变换的数据视图,这种数据视图后面在仿写学生信息管理系统时也会用到。
但是这里需要注意一下要对每次说的话也就是字符串进行尺寸大小的判定,判定大小来获得单元格的高度和气泡的高度。通过单元格行数来确定这次弹出的是谁发出的文本信息
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
} else {
while ([cell.contentView.subviews lastObject] != nil) {
[(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview];
}
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (indexPath.row % 2 != 0) {
UIImageView* other = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"关注头像2.jpg"]];
other.frame = CGRectMake(3, 15, 30, 30);
[cell.contentView addSubview:other];
//设置气泡
UIImageView* bubbleImageview = [[UIImageView alloc] init];
bubbleImageview.backgroundColor = [UIColor whiteColor];
//设置对话框
UILabel* label = [[UILabel alloc] init];
label.numberOfLines = 0;
label.text = self.array[indexPath.row];
label.font = [UIFont systemFontOfSize:18];
NSDictionary* atrribute = @{NSFontAttributeName:label.font};
CGSize size = [label.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width * 0.6, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:atrribute context:nil].size;
label.frame = CGRectMake(45, 20, size.width, size.height);
bubbleImageview.frame = CGRectMake(40, 10, size.width + 10, size.height + 20);
[cell.contentView addSubview:bubbleImageview];
[cell.contentView addSubview:label];
} else {
UIImageView* selfperson = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"关注头像1.jpg"]];
selfperson.frame = CGRectMake(360, 15, 30, 30);
[cell.contentView addSubview:selfperson];
//设置气泡
UIImageView* bubbleImageview = [[UIImageView alloc] init];
bubbleImageview.backgroundColor = [UIColor whiteColor];
//设置对话框
UILabel* label = [[UILabel alloc] init];
label.numberOfLines = 0;
label.text = self.array[indexPath.row];
label.font = [UIFont systemFontOfSize:18];
NSDictionary* atrribute = @{NSFontAttributeName:label.font};
CGSize size = [label.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width * 0.6, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:atrribute context:nil].size;
label.frame = CGRectMake([UIScreen mainScreen].bounds.size.width - size.width - 45, 20, size.width, size.height);
bubbleImageview.frame = CGRectMake([UIScreen mainScreen].bounds.size.width - size.width - 50, 10, size.width + 10, size.height + 20);
[cell.contentView addSubview:bubbleImageview];
[cell.contentView addSubview:label];
}
CGFloat red = 245.0 / 255.0;
CGFloat green = 245.0 / 255.0;
CGFloat blue = 245.0 / 255.0;
UIColor *backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
cell.backgroundColor = backgroundColor;
return cell;
}
- (void)send {
[self.array addObject:self.textfield.text];
NSDictionary* attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:18]};
CGSize size = [self.textfield.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width * 0.6, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:attribute context:nil].size;
int height = size.height + 10;
self.height = [NSNumber numberWithInt:height];
[self.heightArray addObject:self.height];
//插入一条新的cell
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:(self.array.count - 1) inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
self.textfield.text = @"";
}
这里每一次更新完数据视图,都要设置将数据视图拉到最底部,这样才能展示出最新发出的消息。
如下图:
讲完我的信息,接下来讲一下设置界面
这个界面要实现四个单元格的点击效果,分别是基本资料、修改密码、消息设置和清除缓存。虽然界面多但都比较简单。
基本资料界面主要要实现一个性别按钮只能选中一个,那么就在每次点击时对两个按钮的状态都进行更新就可以了。
修改密码界面要实现对两个文本框输入内容的判定,只有当两次密码输入一致时,才能允许修改密码
消息设置界面主要是要保存按钮的状态,与之前的关注界面类似
清除缓存点击后弹出一个警告提示框,用定时器在一定时间后移除该警告提示框即可。
总结
到这里就基本把3Gshare的所有界面都讲解完了,通过这个项目还是学到了很多新东西的,写网易云音乐时最大的收获是多个控件相互嵌套和夜间模式需要的多界面传值。3Gshare这个项目最大的收获就是聊天室所需要的实时更新数据视图,保留弹出的子视图和按钮状态的保持与更新。
希望接下来可以继续进步,学习更加深入