iOSApp设备管理制作过程(xib自动布局)
该工程git仓库 https://gitee.com/zjf1998/DeviceManage
目 录
1. iOS工程搭建 2
1.1新建SDSY项目 2
1.2 installpod 3
1.3 整理项目目录 4
2. 封装AFNetworking 5
2.1 创建网络工具类 5
2.2创建Model解析类 9
3. 实现登录模块功能 12
3.1 创建用户信息模型UserInfoModel类 12
3.2 创建登录xib和Controller 17
4. 实现首页模块功能 24
4.1 实现TabBar功能 24
4.2实现HomeViewController,并使用xib适配 24
5. 实现咨询模块功能 36
1.新建InfoViewController 36
2.添加4个约束,使得这个tableView布满整个屏幕 36
3.新建一个viewController,同时添加一个webView,同样使其布满整个屏幕 37
4.最后在infoViewController中添加tableViewCell点击事件 37
6. 实现购物车模块功能 38
1.创建ShoppingCartViewController并勾选xib文件 38
2.在xib文件中拖入一个tableView和一个view 39
3.适配iphoneX界面 40
4.创建UITableViewCell 42
首先需要使用Xcode创建DeviceManager工程,选择iOS-Application-SingleViewApp,点击Next:
输入项目名称,组织名称,bundleid,和使用的语言。由于不要需要使用单元测试和CoreDate,所以去掉底部的三个勾,如图配置好以后,点击Finish:
本项目使用CocoaPod管理第三方框架,所以需要将Pod引入项目。
使用命令行,切换到项目根目录,使用如下命令创建Pod:
此时在工程目录下Pod会创建一个文件,名为Podfile。需工程需要导入的第三方框架就添加在这个文件当中。使用vim编辑Podfile,加入DeviceManager需要使用的第三方库,修改Podfile文件为:
target 'DeviceManager' do
# Pods for DeviceManager
pod 'AFNetworking' , '~> 3.1.0'
pod 'SVProgressHUD', '~> 2.1.2'
pod 'SDCycleScrollView', '~> 1.73'
pod 'SDAutoLayout'
pod 'MJRefresh'
pod 'DOPDropDownMenu-Enhanced'
pod 'JPush'
pod 'AMapLocation'
pod 'Masonry'
pod 'LANCategory'
end
依次说明一下这几个第三方库的作用:
保存Podfile后,在Podfile当前目录下执行pod install命令,安装第三方库。
如图所示安装成功后,pod会把第三方库和工程合并到一个workspace中,所以现在需要从DeviceManager.xcworkspace文件打开工程。打开后可以看到集成的第三方库:
需要使用第三方库的时候,只需要导入头文件,直接使用就可以了。项目直接把第三方库交给了CocoaPod管理了。
一般iOS项目会使用MVC的设计模式进行开发,MVC的设计模式可以降低开发的耦合性、提高组件的重用性、提高可维护性,最重要的是可以使开发者条理清晰。Apple的UIKit等系统库都大量使用了MVC设计模式,所以本项目也将会在MVC的设计模式上进行开发。
首先需要整理项目目录,便于之后的开发和文件的分类。项目按照功能分类,在各个功能下分为三个模块:Model、Controller、View,在DeviceManager目录下创建好空文件夹,如下所示:
这里需要说明一下:
项目一般都会封装一层业务层的网络框架,这样可以统一请求网络请求接口,记录或打印网络日志,封装请求格式和解析格式等等。本项目底层网络框架采用AFNetworking,对AFNetworking进行了如下的业务封装:
NetworkTool类就是本项目对AFNetworking的一层业务封装,具体代码如下:
NetworkTool.h(SDSY/SDSY/Classes/Tools(工具)/NetworkTool.h):
#import
// 请求类型
typedefenum {
GETType = 1,
POSTType = 2
}MethodType;
typedefvoid (^SuccessBlock)(NSDictionary *respondDictionary);
typedefvoid (^FailureBlock)(NSError *error);
@interface NetworkTool : AFHTTPSessionManager
// 返回单例对象
+(instancetype)shareInstance;
// 请求HTTP方法
-(void)requireMethodType:(MethodType)type
URLString:(NSString *)urlString
parameters:(NSDictionary *)parameters
success:(SuccessBlock)success
failure:(FailureBlock)failure;
@end
NetworkTool.m(SDSY/SDSY/Classes/Tools(工具)/NetworkTool.m):
#import "NetworkTool.h"
@implementation NetworkTool
staticNetworkTool *tool;
+(instancetype)shareInstance {
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
tool = [[NetworkTool alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]];
tool.responseSerializer.acceptableContentTypes = [NSSetsetWithObjects:@"text/html",@"application/json",@"text/json", @"text/plain", @"text/javascript", nil];
});
returntool;
}
-(void)requireMethodType:(MethodType)type
URLString:(NSString *)urlString
parameters:(NSDictionary *)parameters
success:(SuccessBlock)success
failure:(FailureBlock)failure {
// 打印请求
[selfprintNetworkLog:type URLString:urlString parameters:parameters];
NSString *fullURL = [selfgetFullRequestURL:urlString];
if (type==GETType) {
[selfGET:urlString parameters:parameters progress:nilsuccess:^(NSURLSessionDataTask * _Nonnull task, id_Nullable responseObject) {
if (ENABLE_JSON_LOG) {
NSLog(@"[url:%@] response:%@",fullURL,responseObject);
}
success(responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if(ENABLE_NETWORK_ERROR_LOG){
NSLog(@"[URL:%@] error:%@",fullURL,error);
}
failure(error);
}];
}else {
[selfPOST:urlString parameters:parameters progress:nilsuccess:^(NSURLSessionDataTask * _Nonnull task, id_Nullable responseObject) {
if (ENABLE_JSON_LOG) {
NSLog(@"[URL:%@] response:%@",fullURL,responseObject);
}
success(responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if(ENABLE_NETWORK_ERROR_LOG){
NSLog(@"[URL:%@] error:%@",fullURL,error);
}
failure(error);
}];
}
}
// 打印请求地址+参数
-(void)printNetworkLog:(MethodType)type URLString:(NSString *)urlString parameters:(NSDictionary *)parameters {
// 初始化字符串
NSString *proURLString = [selfgetFullRequestURL:urlString]; // 完整请求地址
NSString *typeString = [[NSStringalloc] init]; // 请求类型
NSMutableString *parametersString = [[NSMutableStringalloc] init]; // 请求参数
// 生成请求方法string
typeString = type == GETType ? @"GET" : @"POST";
// 检查是否存在参数
if (parameters){
// 生成请求参数string
for (NSString *allKey in [parameters allKeys]) {
[parametersString appendFormat:@"%@=%@, ",allKey,parameters[allKey]];
}
// 删除最后到 ", " 两个字符
[parametersString deleteCharactersInRange:NSMakeRange([parametersString length]-2, 2)];
}
NSString *request = [NSStringstringWithFormat:@"[%@] %@ {%@}",typeString,proURLString,parametersString];
NSLog(@"%@",request);
}
-(NSString *)getFullRequestURL:(NSString *)urlString {
returnself.baseURL ? [NSStringstringWithFormat:@"%@%@",self.baseURL,urlString] : urlString;
}
@end
注意到上面的几个宏定义,需要加入到SDSYGlobalDefine.h文件中:
// NetworkTool
#define BASE_URL @"http://aaa.zzjc.edu.cn/"
#ifdef DEBUG
// 打印JSON
#define ENABLE_JSON_LOG YES
// 网络请求错误log
#define ENABLE_NETWORK_ERROR_LOG YES
#else
// 打印JSON
#define ENABLE_JSON_LOG NO
// 网络请求错误log
#define ENABLE_NETWORK_ERROR_LOG NO
#endif
对以上宏定义进行解释:
以上宏定义代码对当前的编译环境进了判断,如果当前是DEBUG环境,则开启JSON打印和网络错误日志,否则关闭两者打印。
网络框架已经将请求服务器返回的JSON字符串转换为了NSDictionary,如果我们之间使用NSDictionary来存取数据会十分的不方便。比如:每次读取或者写入时都需要使用Key-Value的方式进行,而Value的类型是id类型,非常的不直观。如果使用对象作为数据模型就会使开发更方便,出错率更低。所以项目需要一个将JSON字符串转化为JSON对象的类。
本项目的数据模型解析类的构造如下:BasaModel类是所有数据模型的父类,BasaModel负责解析JSON字符串为子类的属性。具体实现如下,BaseModel.h(SDSY/SDSY/Classes/Tools(工具)/BaseModel.h):
#import
#import "NSObject+LAN.h"
// 用于字符串格式化 "yyyy-MM-dd" -->"MM月dd日"
@interface NSString(Format)
-(NSString *)formatDate;
@end
@interface BaseModel : NSObject
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
@end
BaseModel.m(SDSY/SDSY/Classes/Tools(工具)/BaseModel.m):
#import "BaseModel.h"
@implementation NSString(Format)
- (NSString *)formatDate {
// 检查字符串是否为空
if (self == nil || !self.length) {
returnnil;
}
// 设置输入格式
NSDateFormatter *inputFormatter = [[NSDateFormatteralloc] init];
inputFormatter.dateFormat = @"yyyy-MM-dd";
NSDate *formatDate = [inputFormatter dateFromString:self];
// 设置输出格式
NSDateFormatter *outputFormatter = [[NSDateFormatteralloc] init];
outputFormatter.dateFormat = @"MM月dd日";
return [outputFormatter stringFromDate:formatDate];
}
@end
@implementation BaseModel
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (self=[superinit]) {
// 检查是否是空的或不是字典
if(dictionary == nil || ![dictionary isKindOfClass:[NSDictionaryclass]]) {
[selfsetValuesForKeysWithDictionary:@{}];
returnself;
}
// 检查是否存在空的元素,如果存在NSNull 自动转换为 @""
[selfsetValuesForKeysWithDictionary:dictionary];
}
returnself;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {
#pragma mark 重写方法,防止报错
}
- (void)setNilValueForKey:(NSString *)key{}
// 重写打印描述
-(NSString *)description {
NSString *str = [NSString stringWithFormat:@"<%@>:%@",[self class],[self getAllPropertiesNameAndValue]];
return str;
}
@end
BaseModel类将JSON字符串转化为JSON对象的原理是,使用Key-Value对子类进行设置,并做相关的检查。在这里我们需要实现NSObject+LAN分类,该分类实现了用Runtime动态获取对象的属性列表和方法列表。BaseModel借助于NSObject+LAN分类,重写了-(NSString *)description方法,就可以打印出Model的所有属性了。
NSObject+LAN.h(SDSY/SDSY/Classes/Tools(工具)/Category/ NSObject+LAN.h):
#import
#import
@interface NSObject (LAN)
/**
* @brief 获取对象的所有属性,不包括属性值
*/
- (NSArray *)getAllPropertiesName;
/**
* @brief 获取对象的所有属性以及属性值
*/
- (NSDictionary *)getAllPropertiesNameAndValue;
/**
* @brief 打印对象的所有方法
*/
-(void)printMethodList;
@end
NSObject+LAN.m(SDSY/SDSY/Classes/Tools(工具)/Category/ NSObject+LAN.m):
#import "NSObject+LAN.h"
@implementation NSObject (LAN)
/* 获取对象的所有属性,不包括属性值 */
- (NSArray *)getAllPropertiesName
{
u_int count;
objc_property_t *properties =class_copyPropertyList([selfclass], &count);
NSMutableArray *propertiesArray = [NSMutableArrayarrayWithCapacity:count];
for (int i = 0; i
{
constchar* propertyName =property_getName(properties[i]);
[propertiesArray addObject: [NSStringstringWithUTF8String: propertyName]];
}
free(properties);
return propertiesArray;
}
/* 获取对象的所有属性以及属性值 */
- (NSDictionary *)getAllPropertiesNameAndValue
{
NSMutableDictionary *props = [NSMutableDictionarydictionary];
unsignedint outCount, i;
objc_property_t *properties = class_copyPropertyList([selfclass], &outCount);
for (i = 0; i
{
objc_property_t property = properties[i];
constchar* char_f =property_getName(property);
NSString *propertyName = [NSStringstringWithUTF8String:char_f];
id propertyValue = [selfvalueForKey:(NSString *)propertyName];
if (propertyValue) [props setObject:propertyValue forKey:propertyName];
}
free(properties);
return props;
}
/* 获取对象的所有方法 */
-(void)printMethodList
{
unsignedint mothCout_f =0;
Method* mothList_f = class_copyMethodList([selfclass],&mothCout_f);
for(int i=0;i
{
Method temp_f = mothList_f[i];
IMP imp_f = method_getImplementation(temp_f);
SEL name_f = method_getName(temp_f);
constchar* name_s =sel_getName(method_getName(temp_f));
int arguments = method_getNumberOfArguments(temp_f);
constchar* encoding =method_getTypeEncoding(temp_f);
NSLog(@"方法名:%@,参数个数:%d,编码方式:%@",[NSString stringWithUTF8String:name_s],
arguments,[NSStringstringWithUTF8String:encoding]);
}
free(mothList_f);
}
@end
User登录使用的Model为UserInfoModel。该Model类封装了如下功能:
UserInfoModel作为一个全局单例,控制用户的登录状态与用户登录信息。
UserInfoModel.h(SDSY/SDSY/Classes/Login(登录)/Model/UserInfoModel.h):
#import "BaseModel.h"
#import"UserModel.h"
@interface UserInfoModel : BaseModel<NSCoding>
// 属性,用setValue
@property (nonatomic,retain)NSString *expiration;
@property (nonatomic,retain)NSString *stu_id;
@property (nonatomic,retain)NSString *token;
@property (nonatomic, retain, readonly)NSString *deviceCode;
@property (nonatomic, retain)UserModel *deviceCode;
// 单例
+ (instancetype) shareInstance;
// 检查登录状态
- (BOOL) checkLogin;
// 退出登录
- (void) logout;
// 保存模型
-(void)saveToFile;
@end
UserInfoModel.m(SDSY/SDSY/Classes/Login(登录)/Model/UserInfoModel.m):
#import "UserInfoModel.h"
#import "JPUSHService.h"
typedef enum {
AutoLogin = 1, // 自动登录
GeneralLogin = 2, // 普通账号密码登录
NoToken = 3, // 不存在token
PastToken = 4, // token过期
}LoginType;
@implementation UserInfoModel
static UserInfoModel *userInfo;
+(instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
userInfo = [UserInfoModel readFromFile];
// 如果取不到文件,就从新创建
if (userInfo == nil) {
userInfo = [[UserInfoModel alloc] init];
}
});
return userInfo;
}
#pragma mark 准守NSCoding协议
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
self.expiration = [coder decodeObjectForKey:@"expiration"];
self.tid = [coder decodeObjectForKey:@"tid"];
self.token = [coder decodeObjectForKey:@"token"];
self.userModel = [coder decodeObjectForKey:@"userModel"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.expiration forKey:@"expiration"];
[coder encodeObject:self.tid forKey:@"tid"];
[coder encodeObject:self.token forKey:@"token"];
[coder encodeObject:self.userModel forKey:@"userModel"];
}
-(void)setValuesForKeysWithDictionary:(NSDictionary
// 1.设置值
[super setValuesForKeysWithDictionary:keyedValues];
// 2.保存数据
[self saveToFile];
// 3.手动登陆
[self loginLog:GeneralLogin];
}
#pragma mark - 自定义方法
// 保存数据
-(void)saveToFile {
[NSKeyedArchiver archiveRootObject:self toFile:[UserInfoModel savePath]];
}
-(BOOL)checkLogin {
// 1. 是否存在token
if (self.token==nil || [self.token length]==0) {
[self loginLog:NoToken];
return false;
}
// 2.token是否过期
// NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
// NSDate *expirationDate = [formatter dateFromString:self.expiration];
// if([expirationDate compare:[NSDate date]] == NSOrderedAscending) {
// // 升序,表示需要刷新token,重新登录
// [self loginLog:PastToken];
// return false;
// }
// 自动登录成功
[self loginLog:AutoLogin];
return true;
}
- (void)loginLog:(LoginType)type{
NSString *loginType = @"";
if (type == AutoLogin) {
// 自动登录
loginType = @"自动登录";
}else if(type == GeneralLogin) {
// 普通
loginType = @"账号密码登录";
}else if (type == NoToken) {
// 不存在token
loginType = @"不存在token,自动登录失败";
}else if (type == PastToken) {
// 不存在token
loginType = @"不存在token,自动登录失败";
}
NSLog(@"-------------------------------------------------");
NSLog(@"-------------------登录服务器信息-------------------");
NSLog(@"-------------------------------------------------");
NSLog(@"登录类型: %@",loginType);
NSLog(@"expiration: %@",self.expiration);
NSLog(@"tid: %@",self.tid);
NSLog(@"token: %@",self.token);
NSLog(@"保存路径: %@",[UserInfoModel savePath]);
NSLog(@"-------------------------------------------------");
NSLog(@"-------------------------------------------------");
}
- (void)logout {
NSFileManager *manager = [NSFileManager defaultManager];
// 1. 是否文件
if (![manager fileExistsAtPath:[UserInfoModel savePath]]) {
NSLog(@"没有找到token文件");
return ;
}
// 2.删除token信息
if([manager removeItemAtPath:[UserInfoModel savePath] error:nil]) {
NSLog(@"成功登出");
}
// 3.释放模型
userInfo = [[UserInfoModel alloc] init];
}
// 载入数据
+ (instancetype)readFromFile {
return [NSKeyedUnarchiver unarchiveObjectWithFile:[UserInfoModel savePath]];
}
+ (NSString *)savePath {
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
return [path stringByAppendingPathComponent:@"UserInfo.plist"];
}
#pragma mark - 重写方法
- (NSString *)deviceCode {
return [JPUSHService registrationID];
}
@end
需要实现的登录页面如下,用户输入学号和密码后,点击登录。APP调用登录接口,服务器根据登录学号与密码,返回相关登录信息,APP将登录信息解析为UserInfoModel模型:
本页面使用xib构建登录页面,Controller用来控制UI细节、网络请求和Model解析,首先创建xib:
LoginViewController.h(SDSY/SDSY/Classes/Login(登录)/Controller/LoginViewController.h):
#import
@interface LoginViewController : UIViewController
@end
LoginViewController.m(SDSY/SDSY/Classes/Login(登录)/Controller/LoginViewController.m):
//
// LoginViewController.m
// test_interface
//
// Created by sjl on 2018/10/19.
// Copyright © 2018年 betterMan. All rights reserved.
//
#import "LoginViewController.h"
#import "UserInfoModel.h"
#import "MainViewController.h"
#import "AppDelegate.h"
#import "JPUSHService.h"
#import "UserInfoModel.h"
@interface LoginViewController ()
@property (weak, nonatomic) IBOutlet UITextField *idTextField; // 学号id
@property (weak, nonatomic) IBOutlet UITextField *passwdTextField; // 密码
@property (weak, nonatomic) IBOutlet UIButton *loginBtn; // 登录按钮
@property (weak, nonatomic) IBOutlet UIView *labelBackView; // label背景
@end
@implementation LoginViewController
-(void)viewWillAppear:(BOOL)animated
{
//[[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:216/255.0f green:209/255.0f blue:192/255.0f alpha:1]];
self.navigationController.navigationBarHidden = YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 检查是否已经登录,如果已经登录,则进入主界面
if ([[UserInfoModel shareInstance] checkLogin] ) {
// 已登录
[self showMainViewController];
return;
}
// 调整登录按钮
self.loginBtn.layer.masksToBounds = true;
self.loginBtn.layer.cornerRadius = 10;
// 调整背景View
self.labelBackView.layer.masksToBounds = true;
self.labelBackView.layer.cornerRadius = 10;
self.labelBackView.layer.borderWidth = 0.5;
self.labelBackView.layer.borderColor = [UIColor grayColor].CGColor;
// textField
self.idTextField.delegate = self;
self.passwdTextField.delegate = self;
#pragma mark 测试账号自动填充
self.idTextField.text = @"tom";
self.passwdTextField.text = @"654321";
}
- (IBAction)loginBtnTapped:(id)sender {
[self login];
}
#pragma mark - 事件监听
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - 网络请求
// 登录
- (void)login {
[SVProgressHUD show];
// 初始化参数
NSDictionary *parameters = @{@"username": self.idTextField.text, @"password": self.passwdTextField.text };
[[NetworkTool shareInstance] requireMethodType:POSTType URLString:@"DeviceManage/loginValidate" parameters:parameters success:^(NSDictionary *respondDictionary) {
// 如果为空的模型,直接返回
if ([respondDictionary[@"result"][0] allKeys].count == 0) {
[SVProgressHUD dismiss];
[SVProgressHUD showErrorWithStatus:@"web接口服务连接失败,请确保主机ip地址是否正确,然后打开tomcat服务器"];
return;
}
// 设置用户模型
[[UserInfoModel shareInstance] setValuesForKeysWithDictionary:respondDictionary[@"result"][0]];
[SVProgressHUD dismiss];
[self showMainViewController];
} failure:^(NSError *error) {
[SVProgressHUD dismiss];
[SVProgressHUD showErrorWithStatus:@"web接口服务连接失败,请确保主机ip地址是否正确,然后打开tomcat服务器"];
}];
}
#pragma mark - 自定义方法
- (void)showMainViewController{
((AppDelegate *)[UIApplication sharedApplication].delegate).window.rootViewController = [[MainViewController alloc] init];
}
#pragma mark - UITextFieldDelegate协议
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField.tag == 1) {
// 学号textField,点击next,密码textField聚焦
[self.passwdTextField becomeFirstResponder];
}else {
// 密码textField,点击return,直接登录
[self login];
[self.idTextField resignFirstResponder];
[self.passwdTextField resignFirstResponder];
}
return true;
}
#pragma mark - 系统回调
-(void)touchesBegan:(NSSet
[self.idTextField resignFirstResponder];
[self.passwdTextField resignFirstResponder];
}
@end
这里有三点需要注意:
1. 需要导入网络框架NetworkTool,因为在整个项目中网络框架经常用到,所以导入网络框架最合适的地方应该是PCH文件中,所以我们需要在PrefixHeader.pch文件中加入如下代码:
#import "NetworkTool.h"
2.MainViewController的父类是UITabBarController,用来管理显示在主屏上的四个模块(首页、资讯、购物车、设置),在之后的章节会详细介绍。目前为了运行起来,可以先创建一个MainViewController类,继承于UITabBarController在/DeviceManage/DeviceManage/Classes目录下。
3. 可以注意到,在请求服务器登录的参数中,密码使用MD5处理以后发送给服务器,而- (NSString *)MD5String;方法在NSString+Category分类中实现,创建NSString+Category分类:
NSString+Category.h(/SDSY/SDSY/Classes/Tools(工具)/Category/NSString+Category.h):
#import
#import
#import
@interface NSString (Category)
//计算高度,限制width
- (CGFloat) heightWithFont:(UIFont*) font width:(CGFloat) width;
// 计算宽度
-(CGFloat)widthWithFont:(UIFont *)font;
//MD5加密
- (NSString *)MD5String;
//sha1加密
- (NSString *) sha1String;
// 转换日期
- (NSString *)formatDateWithInputFormat:(NSString *)inputformat outputFormat:(NSString *)outputFormat;
@end
NSString+Category.m(/SDSY/SDSY/Classes/Tools(工具)/Category/NSString+Category.m)
#import "NSString+Category.h"
@implementation NSString (Category)
- (CGFloat) heightWithFont:(UIFont*) font width:(CGFloat) width{
CGSize size = CGSizeMake(width, CGFLOAT_MAX);
size = [selfboundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeadingattributes:[NSDictionarydictionaryWithObject:font forKey:NSFontAttributeName] context:nil].size;
return size.height;
}
-(CGFloat)widthWithFont:(UIFont *)font {
NSMutableDictionary *dic = [NSMutableDictionarydictionaryWithObject:font forKey:NSFontAttributeName];
CGSize size = [selfboundingRectWithSize:CGSizeMake(MAXFLOAT, 0.0) options:NSStringDrawingUsesLineFragmentOriginattributes:dic context:nil].size;
return size.width;
}
- (NSString *)MD5String {
constchar *cstr = [selfUTF8String];
unsignedchar result[16];
CC_MD5(cstr, (CC_LONG)strlen(cstr), result);
return [NSStringstringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
- (NSString *) sha1String{
constchar *cstr = [selfcStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSDatadataWithBytes:cstr length:self.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (unsignedint)data.length, digest);
NSMutableString *outputStr = [NSMutableStringstringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i=0; i<CC_SHA1_DIGEST_LENGTH; i++) {
[outputStr appendFormat:@"%02x", digest[i]];
}
return outputStr;
}
-(NSString *)formatDateWithInputFormat:(NSString *)inputformat outputFormat:(NSString *)outputFormat {
NSDateFormatter *inputFormatter = [[NSDateFormatteralloc] init];
inputFormatter.dateFormat = inputformat;
NSDate *date = [inputFormatter dateFromString:self];
NSDateFormatter *outputFormatter = [[NSDateFormatteralloc] init];
outputFormatter.dateFormat = outputFormat;
return [outputFormatter stringFromDate:date];
}
@end
由于经常会使用到NSString+Category分类,所以将此分类加入PCH文件中:
#import "NSString+Category.h"
在App启动时,需要检查用户的登录状态,如果本地有用户登录的Token,并且Token未过期,代表当前用户的登录状态有效,展示主页;如果未能找到本地的用户信息,或者Token已失效,展示登录页面。本项目,将登录状态的检测与跳转的页面交给了LoginViewController类实现。在上面的LoginViewController.m的- (void)viewDidLoad的方法中有判断。所以,APP启动后的rootViewController为LoginViewController,待LoginViewController检查Token后,跳转对应的页面。
删除ViewController类,并在AppDelegate.m文件中修改- (BOOL)application: didFinishLaunchingWithOptions:方法为:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化窗体,设置第一个启动的Controller
self.window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
self.window.rootViewController = [[LoginViewControlleralloc] init];
[self.windowmakeKeyAndVisible];
returnYES;
}
将rootViewController指向了LoginViewController。运行代码可以看到我们实现的界面了,并且能看见控制台如下输出:
在这里需要实现,点击底部的TabBarItem切换四个模块的主要页面(首页、资讯、搜索、设置)。首先创建四个空的类,在之后会实现四个模块的代码:
/**
* @brief 设置TabBar的字控制器
*/
- (void)setupViewControllers {
// 1.设置首页导航控制器
HomeViewController *homeViewController = [[HomeViewController alloc] init];
UINavigationController *homeNavi = [[UINavigationController alloc] initWithRootViewController:homeViewController];
[self addChildViewController:homeNavi];
// 2.设置咨询导航控制器
InfoViewController *infoViewController = [[InfoViewController alloc] init];
UINavigationController *infoNavi = [[UINavigationController alloc] initWithRootViewController:infoViewController];
[self addChildViewController:infoNavi];
// 3.设置购物车导航控制器
ShoppingCartViewController *shoppingCartViewController = [[ShoppingCartViewController alloc] init];
UINavigationController *shopNavi = [[UINavigationController alloc] initWithRootViewController:shoppingCartViewController];
[self addChildViewController:shopNavi];
// 4.设置我的导航控制器
MineViewController *mineViewController = [[MineViewController alloc] init];
UINavigationController *mineNavi = [[UINavigationController alloc] initWithRootViewController:mineViewController];
[self addChildViewController:mineNavi];
}
创建HomeViewController,同时勾选also create xib file
来到HomeViewController.xib,创建一个uiview视图,和一个tableView视图,
3.添加宏判断,适配iphoneX系列
//判断是否是ipad
#define isiPad ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
//判断iPhone4系列
#define kiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iPhone5系列
#define kiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iPhone6系列
#define kiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iphone6+系列
#define kiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2208), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iPhoneX
#define IS_IPHONE_X ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iPHoneXr
#define IS_IPHONE_Xr ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(828, 1792), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iPhoneXs
#define IS_IPHONE_Xs ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//判断iPhoneXs Max
#define IS_IPHONE_Xs_Max ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2688), [[UIScreen mainScreen] currentMode].size) && !isiPad : NO)
//iPhoneX系列
#define Height_StatusBar ((IS_IPHONE_X==YES || IS_IPHONE_Xr ==YES || IS_IPHONE_Xs== YES || IS_IPHONE_Xs_Max== YES) ? 44.0 : 20.0)
#define Height_NavBar ((IS_IPHONE_X==YES || IS_IPHONE_Xr ==YES || IS_IPHONE_Xs== YES || IS_IPHONE_Xs_Max== YES) ? 88.0 : 64.0)
#define Height_TabBar ((IS_IPHONE_X==YES || IS_IPHONE_Xr ==YES || IS_IPHONE_Xs== YES || IS_IPHONE_Xs_Max== YES) ? 83.0 : 49.0)
self.bannerView=[SDCycleScrollView cycleScrollViewWithFrame:CGRectMake(0,Height_NavBar, SCREEN_WIDTH, 200) delegate:self placeholderImage:[UIImage imageNamed:@"Error"]];
4.创建设备分类按钮ClassifyButton类,借鉴与尚德书院活动分类
ClassifyButton用于显示设备分类(办公设备、生活实践等等),拥有一个居上的icon和一个在下的文字描述:
ClassifyButton.h (SDSY/SDSY/Classes/Home(首页)/View/ClassifyButton.h):
#import
@interface ClassifyButton : UIButton
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title image:(UIImage *)image;
@end
ClassifyButton.m(SDSY/SDSY/Classes/Home(首页)/View/ClassifyButton.m):
#import "ClassifyButton.h"
@implementation ClassifyButton
#pragma mark - 自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title image:(UIImage *)image {
if (self = [superinitWithFrame:frame]) {
// 设置Btn标题
[selfsetTitle:title forState:UIControlStateNormal];
// 设置title颜色
[selfsetTitleColor:[UIColorblackColor] forState:UIControlStateNormal];
// 设置title文字大小
self.titleLabel.font = [UIFontsystemFontOfSize:12];
// 设置Btn icon
[selfsetImage:image forState:UIControlStateNormal];
}
returnself;
}
#pragma mark - 系统回调方法
- (void)layoutSubviews {
[superlayoutSubviews];
// 1.调整image位置
CGRect imageFrame = self.imageView.frame;
imageFrame.origin.x = (self.frame.size.width - imageFrame.size.width) * 0.5;
imageFrame.origin.y = 5;
self.imageView.frame = imageFrame;
// 2.调整title位置
[self.titleLabelsizeToFit];
CGRect titleFrame = self.titleLabel.frame;
titleFrame.origin.x = (self.frame.size.width - titleFrame.size.width) * 0.5;
titleFrame.origin.y = self.imageView.bottomY + 5;
self.titleLabel.frame = titleFrame;
}
@end
按钮点击事件
// 设备分类按钮
- (void)classifyBtnTapped:(UIButton *)btn {
// 1.取出点击btn的标题
NSString *title = [btn titleForState:UIControlStateNormal];
NSString *type = @"";
// 2.检查哪个按钮被按下
if ([title isEqualToString:@"办公设备"]) {
[SVProgressHUD showInfoWithStatus:@"设备分类编号1被点击"];
[SVProgressHUD dismissWithDelay:1];
type = @"1";
}else if ([title isEqualToString:@"生活设备"]) {
[SVProgressHUD showInfoWithStatus:@"设备分类编号2被点击"];
[SVProgressHUD dismissWithDelay:1];
type = @"2";
}else if ([title isEqualToString:@"学习设备"]) {
[SVProgressHUD showInfoWithStatus:@"设备分类编号3被点击"];
[SVProgressHUD dismissWithDelay:1];
type = @"3";
}else if ([title isEqualToString:@"户外设备"]) {
[SVProgressHUD showInfoWithStatus:@"设备分类编号4被点击"];
[SVProgressHUD dismissWithDelay:1];
type = @"4";
}else if ([title isEqualToString:@"电子设备"]) {
[SVProgressHUD showInfoWithStatus:@"设备分类编号5被点击"];
[SVProgressHUD dismissWithDelay:1];
type = @"5";
}else if ([title isEqualToString:@"其他设备"]) {
[SVProgressHUD showInfoWithStatus:@"设备分类编号6被点击"];
[SVProgressHUD dismissWithDelay:1];
type = @"6";
}
//清空设备列表
self.deviceArray = [[NSMutableArray alloc] init];
//访问网络
NSDictionary *paramters = @{@"deviceClassId":type};
[[NetworkTool shareInstance] requireMethodType:POSTType URLString:@"DeviceManage/findDeviceByDeviceClassId" parameters:paramters success:^(NSDictionary *respondDictionary) {
for (NSDictionary *dic in respondDictionary[@"result"]) {
DeviceModel *model = [[DeviceModel alloc]initWithDictionary:dic];
[self.deviceArray addObject:model];
}
[self.tableView reloadData];
} failure:^(NSError *error) {
[SVProgressHUD showfailed];
}];
}
5.新建deviceTableViewCell
添加一系列控件用于制作tableViewCell
编写回调方法setModel
-(void)setModel:(DeviceModel *)model {
_model = model;
// 设备图片
if([self.model.DeviceName isEqualToString:@"打印机"])
[self.dev_image setImage:[UIImage imageNamed:@"printer"]];
else if([self.model.DeviceName isEqualToString:@"耳机"])
[self.dev_image setImage:[UIImage imageNamed:@"earphone"]];
else if([self.model.DeviceName isEqualToString:@"鼠标"])
[self.dev_image setImage:[UIImage imageNamed:@"mouse"]];
else if([self.model.DeviceName isEqualToString:@"笔记本电脑"])
[self.dev_image setImage:[UIImage imageNamed:@"computer"]];
else if([self.model.DeviceName isEqualToString:@"U盘"])
[self.dev_image setImage:[UIImage imageNamed:@"udisk"]];
else if([self.model.DeviceName isEqualToString:@"头盔"])
[self.dev_image setImage:[UIImage imageNamed:@"helmet"]];
// 设备名
self.dev_name.text = self.model.DeviceName;
[self.dev_name setNeedsLayout];
// 设备价格
self.dev_price.text=[NSString stringWithFormat:@"%ld",self.model.DevicePrice];
[self.dev_name setNeedsLayout];
6.编写购物车按钮的点击事件
- (IBAction)addShopingcart:(id)sender {
//使用NetworkTool会自动把返回值当成json串,而接口返回值是空,这里告诉AFNetworking:别把这个当json来处理!
NSDictionary *parameters = @{@"addDeviceID":[NSString stringWithFormat:@"%ld",self.model.DeviceID],@"addBuyNum":@"1",@"addUserID":[NSString stringWithFormat:@"%ld",[UserInfoModel shareInstance].UserID]};
AFHTTPSessionManager *manager =[AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager POST:[NSString stringWithFormat:@"%@addShopingcart",BASE_URL] parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
NSString *str = [NSString stringWithFormat:@"设备编号%ld加入购物车成功",self.model.DeviceID];
[SVProgressHUD showInfoWithStatus:str];
[SVProgressHUD dismissWithDelay:1];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[SVProgressHUD showfailed];
}];
7.运行结果
可以看到已经成功适配了iphoneX。
9.额外增加等比例约束
首先添加两个文件,然后在需要用的视图的viewDidLoad中加上一句话调用即可使用
在工具类中增加UIView类的扩展
UIView+AdaptScreenWidth.h
#import
typedef NS_ENUM(NSInteger, AdaptScreenWidthType) {
AdaptScreenWidthTypeConstraint = 1<<0, /**< 对约束的constant等比例 */
AdaptScreenWidthTypeFontSize = 1<<1, /**< 对字体等比例 */
AdaptScreenWidthTypeCornerRadius = 1<<2, /**< 对圆角等比例 */
AdaptScreenWidthTypeAll = 1<<3, /**< 对现有支持的属性等比例 */
};
@interface UIView (AdaptScreen)
/**
遍历当前view对象的subviews和constraints,对目标进行等比例换算
@param type 想要和基准屏幕等比例换算的属性类型
@param exceptViews 需要对哪些类进行例外
*/
- (void)adaptScreenWidthWithType:(AdaptScreenWidthType)type
exceptViews:(NSArray
@end
UIView+AdaptScreenWidth.m
//
// UIView+AdaptScreenWidth.m
// TestXib
//
// Created by lbs on 2018/8/14.
// Copyright © 2018年 by. All rights reserved.
//
#import "UIView+AdaptScreen.h"
// 基准屏幕宽度
#define kRefereWidth 375.0
// 以屏幕宽度为固定比例关系,来计算对应的值。假设:基准屏幕宽度375,floatV=10;当前屏幕宽度为750时,那么返回的值为20
#define AdaptW(floatValue) (floatValue*[[UIScreen mainScreen] bounds].size.width/kRefereWidth)
@implementation UIView (AdaptScreen)
- (void)adaptScreenWidthWithType:(AdaptScreenWidthType)type
exceptViews:(NSArray
if (![self isExceptViewClassWithClassArray:exceptViews]) {
// 是否要对约束进行等比例
BOOL adaptConstraint = ((type & AdaptScreenWidthTypeConstraint) || type == AdaptScreenWidthTypeAll);
// 是否对字体大小进行等比例
BOOL adaptFontSize = ((type & AdaptScreenWidthTypeFontSize) || type == AdaptScreenWidthTypeAll);
// 是否对圆角大小进行等比例
BOOL adaptCornerRadius = ((type & AdaptScreenWidthTypeCornerRadius) || type == AdaptScreenWidthTypeAll);
// 约束
if (adaptConstraint) {
[self.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull subConstraint, NSUInteger idx, BOOL * _Nonnull stop) {
subConstraint.constant = AdaptW(subConstraint.constant);
}];
}
// 字体大小
if (adaptFontSize) {
if ([self isKindOfClass:[UILabel class]] && ![self isKindOfClass:NSClassFromString(@"UIButtonLabel")]) {
UILabel *labelSelf = (UILabel *)self;
labelSelf.font = [UIFont systemFontOfSize:AdaptW(labelSelf.font.pointSize)];
}
else if ([self isKindOfClass:[UITextField class]]) {
UITextField *textFieldSelf = (UITextField *)self;
textFieldSelf.font = [UIFont systemFontOfSize:AdaptW(textFieldSelf.font.pointSize)];
}
else if ([self isKindOfClass:[UIButton class]]) {
UIButton *buttonSelf = (UIButton *)self;
buttonSelf.titleLabel.font = [UIFont systemFontOfSize:AdaptW(buttonSelf.titleLabel.font.pointSize)];
}
else if ([self isKindOfClass:[UITextView class]]) {
UITextView *textViewSelf = (UITextView *)self;
textViewSelf.font = [UIFont systemFontOfSize:AdaptW(textViewSelf.font.pointSize)];
}
}
// 圆角
if (adaptCornerRadius) {
if (self.layer.cornerRadius) {
self.layer.cornerRadius = AdaptW(self.layer.cornerRadius);
}
}
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) {
// 继续对子view操作
[subView adaptScreenWidthWithType:type exceptViews:exceptViews];
}];
}
}
// 当前view对象是否是例外的视图
- (BOOL)isExceptViewClassWithClassArray:(NSArray
__block BOOL isExcept = NO;
[classArray enumerateObjectsUsingBlock:^(Class _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([self isKindOfClass:obj]) {
isExcept = YES;
*stop = YES;
}
}];
return isExcept;
}
@end
最后,不管是用xib拖控件拉约束,还是用纯代码的形式写界面,只要在代码里对父视图调个方法就可以对其本身和子视图,进行约束和字体大小等比例换算了。例如对首页上所有的view进行等比例换算的布局
- (void)viewDidLoad {
[super viewDidLoad];
//等比例约束
[self.view adaptScreenWidthWithType:AdaptScreenWidthTypeAll exceptViews:nil];
//初始化视图
[self setupView];
}
未添加等比例约束 添加等比例约束
//tableView点击事件
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:true];
// 取出cell
InfoTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// 弹出咨询详情页
InfoDetailViewController *infoDetailViewController = [[InfoDetailViewController alloc] init];
infoDetailViewController.html = cell.model.InformationContent;
[self.navigationController pushViewController:infoDetailViewController animated:true];
}
首先给tableView添加约束,让table距底部有100的距离。
然后让右键点击底部的的view拖到上面那个tableView,添加VerticalSpacing约束
添加后效果如下
约束使用代码控制,使用xib拖线的方式
//高度51 + 底部导航栏
_tableViewBottom.constant = Height_TabBar + 51;
这样一来,无论是普通的TabBar(高度49),还是iphoneX系列的TableBar(83)都可以适配,效果图:
1.在xib文件中添加如下控件即可,因为是tableViewCell,控件大小相对固定,无需添加约束
2.重载setModel方法,设置每个tableViewCell控件的内容
-(void)setModel:(ShopListModel *)model {
_model = model;
// 商品名
self.shop_name.text = [self.model.Device valueForKey:@"DeviceName"];
[self.shop_name setNeedsLayout];
// 商品价格
self.shop_price.text = [self.model.Device valueForKey:@"DevicePrice"];
[self.shop_price setNeedsLayout];
// 设备图片
if([[self.model.Device valueForKey:@"DeviceName"] isEqualToString:@"打印机"])
[self.shop_image setImage:[UIImage imageNamed:@"printer"]];
else if([[self.model.Device valueForKey:@"DeviceName"] isEqualToString:@"耳机"])
[self.shop_image setImage:[UIImage imageNamed:@"earphone"]];
else if([[self.model.Device valueForKey:@"DeviceName"] isEqualToString:@"鼠标"])
[self.shop_image setImage:[UIImage imageNamed:@"mouse"]];
else if([[self.model.Device valueForKey:@"DeviceName"] isEqualToString:@"笔记本电脑"])
[self.shop_image setImage:[UIImage imageNamed:@"computer"]];
else if([[self.model.Device valueForKey:@"DeviceName"] isEqualToString:@"U盘"])
[self.shop_image setImage:[UIImage imageNamed:@"udisk"]];
else if([[self.model.Device valueForKey:@"DeviceName"] isEqualToString:@"头盔"])
[self.shop_image setImage:[UIImage imageNamed:@"helmet"]];
[self.shop_image setNeedsLayout];
//商品数量
self.shop_num.text = [NSString stringWithFormat:@"%ld",self.model.BuyNum];
[self.shop_num setNeedsLayout];
//设置该商品是否被选中图像
if(self.model.isChoice == false){
[self.shop_choice setImage:[UIImage imageNamed:@"choice"] forState:UIControlStateNormal];
}else{
[self.shop_choice setImage:[UIImage imageNamed:@"choiced"] forState:UIControlStateNormal];
}
}
3.以xib拖线的方式添加按钮点击事件
//增加购物车中商品数量
- (IBAction)addShop:(id)sender {
if(self.model.isChoice == false)
return;
NSInteger curBuynum = self.model.BuyNum;
curBuynum++;
self.model.BuyNum = curBuynum;
NSLog(@"设备编号%@购物车数量加1",[self.model.Device valueForKey:@"DeviceID"]);
//刷新视图
self.shop_num.text = [NSString stringWithFormat:@"%ld",self.model.BuyNum];
[self updateMoneySum];
}
//减少购物车中商品
- (IBAction)reduceShop:(id)sender {
if(self.model.isChoice == false)
return;
NSInteger curBuynum = self.model.BuyNum;
if(curBuynum > 0)
curBuynum--;
self.model.BuyNum = curBuynum;
NSLog(@"设备编号%@购物车数量减1",[self.model.Device valueForKey:@"DeviceID"]);
//刷新视图
self.shop_num.text = [NSString stringWithFormat:@"%ld",self.model.BuyNum];
[self updateMoneySum];
}
//选中或取消选中该商品
- (IBAction)shopChoice:(id)sender {
if(self.model.isChoice == false){
self.model.isChoice = true;
[self.shop_choice setImage:[UIImage imageNamed:@"choiced"] forState:UIControlStateNormal];
}
else{
self.model.isChoice = false;
[self.shop_choice setImage:[UIImage imageNamed:@"choice"] forState:UIControlStateNormal];
}
[self updateMoneySum];
}
4.在自定义cell里 获取其控制器viewController,获取控制器viewController并计算总价格
//获取控制器viewController并计算总价格
-(void)updateMoneySum{
ShoppingCartViewController *shoppingCartViewController = [self viewController];
NSInteger moneySum = 0;
for (int i=[shoppingCartViewController.shopArray count]-1; i>=0; i--)
{
ShopListModel *model = shoppingCartViewController.shopArray[i];
if(model.isChoice == true){
moneySum += model.BuyNum * [[model.Device valueForKey:@"DevicePrice"] intValue];
}
}
shoppingCartViewController.money.text = [NSString stringWithFormat:@"%ld",moneySum];
}
//在自定义cell里 获取其控制器viewController
- (UIViewController *)viewController
{
for (UIView* next = [self superview]; next; next = next.superview) {
UIResponder *nextResponder = [next nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)nextResponder;
}
}
return nil;
}
5.在tableView中指定按xib进行加载
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ShopTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"COMCELL"];
if (cell==nil) {
//xib布局
cell = [[NSBundle mainBundle] loadNibNamed:@"ShopTableViewCell" owner:nil options:nil].firstObject;
}
cell.model = self.shopArray[indexPath.row];
return cell;
}