iOSApp设备管理制作过程(xib自动布局)

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

 

 

 

 

 

 

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

       依次说明一下这几个第三方库的作用:

  1. AFNetworking:第三方网络连接库,在本项目中并不会拿AFNetworking直接访问网络,而是将AFNetworking封装以后使用。目的是为了封装BaseURL、封装解析类型、网络请求日志等等,具体封装过程会在之后章节介绍。
  2. SVProgressHUD:loadiong时显示的小圆圈,在耗时的网络请求中会使用SVProgressHUD显示loading界面。
  3. SDCycleScrollView:循环轮播图。如果自己用UIScrollView来实现轮播图,会出现滚动到最后一张图片的时候无法滚动到第一张图,形成循环轮播。想要自己实现也是可以的,但是比较复杂,这里直接使用第三方框架。
  4. SDAutoLayout:自动布局框架。和Masonry类似,SDAutoLayout主要用来布局Cell。底层使用AutoLayout引擎。
  5. MJRefresh:TableView的下拉刷新和上拉加载组件。用在需要手动下拉刷新,获取需要分页的TableView。
  6. DOPDropDownMenu-Enhanced:使用在TableView上,用来显示筛选或排序类型的组件,点击可以显示下拉选项。
  7. JPush:极光推送。一个第三方推送SDK,用来接收推送消息。可以对设备设置别名,分组,从而实现分组或对个别用户进消息推送。
  8. AMapLocation:高德地图定位组件,用于获取设备当前位置,可以根据需求设置定位精度,对设备进行单次定位或是持续定位,获取高德坐标。
  9. Masonry:自动布局框架。项目前期使用SDAutoLayout进行布局,由于Masonry布局支持iOS11的SafeArea新特性,所以项目后期会使用Masonry进行布局,SDAutoLayout主要用于Cell的布局。
  10. 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目录下创建好空文件夹,如下所示:

       这里需要说明一下:

  1. 项目分为五个功能模块:首页、登录、购物车、咨询、我的。因为本项目使用MVC设计模式,所以每个功能模块下又分成M、V、C三个模块。
  2. 在Model文件夹中存放数据模型类,用来解析并转换JSON字符串为JSON对象;在View文件夹中存放视图类,用来显示UI;在Controller文件夹中存放控制器类,Controller是Model和View通信的桥梁,Controller通过请求获取数据并使用Model将JSON字符串转化为JSON对象传给View,用户对于View的操作,View会通知Controller,Controller处理以后改变Model。
  3. Framework(SDSY/Framework)文件夹中存放不支持CocoaPod的第三方库来手动管理。
  4. Tools(SDSY/Classes/Tools)文件夹中存放一些公共的工具类,比如:自定义网络框架、Category、全局定义、基础类(BaseClass)等等。

2. 封装AFNetworking

2.1 创建网络工具类

       项目一般都会封装一层业务层的网络框架,这样可以统一请求网络请求接口,记录或打印网络日志,封装请求格式和解析格式等等。本项目底层网络框架采用AFNetworking,对AFNetworking进行了如下的业务封装:

  1. 单例方式获取网络工具类
  2. 封装AFNetworking两种请求方法(GET/POST)为一种,实现唯一的请求方法获取数据
  3. 封装请求的BaseURL
  4. 封装支持的解析类型(acceptableContentTypes)
  5. 封装可以手动关闭打开的打印网络请求日志,包括请求内容(请求类型、请求地址、请求内容)、请求获得的JSON、请求失败时的错误信息
  6. 封装返回的内容(服务器返回数据,请求错误等)

       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

对以上宏定义进行解释:

  1. BASE_URL:定义请求的基地址。
  2. ENABLE_JSON_LOG:打印网络请求获得的JSON
  3. 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" -->"MMdd"

@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 = @"MMdd";

 

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类封装了如下功能:

  1. 作为Model类最基本的用途,存储数据(学生ID,登录凭证,凭证过期时间等)
  2. 单例,在一个App的声明周期内存储用户登录信息
  3. 获取当前用户登录状态
  4. 退出登录
  5. 持久化储存用户登录信息,用于第二次打开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 © 2018betterMan. 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启动后的rootViewControllerLoginViewController,待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视图,

  1. 在右边的控制栏中设置好这两个个视图的在父视图中的位置

  1. 点击右下角,添加约束,轮播图的高度为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)

  1. 设置顶部轮播图,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对象的subviewsconstraints,对目标进行等比例换算

 

 @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

// 以屏幕宽度为固定比例关系,来计算对应的值。假设:基准屏幕宽度375floatV=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;

}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值