该工程git仓库 https://gitee.com/zjf1998/DeviceManage
目 录
4.2实现HomeViewController,并使用xib适配 24
2.添加4个约束,使得这个tableView布满整个屏幕 36
3.新建一个viewController,同时添加一个webView,同样使其布满整个屏幕 37
4.最后在infoViewController中添加tableViewCell点击事件 37
1.创建ShoppingCartViewController并勾选xib文件 38
2.在xib文件中拖入一个tableView和一个view 39
1. iOS工程搭建
1.1新建SDSY项目
首先需要使用Xcode创建DeviceManager工程,选择iOS-Application-SingleViewApp,点击Next:
输入项目名称,组织名称,bundleid,和使用的语言。由于不要需要使用单元测试和CoreDate,所以去掉底部的三个勾,如图配置好以后,点击Finish:
1.2 installpod
本项目使用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
依次说明一下这几个第三方库的作用:
- AFNetworking:第三方网络连接库,在本项目中并不会拿AFNetworking直接访问网络,而是将AFNetworking封装以后使用。目的是为了封装BaseURL、封装解析类型、网络请求日志等等,具体封装过程会在之后章节介绍。
- SVProgressHUD:loadiong时显示的小圆圈,在耗时的网络请求中会使用SVProgressHUD显示loading界面。
- SDCycleScrollView:循环轮播图。如果自己用UIScrollView来实现轮播图,会出现滚动到最后一张图片的时候无法滚动到第一张图,形成循环轮播。想要自己实现也是可以的,但是比较复杂,这里直接使用第三方框架。
- SDAutoLayout:自动布局框架。和Masonry类似,SDAutoLayout主要用来布局Cell。底层使用AutoLayout引擎。
- MJRefresh:TableView的下拉刷新和上拉加载组件。用在需要手动下拉刷新,获取需要分页的TableView。
- DOPDropDownMenu-Enhanced:使用在TableView上,用来显示筛选或排序类型的组件,点击可以显示下拉选项。
- JPush:极光推送。一个第三方推送SDK,用来接收推送消息。可以对设备设置别名,分组,从而实现分组或对个别用户进消息推送。
- AMapLocation:高德地图定位组件,用于获取设备当前位置,可以根据需求设置定位精度,对设备进行单次定位或是持续定位,获取高德坐标。
- Masonry:自动布局框架。项目前期使用SDAutoLayout进行布局,由于Masonry布局支持iOS11的SafeArea新特性,所以项目后期会使用Masonry进行布局,SDAutoLayout主要用于Cell的布局。
- LANCategory:实用的Category。LANCategory封装了一些开发实用的Category,比如日期格式化、frame绝对布局工具、UIViw子视图管理等等分类。
保存Podfile后,在Podfile当前目录下执行pod install命令,安装第三方库。
如图所示安装成功后,pod会把第三方库和工程合并到一个workspace中,所以现在需要从DeviceManager.xcworkspace文件打开工程。打开后可以看到集成的第三方库:
需要使用第三方库的时候,只需要导入头文件,直接使用就可以了。项目直接把第三方库交给了CocoaPod管理了。
1.3 整理项目目录
一般iOS项目会使用MVC的设计模式进行开发,MVC的设计模式可以降低开发的耦合性、提高组件的重用性、提高可维护性,最重要的是可以使开发者条理清晰。Apple的UIKit等系统库都大量使用了MVC设计模式,所以本项目也将会在MVC的设计模式上进行开发。
首先需要整理项目目录,便于之后的开发和文件的分类。项目按照功能分类,在各个功能下分为三个模块:Model、Controller、View,在DeviceManager目录下创建好空文件夹,如下所示:
这里需要说明一下:
- 项目分为五个功能模块:首页、登录、购物车、咨询、我的。因为本项目使用MVC设计模式,所以每个功能模块下又分成M、V、C三个模块。
- 在Model文件夹中存放数据模型类,用来解析并转换JSON字符串为JSON对象;在View文件夹中存放视图类,用来显示UI;在Controller文件夹中存放控制器类,Controller是Model和View通信的桥梁,Controller通过请求获取数据并使用Model将JSON字符串转化为JSON对象传给View,用户对于View的操作,View会通知Controller,Controller处理以后改变Model。
- Framework(SDSY/Framework)文件夹中存放不支持CocoaPod的第三方库来手动管理。
- Tools(SDSY/Classes/Tools)文件夹中存放一些公共的工具类,比如:自定义网络框架、Category、全局定义、基础类(BaseClass)等等。
2. 封装AFNetworking
2.1 创建网络工具类
项目一般都会封装一层业务层的网络框架,这样可以统一请求网络请求接口,记录或打印网络日志,封装请求格式和解析格式等等。本项目底层网络框架采用AFNetworking,对AFNetworking进行了如下的业务封装:
- 单例方式获取网络工具类
- 封装AFNetworking两种请求方法(GET/POST)为一种,实现唯一的请求方法获取数据
- 封装请求的BaseURL
- 封装支持的解析类型(acceptableContentTypes)
- 封装可以手动关闭打开的打印网络请求日志,包括请求内容(请求类型、请求地址、请求内容)、请求获得的JSON、请求失败时的错误信息
- 封装返回的内容(服务器返回数据,请求错误等)
NetworkTool类就是本项目对AFNetworking的一层业务封装,具体代码如下:
NetworkTool.h(SDSY/SDSY/Classes/Tools(工具)/NetworkTool.h):
#import <AFNetworking/AFNetworking.h>
// 请求类型
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
对以上宏定义进行解释:
- BASE_URL:定义请求的基地址。
- ENABLE_JSON_LOG:打印网络请求获得的JSON串
- ENABLE_NETWORK_ERROR_LOG:打印网络请求发生的错误。
以上宏定义代码对当前的编译环境进了判断,如果当前是DEBUG环境,则开启JSON打印和网络错误日志,否则关闭两者打印。
2.2创建Model解析类
网络框架已经将请求服务器返回的JSON字符串转换为了NSDictionary,如果我们之间使用NSDictionary来存取数据会十分的不方便。比如:每次读取或者写入时都需要使用Key-Value的方式进行,而Value的类型是id类型,非常的不直观。如果使用对象作为数据模型就会使开发更方便,出错率更低。所以项目需要一个将JSON字符串转化为JSON对象的类。
本项目的数据模型解析类的构造如下:BasaModel类是所有数据模型的父类,BasaModel负责解析JSON字符串为子类的属性。具体实现如下,BaseModel.h(SDSY/SDSY/Classes/Tools(工具)/BaseModel.h):
#import <Foundation/Foundation.h>
#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 <Foundation/Foundation.h>
#import <objc/runtime.h>
@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<count; 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<outCount; 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<mothCout_f;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
3. 实现登录模块功能
3.1 创建用户信息模型UserInfoModel类
User登录使用的Model为UserInfoModel。该Model类封装了如下功能:
- 作为Model类最基本的用途,存储数据(学生ID,登录凭证,凭证过期时间等)
- 单例,在一个App的声明周期内存储用户登录信息
- 获取当前用户登录状态
- 退出登录
- 持久化储存用户登录信息,用于第二次打开App的自动登录
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<NSString *,id> *)keyedValues {
// 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
3.2 创建登录xib和Controller
需要实现的登录页面如下,用户输入学号和密码后,点击登录。APP调用登录接口,服务器根据登录学号与密码,返回相关登录信息,APP将登录信息解析为UserInfoModel模型:
本页面使用xib构建登录页面,Controller用来控制UI细节、网络请求和Model解析,首先创建xib:
LoginViewController.h(SDSY/SDSY/Classes/Login(登录)/Controller/LoginViewController.h):
#import <UIKit/UIKit.h>
@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 ()<UITextFieldDelegate,UIScrollViewDelegate>
@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<UITouch *> *)touches withEvent:(UIEvent *)event {
[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 <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CommonCrypto/CommonDigest.h>
@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。运行代码可以看到我们实现的界面了,并且能看见控制台如下输出:
4. 实现首页模块功能
4.1 实现TabBar功能
在这里需要实现,点击底部的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];
}
4.2实现HomeViewController,并使用xib适配
创建HomeViewController,同时勾选also create xib file
来到HomeViewController.xib,创建一个uiview视图,和一个tableView视图,
- 在右边的控制栏中设置好这两个个视图的在父视图中的位置
- 点击右下角,添加约束,轮播图的高度为200,所以让Device View距顶部的高度200,Device View的高度为170,其他的约束贴合边框即可。
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)
- 设置顶部轮播图,Height_NavBar代表了导航栏的高度
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 <UIKit/UIKit.h>
@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 <UIKit/UIKit.h>
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<Class> *)exceptViews;
@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<Class> *)exceptViews {
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<Class> *)classArray {
__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];
}
未添加等比例约束 添加等比例约束
5. 实现咨询模块功能
1.新建InfoViewController
2.添加上下左右4个约束0,使得这个tableView布满整个屏幕
3.新建一个viewController,同时添加一个webView,同样使其布满整个屏幕
4.最后在infoViewController中添加tableViewCell点击事件
//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];
}
6. 实现购物车模块功能
1.创建ShoppingCartViewController并勾选xib文件
2.在xib文件中拖入一个tableView和一个view
首先给tableView添加约束,让table距底部有100的距离。
然后让右键点击底部的的view拖到上面那个tableView,添加VerticalSpacing约束
添加后效果如下
3.适配iphoneX界面
约束使用代码控制,使用xib拖线的方式
//高度51 + 底部导航栏
_tableViewBottom.constant = Height_TabBar + 51;
这样一来,无论是普通的TabBar(高度49),还是iphoneX系列的TableBar(83)都可以适配,效果图:
4.创建UITableViewCell
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;
}