iOS实现一个包含若干页面和子页面的“打卡”App

iOS实现一个包含若干页面和子页面的“打卡”App

开发环境

  • Mac OS
  • Objective-C
  • Xcode

实验目的

  • 学习使用纯代码进行UI布局
  • 学习TableView,UICollectionView,UINavigationController,UICollectionController,UITabBarController等组件的使用,以及delegate和protocol的概念。
  • 学习使用UIView动画及Core Animation动画

项目实现

一、创建一个Xcode项目

点击File->New->Project,选择ios下的Single View App,创建一个名为clockin的项目。

二、项目结构

在这里插入图片描述

AppDelegate

由于该打卡App包含登录、发现、打卡等多个页面,所以需要多个类来标识不同的页面。通过一个底部导航栏来控制发现、打卡、我的三个页面之间的跳转。所以需要在AppDelegate文件中初始化一个tabBar控制器并设置该控制器为window的根控制器,再通过不同类创建不同的子控制器,分别是发现页、打卡页和“我的”页。将其添加进根控制器中,即可实现点击底部导航栏跳转到不同的页面。
此外,当每个tabbaritem处于选中/未选中状态时,图标的颜色应该是不同的,可以通过设置tabBarItem.selectedImage和tabBarItem.image,来使得选中时选择有颜色的图标,未选中时选择灰暗的图标。
相关代码

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // Override point for customization after application launch.
    //1.创建Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];

    //a.初始化一个tabBar控制器
    UITabBarController *tb=[[UITabBarController alloc]init];
    //设置控制器为Window的根控制器
    self.window.rootViewController=tb;

    //b.创建子控制器
    DiscoverViewController *c1=[[DiscoverViewController alloc]init];
    UINavigationController *c1Controller;
    c1Controller = [[UINavigationController alloc] initWithRootViewController:c1];
    c1Controller.tabBarItem.title = @"发现";
    c1Controller.tabBarItem.selectedImage = [UIImage imageNamed:@"33/discovery.png"];
    c1Controller.tabBarItem.image = [UIImage imageNamed:@"33/discovery2.png"];

    AddClockInViewController *c2=[[AddClockInViewController alloc]init];
    UINavigationController *c2Controller;
    c2Controller = [[UINavigationController alloc] initWithRootViewController:c2];
    c2Controller.tabBarItem.title = @"打卡";
    c2Controller.tabBarItem.selectedImage = [UIImage imageNamed:@"33/clock.png"];
    c2Controller.tabBarItem.image = [UIImage imageNamed:@"33/clock2.png"];
    
    SigninController *c3=[[SigninController alloc]init];
    UINavigationController *c3Controller;
    c3Controller = [[UINavigationController alloc] initWithRootViewController:c3];
    c3Controller.tabBarItem.title = @"我的";
    c3Controller.tabBarItem.selectedImage = [UIImage imageNamed:@"33/mine.png"];
    c3Controller.tabBarItem.image = [UIImage imageNamed:@"33/mine2.png"];
    
    tb.viewControllers=@[c1Controller,c2Controller,c3Controller];
    
    //2.设置Window为主窗口并显示出来
    [self.window makeKeyAndVisible];
    return YES;
}
SigninController 登录页

登录页要求很简单,只需有个圆形的登录按钮和一个背景渐变效果。点击登录按钮时需要跳转到个人信息页面。按钮事件相关代码如下:

[btn addTarget:self action:@selector(BtnClick:) forControlEvents:UIControlEventTouchUpInside];

-(void)BtnClick:(UIButton *)btn{
    PersonalMessageViewController *nextVC = [[PersonalMessageViewController alloc]init];
    NSMutableArray *v = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
    [v removeObjectAtIndex:0];
    [v addObject:nextVC];
    [self.navigationController setViewControllers:v animated:YES];//从当前界面到nextVC这个界面
}

设置圆形按钮的关键在于将按钮的layer的cornerRadius大小设置为Button宽高的一半。即若按钮的长和宽为200,则layer的cornerRadius应该设置为100。

PersonalMessageViewController 个人信息

该页面是登录后跳转的页面,显示用户的个人资料,包括头像、用户名、邮箱、电话。还有一些关于app的信息:版本号、隐私、cookie等。该页面是一个静态的页面,主要是调整view的大小和布局,将不同的文字信息放在不同的label中,再将属于同一个view的label集合在一块。该页的设计比较简单,只需注意一下字体大小、边框大小和颜色、各元素之间的位置即可。
完成效果大致如下:
在这里插入图片描述

Single 单实例模板类

该类中存储着一个NSMutableArray数据结构,名为datalist,其作用是存储发现页中时间、地点、景点、心得和图片。通过在不同的文件中调用该类的datalist,可以实现数据的传输。
Single.h

#import <UIKit/UIKit.h>
@interface Single:NSObject

@property(nonatomic, strong)NSMutableArray *datalist;
+(instancetype)sharedInstance;
@end

Single.m

#import "Single.h"
#import <Foundation/Foundation.h>
@implementation Single
-(id)init{
    if(self = [super init]){
        self->_datalist = [[NSMutableArray alloc]init];
    }
    return self;
}
+(instancetype)sharedInstance{
    static Single *myInstance = nil;
    if(myInstance == nil){
        myInstance = [[Single alloc]init];
    }
    return myInstance;
}

@end
DiscoverViewController 添加打卡页
  • 输入框

根据要求,用户可以在该页输入时间、地点、景点名称、旅游心得,并且还可以上传图片。由于前三个都是单行输入框,因此考虑用UITextField实现,旅游心得是多行输入框,用UITextView实现。对于UITextField,提示信息的设置只需设置其placeholder属性即可,而对于UITextView,没有设置提示信息的方法,因此必须手动设置。想法是在UITextView中添加一个label,当检测到UITextView中的text长度为0时,显示label中的提示信息,反之,将其隐藏。相应代码如下:

 - (void) textViewDidChange:(UITextView *)textView{
    if ([textView.text length] == 0) {
        [_label setHidden:NO];
    }else{
        [_label setHidden:YES];
    }
}
  • 图片的上传
    设置一个按钮,为按钮设置一个点击事件。点击时判断当前已加入的图片是否已达到九张,若达到则提示用户最多只能上传九张图片,反之,调用系统相册从中选取一张图片,将选取的图片在添加打卡页面上显示出来并将其添加到一个NSMutableArray数据结构中。
  • 发布按钮
    设置一个发布按钮,点击后,获取所有输入框中的数据和NSMutableArray中所存储的图片,将其添加到单实例类的datalist中,以便发现页可以显示数据。按钮事件如下:
-(void)BtnClick2:(UIButton *)btn{
    //datalist
    NSDictionary *temp = [[NSDictionary alloc]init];
    temp = @{@"data":_textField1.text,
             @"place":_textField2.text,
             @"spot":_textField3.text,
             @"acquaintance":_textview.text,
             @"image":_imagelist.mutableCopy
    };
    [[Single sharedInstance].datalist addObject:temp];
    [_imagelist removeAllObjects];
    self.count = 0;                         
    [self showDismissWithTitle:@"" message:@"发布成功" parent:self];
    self.tabBarController.selectedIndex = 0;//跳转到发现页
    [self loadView];
}

在这里插入图片描述
在这里插入图片描述

QueryInformationViewController 查询页

点击发现页中的打卡记录跳转到该页,显示不同的打卡信息。由于打卡信息有时间、地点、景点、心得和图片。所以,与添加打卡页类似,需要若干个label和一个textview来显示信息,还需要UIImageView来显示图片。声明所需要的变量如下:

@property(strong,nonatomic) UILabel *label1;
@property(strong,nonatomic) UILabel *label2;
@property(strong,nonatomic) UILabel *label3;
@property(strong,nonatomic) UITextView *textview;
@property(strong,nonatomic) UIView *viewww;
@property(strong,nonatomic) UIImageView *v;
DiscoverViewController 发现页
  • 背景渐变的实现
-(void)setBack{
    CAGradientLayer *l = [[CAGradientLayer alloc] init];
    l.colors = @[(__bridge id)UIColorFromRGB(0x87cefa).CGColor , (__bridge id)UIColorFromRGB(0xffc0cb).CGColor];
    l.startPoint = CGPointMake(0, 0);
    l.endPoint = CGPointMake(1, 1);
    l.frame = self.view.bounds;
    [self.view.layer addSublayer:l];
}
  • 搜索框的实现
    先声明一些属性:
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UISearchController *searchController;
// 搜索结果数组
@property (nonatomic, strong) NSMutableArray *results;

再初始化相关属性:

 - (UITableView *)tableView {
    if (_tableView == nil) {
        _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        [self.view addSubview:_tableView];
    }
    
    return _tableView;
}

 - (NSMutableArray *)results {
    if (_results == nil) {
        _results = [NSMutableArray arrayWithCapacity:0];
    }
    
    return _results;
}

创建UISearchController, 使用当前控制器来展示结果

    UISearchController *search = [[UISearchController alloc]initWithSearchResultsController:nil];
    search.searchResultsUpdater = self;
    search.obscuresBackgroundDuringPresentation = NO;
    self.searchController = search;
    self.tableView.tableHeaderView = search.searchBar;

再实现tableView的数据源及代理方法,由于tableview代理方法较长,代码就不贴了。并最后实现 UISearchController 的协议 UISearchResultsUpdating方法:

 - (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    
    NSString *inputStr = searchController.searchBar.text ;
    
    if (self.results.count > 0) {
        [self.results removeAllObjects];
    }
    for (NSDictionary *dictionary in [Single sharedInstance].datalist) {
        //NSString* str = [dictionary[@"date"] componentsJoinedByString:@""];
        NSString* str = dictionary[@"data"];
        NSString* str2 = dictionary[@"place"];
        NSString* str3 = dictionary[@"acquaintance"];
        str = [str stringByAppendingString:str2];
        str = [str stringByAppendingString:str3];
        if ([str.lowercaseString rangeOfString:inputStr.lowercaseString].location != NSNotFound) {
            [self.results addObject:dictionary];
        }
    }
    
    [self.tableView reloadData];
}

通过实现 UISearchController 的协议 UISearchResultsUpdating方法,可以实现对输入时间、地点对打卡信息进行快速检索。

  • 打卡记录排序
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"data" ascending:NO];
[[Single sharedInstance].datalist sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
  • 点击打卡记录跳转查看详细信息
    用QueryInformationViewController页面来展示信息,为了使得点击不同的打卡记录能显示不同的信息,需要找到该打卡记录所对应的datalist中所存储的信息。通过实现tabelview的代理方法:didSelectRowAtIndexPath,使得点击时获取点击位置并将datalist中所存储对应的数据传递给查询页,使得数据在查询页中显示。
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    CGRect screenFrame = [UIScreen mainScreen].bounds;
    int screenWidth = screenFrame.size.width;
    int screenHeight = screenFrame.size.height;
    QueryInformationViewController *newpage = [[QueryInformationViewController alloc] init];
    newpage.label1 = [[UILabel alloc]initWithFrame:CGRectMake(30, screenHeight/10, 100, 60)];
    newpage.label1.text = [Single sharedInstance].datalist[indexPath.section][@"data"];
    newpage.label2 = [[UILabel alloc]initWithFrame:CGRectMake(30, screenHeight/10+30, 100, 60)];
    newpage.label2.text = [Single sharedInstance].datalist[indexPath.section][@"place"];
    newpage.label3 = [[UILabel alloc]initWithFrame:CGRectMake(30, screenHeight/10+60, 100, 60)];
    newpage.label3.text = [Single sharedInstance].datalist[indexPath.section][@"spot"];
    newpage.textview = [[UITextView alloc]initWithFrame:CGRectMake(27, screenHeight/10+100, screenWidth/6 * 5, screenHeight/3)];
    [newpage.textview setText:[Single sharedInstance].datalist[indexPath.section][@"acquaintance"]];
    
    newpage.viewww = [[UIView alloc]init];
    
    NSMutableArray *ma = [Single sharedInstance].datalist[indexPath.section][@"image"];
        for(UIImage *image in ma){
            UIImageView *t = [[UIImageView alloc] initWithFrame:CGRectMake(40+75*(_count%3)-10, screenHeight/3 * 2+75*(_count/3)-50, 70, 70)];
            _count++;
            t.image = image;
            [newpage.viewww addSubview:t];
        }
    _count = 0;
    [self customPresentWith:@"cube"controller:newpage];//转场动画
}
  • 转场动画的实现
 - (void)customPresentWith:(NSString *)type
               controller:(QueryInformationViewController *)view{
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view];
    CATransition *animation = [CATransition animation];
    animation.duration = 1.0;
    animation.timingFunction = UIViewAnimationCurveEaseInOut;
    animation.type = type;
    animation.subtype = kCATransitionFromLeft;
    [self.view.window.layer addAnimation:animation forKey:nil];
    [self presentViewController:nav animated:YES completion:nil];
}
  • 值得注意
    由于发现页的数据会随着打卡数据的增加而增加,因此,该页面应该是动态刷新的,当添加打卡后返回发现页时,发现页应该要被刷新。因此,需要将动态更新的部分代码放进viewWillAppear中,而不是viewDidLoad。
    在这里插入图片描述
    按时间排序:
    在这里插入图片描述
    搜索:
    在这里插入图片描述
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值