iOS 知识点iOS入门

04.项目实战 百思不得姐 登录界面补充,我的界面,设置界面清除

2016-01-25  本文已影响1060人  Liwx

@(iOS 项目实战)[项目实战]


目录


补充

约束和frame并用问题

只要修改了约束,就修改约束,如果使用frame,就直接修改frame.如果既用约束,又用frame,有可能会造成冲突.

多个对象拥有同样功能,抽取基类

如果很多对象都有同样的功能,可以考虑抽取基类

代理注意点

不要自己成为自己的代理,之后如果外部重新设置代理,会导致自己的代理功能失效.


1.登录/注册界面细节

设置UITextField的光标颜色

设置UITextField的主题颜色tintColor属性,将tintColor属性设置成白色,光标颜色就为白色.

设置UITextField的占位文字颜色

占位文字实现分析: 占位文字颜色在文本框开始编辑的时候变成白色,结束编辑的时候恢复原来的颜色.设置文本框占位文字颜色项目中多处会使用到,避免多处都要进行重复性设置,建议使用自定义TextField类,将设置占位文字颜色的功能封装到自定义TextField类中.

自定义文本框类实现修改文本框占位文字颜色和光标颜色

分析几种监听文本框的方式
- 监听文本框开始/结束编辑方式: 代理,通知,Target三种方式.
- 代理方式: 使用代理设置控件内部属性,如果外部重新设置了代理,会导致控件内部通过代理设置的功能失效.此场景不适合用代理方式监听.
- 通知方式: 使用通知的方式监听文本框开始/结束编辑.开始编辑(UITextFieldTextDidBeginEditingNotification)通知,结束编辑(UITextFieldTextDidEndEditingNotification)通知,文本改变(UITextFieldTextDidChangeNotification)通知.此场景也可以使用通知的方式监听.
- Target方式: 使用Target方式监听文本框开始/结束编辑.监听文本框的开始编辑事件UIControlEventEditingDidBegin结束编辑事件UIControlEventEditingDidEnd.此场景可以使用Target方式监听.

// ----------------------------------------------------------------------------
// 在awakeFromNib方法中设置文本框光标颜色和占位文字字体颜色
- (void)awakeFromNib
{
    // 1.设置光标颜色
    self.tintColor = [UIColor whiteColor];
    
    // 2.添加监听文本框开始编辑和结束编辑
    [self addTarget:self action:@selector(textBegin) forControlEvents:UIControlEventEditingDidBegin];
    [self addTarget:self action:@selector(textEnd) forControlEvents:UIControlEventEditingDidEnd];
    
    // 3.设置默认占位文字颜色为灰色
    // 此处需设置占位文字颜色.
}

#pragma =======================================================================
#pragma mark - 监听文本框事件
// ----------------------------------------------------------------------------
// 监听到文本框开始编辑
- (void)textBegin
{
    // 设置开始编辑时占位文字的颜色
}

// ----------------------------------------------------------------------------
// 监听到文本框结束编辑调用
- (void)textEnd
{
    // 设置结束编辑时占位文字的颜色
}

设置占位文字的场景: 默认情况的占位文字颜色,开始编辑时修改占位文字颜色,结束编辑时恢复占位文字颜色.

设置占位文字颜色分析
- 方式一: 通过修改UITextField的attributedPlaceholder属性.
- 方法二: 使用KVC方式获取占位文字的UILabel,获取到Label在设置Label的文字颜色.该方法有前提,必须在设置占位文字颜色前先设置占位文字,因为占位文本Label是使用懒加载,如果使用KVC方式获取占位文本Label时,有可能该Label还没创建,所以使用该方法必须确保占位文本Label已经创建.
- 方式三: 使用RunTime方式,通过分类实现设置文本框的占位文字颜色.


设置文本框占位文字颜色方式一
// ----------------------------------------------------------------------------
// TODO: TextField 设置文本框的占位文字颜色 方法一: 通过修改UITextField的attributedPlaceholder属性
// 在自定义TextField类中使用UITextField的attributedPlaceholder属性修改
- (void)setAttrPlaceholderColor:(UIColor *)placeholderColor
{
    // 1.设置富文本属性
    NSMutableDictionary *attrDict = [NSMutableDictionary dictionary];
    attrDict[NSForegroundColorAttributeName] = placeholderColor;
    
    // 2.创建富文本属性,并设置颜色
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attrDict];
    
    // 3.将富文本赋值给UITextField的attributedPlaceholder属性
    self.attributedPlaceholder = attributedString;
}

设置文本框占位文字颜色方式二

注意: OC系统自带控件中,所有的子控件都是懒加载.该方法有前提,必须在设置占位文字颜色前先设置占位文字,因为占位文本Label是使用懒加载,如果使用KVC方式获取占位文本Label时,有可能该Label还没创建,所以使用该方法必须确保占位文本Label已经创建.

// ----------------------------------------------------------------------------
// TODO: TextField 设置文本框的占位文字颜色 方法二: 使用KVC方式获取占位文字的UILabel,获取到Label在设置Label的文字颜色.
- (void)setKVCPlaceholderColor:(UIColor *)placeholderColor
{
    // 1.使用KVC获取文本框中的占位文字Label
    UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
    
    // 2.设置占位文字Label的颜色
    placeholderLabel.textColor = placeholderColor;
}

设置文本框占位文字颜色方式三

实现思路分析: 1.先保存占位文字颜色到系统类动态添加的属性. 2.取出动态添加的placeholderColor给系统的占位文字Label设置字体颜色


// ----------------------------------------------------------------------------
// TODO: TextField 方式三: 使用RunTime方式,通过分类设置文本框的占位文字颜色

// ----------------------------------------------------------------------------
// UITextField+PlaceholderColor.h文件
#import <UIKit/UIKit.h>

@interface UITextField (Placeholder)

/** 动态添加placeholderColor属性 */
@property UIColor *placeholderColor;

@end

// ----------------------------------------------------------------------------
// UITextField+PlaceholderColor.m文件
#import "UITextField+Placeholder.h"
#import <objc/message.h>

@implementation UITextField (PlaceholderColor)

#pragma =======================================================================
#pragma mark - RunTime实现交换方法

// ----------------------------------------------------------------------------
// 在load类方法中(类加载进内存的时候调用,只调用一次)
+ (void)load
{
    // 1.获取要交互的方法 Method是C语言结构体,不需要加*
    Method setPlaceholderMethod = class_getInstanceMethod(self, @selector(setPlaceholder:));
    Method wx_setPlaceholderMethod = class_getInstanceMethod(self, @selector(wx_setPlaceholder:));
    
    // 2.交换方法
    method_exchangeImplementations(setPlaceholderMethod, wx_setPlaceholderMethod);
}

// ----------------------------------------------------------------------------
// 给系统的方法添加设置占位文本颜色的功能
- (void)wx_setPlaceholder:(NSString *)placeholder
{
    // 1.调用交互方法,实际是调用setPlaceholderColor:方法
    [self wx_setPlaceholder:placeholder];
    
    // 2.设置保存的占位文字颜色
    self.placeholderColor = self.placeholderColor;
}

#pragma =======================================================================
#pragma mark - 使用RunTime实现关联动态添加的属性placeholderColor
// ----------------------------------------------------------------------------
// 重写set方法设置占位文本颜色
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    // 1.关联动态placeholderColor添加的属性,保存占位文字颜色到系统类动态添加的placeholderColor属性
    
    // object:保存到哪个对象中
    // key:属性名
    // value:属性值
    // policy:策略
    objc_setAssociatedObject(self, @"placeholderColor", placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    // 2.使用KVC获取占位文字的Label,并设置占位文字Label的字体颜色
    UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
    placeholderLabel.textColor = placeholderColor;
}

// ----------------------------------------------------------------------------
// 重写get方法获取placeholderColor关联的对象
- (UIColor *)placeholderColor
{
    return objc_getAssociatedObject(self, @"placeholderColor");
}
@end

2.我的界面搭建

只有storyboard才能设置静态单元格(静态cell),xib不能设置静态单元格.


storyboard搭建我的界面(静态cell)


我的界面底部板块界面



// ----------------------------------------------------------------------------
// 常量和宏
static NSString * const ID = @"cell";
static NSInteger const colCount = 4;
static CGFloat const margin = 1;
#define cellWH ((screenW - margin * (colCount - 1)) / colCount)

// ----------------------------------------------------------------------------
// 设置底部tableFooterView
- (void)setupTableFooterView
{
    // ------------------------------------------------------------------------
    // 1.创建流水布局,设置cell的尺寸和行列最小间距
    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.itemSize = CGSizeMake(cellWH, cellWH);
    flowLayout.minimumLineSpacing = margin;
    flowLayout.minimumInteritemSpacing = margin;
    
    // ------------------------------------------------------------------------
    // 2.创建collectionView,设置collectionView的属性
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 0, 0) collectionViewLayout:flowLayout];
    collectionView.backgroundColor = WXColor(206, 206, 206);
    collectionView.scrollEnabled = NO;
    self.collectionView = collectionView;
    
    // ------------------------------------------------------------------------
    // 3.设置collectionView的数据源和代理
    collectionView.dataSource = self;
    collectionView.delegate = self;
    self.tableView.tableFooterView = collectionView;
    
    // ------------------------------------------------------------------------
    // 4.注册cell
    [collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([WXSquareCell class]) bundle:nil] forCellWithReuseIdentifier:ID];
}


加载我板块cell网络数据

    // ----------------------------------------------------------------------------
    // 请求cell网络数据
    - (void)loadData
    {
        // 1.创建请求会话管理者
        AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
        
        // 2.拼接请求参数
        NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
        parameters[@"a"] = @"square";
        parameters[@"c"] = @"topic";
        
        // 3.发送请求
        [mgr GET:baseUrl parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            
            // 3.1 字典数组转模型数组
            self.squareList = [WXSquareItem mj_objectArrayWithKeyValuesArray:responseObject[@"square_list"]];
            
            // 3.2 处理请求的数据
            [self resolveData];
            
            // 3.3 刷新表格
            [self.collectionView reloadData];
            
            // 3.4 计算collectView的高度
            NSInteger count = self.squareList.count;
            // 3.4.1 计算行数
            NSInteger row = (count - 1) / colCount + 1;
            CGFloat collectionViewH = row * cellWH;
            self.collectionView.wx_height = collectionViewH;
            
            
            // TODO: 3.5 重新设置tableFooterView的显示内容,如果重新设置,会导致拖动到最底部cell会自动回弹
            self.tableView.tableFooterView = self.collectionView;
            
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"%@", error);
        }];
    }

作用: 主要是用于补齐格子,不然未填充的格子会变成灰色的效果.

- 处理请求的数据实现代码
  // ----------------------------------------------------------------------------
  // 处理请求的数据
  - (void)resolveData
  {
      // 如果数据不是4的倍数,添加到4的倍数.主要是用于补齐格子,不然未填充的格子会变成灰色的效果.
      NSInteger count = self.squareList.count;
      NSInteger extre = count % colCount;
      if (extre) {
          for (NSInteger i = 0; i < colCount - extre ; i++) {
              WXSquareItem *item = [[WXSquareItem alloc] init];
              [self.squareList addObject:item];
          }
      }
  }


3.展示网页的几种实现方法

展示网页: 1.WebView 2.openUrl
- WebView: 没有自带功能.好处: 就在当前应用下展示网页.弊端: webView不能监听进度条.
- safari: 自带了很多功能,弊端: 必须要跳转到其他应用.


SFSafariViewController实现浏览展示网页(iOS9之后才能使用)

// 使用SFSafariViewController需导入SafariServices/SafariServices.h头文件
#import <SafariServices/SafariServices.h>
// --------------------------------------------------------------------
// TODO: SFSafariViewController实现浏览展示网页
// 3.1 创建Safari网页控制器 iOS9才能用
SFSafariViewController *safariVc = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:item.url]];
safariVc.delegate = self;

// 3.2 跳转网页控制器
[self presentViewController:safariVc animated:YES completion:nil];
safariVc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:safariVc animated:YES];
#pragma =======================================================================
#pragma mark - SFSafariViewControllerDelegat代理协议

// ----------------------------------------------------------------------------
// TODO: SFSafariViewController 监听Safari点击完成按钮
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller;

// ----------------------------------------------------------------------------
// TODO: SFSafariViewController 监听Safari初始化载入完成
- (void)safariViewController:(SFSafariViewController *)controller didCompleteInitialLoad:(BOOL)didLoadSuccessfully;

WKWebView实现展示网页(iOS8之后才能使用)

4.设置界面


获取SDWebImage缓存大小

封装计算文件夹大小/移除文件夹所有文件业务类WXFileCacheManager

业务类: 常见业务类有网络请求类,处理文件缓存类.
业务类命名: 类名可以以manager结尾.


获取缓存大小原理(获取文件夹下所有文件的大小)

因计算文件夹大小在异步子线程中执行,为了获得异步子线程计算文件夹大小的结果,可以让封装的方法传入一个block参数,让子线程调用block方法,并将计算结果传递给外界.如果block内部有刷新UI操作,必须在主线程调用block.
异步方法不需要设置返回值.

- 主线程回调执行block参考代码

```objectivec
dispatch_async(dispatch_get_main_queue(), ^{
    if (completeBlock) {
        completeBlock(totalSize);
    }
});
```

// ----------------------------------------------------------------------------
// TODO: 获取文件夹大小
+ (void)getCacheSizeOfDirectoriesPath:(NSString *)directoriesPath completeBlock:(void(^)(NSInteger))completeBlock
{
    // ------------------------------------------------------------------------
    // 异步(子线程)计算文件大小,此操作比较耗时,所以放在子线程计算
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 1.创建文件管理者
        NSFileManager *mgr = [NSFileManager defaultManager];
        
        // TODO: 2.判断是否是文件夹
        BOOL isDirectory;
        // 返回值: 路径是否存在, isDirectory: 输出是否是文件夹(目录)
        BOOL isExists = [mgr fileExistsAtPath:directoriesPath isDirectory:&isDirectory];
        // 如果路径不存在或不是文件夹, 抛出异常
        if (!isExists || !isDirectory) {
            NSException *exp = [NSException exceptionWithName:@"directoriesPathError" reason:@"directoriesPath must be directory" userInfo:nil];
            [exp raise];
        }
        
        // 3.获取文件夹下的所有文件子路径
        NSArray *subPathArray = [mgr subpathsAtPath:directoriesPath];
        
        // --------------------------------------------------------------------
        // 4.遍历文件夹下的所有文件,累计文件夹下的所有文件大小
        NSInteger totalSize = 0;
        for (NSString *subPath in subPathArray) {
            
            // 4.1 拼接全路径
            NSString *fullPath = [directoriesPath stringByAppendingPathComponent:subPath];
            
            // 4.2 过滤隐藏文件和文件夹: 判断如果是隐藏文件带有DS字符串,路径不存在或不是文件夹路径,则跳过
            isExists = [mgr fileExistsAtPath:fullPath isDirectory:&isDirectory];
            if ([fullPath containsString:@"DS"] || !isExists || isDirectory) {
                continue;
            }
            
            // 4.3 获取文件属性
            NSDictionary *attr = [mgr attributesOfItemAtPath:fullPath error:nil];
            
            // 4.4 获取文件大小
            NSInteger fileSize = [attr[NSFileSize] integerValue];
            
            // 4.5 累计文件大小
            totalSize += fileSize;
            
        }
        
        // --------------------------------------------------------------------
        // 执行主线程回调,因为block有刷新UI操作,所以必须在主线程执行
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completeBlock) {
                completeBlock(totalSize);
            }
        });
        
    });
}

移除文件夹下所有文件


// ----------------------------------------------------------------------------
// TODO: 移除文件夹路径下的所有文件
+ (void)removeDirectoriesPath:(NSString *)directoriesPath completeBlock:(void(^)())completeBlock
{
    // ------------------------------------------------------------------------
    // 异步(子线程)移除,此操作比较耗时,所以放在子线程执行
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 1.创建文件管理者
        NSFileManager *mgr = [NSFileManager defaultManager];
        
        // 2.判断是否是文件夹
        BOOL isDirectory;
        BOOL isExists = [mgr fileExistsAtPath:directoriesPath isDirectory:&isDirectory];
        if (!isExists || !isDirectory) {
            NSException *exp = [NSException exceptionWithName:@"directoriesPathError" reason:@"directoriesPath must be directory" userInfo:nil];
            [exp raise];
        }
        
        // 3.获取路径下所有子路径
        NSArray *subPathArray = [mgr subpathsAtPath:directoriesPath];
        
        // 4.遍历移除文件夹下的所有文件
        for (NSString *subPath in subPathArray) {
            // 4.1 拼接全路径
            NSString *fullPath = [directoriesPath stringByAppendingPathComponent:subPath];
            
            // 4.2 移除文件
            [mgr removeItemAtPath:fullPath error:nil];
        }
        
        // 5.执行移除文件完成后要处理的操作.避免block里有执行UI操作,最好在主线程下执行
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completeBlock) {
                completeBlock();
            }
        });
    });
}
上一篇 下一篇

猜你喜欢

热点阅读