swift设备管理制作过程(xib自动布局)
swift设备管理制作过程(xib自动布局)
该工程git仓库 https://gitee.com/zjf1998/DeviceManage-swift
目录
4.2实现HomeViewController,并使用xib适配 17
2.添加上下左右4个约束0,使得这个tableView布满整个屏幕 24
3.新建一个viewController,同时添加一个webView,同样使其布满整个屏幕 25
4.最后在infoViewController中添加tableViewCell点击事件 25
1.创建ShoppingCartViewController并勾选xib文件 26
2.在xib文件中拖入一个tableView和一个view 27
1. swift工程搭建
1.1新建DeviceManager-swift项目
首先需要使用Xcode创建DeviceManager-swift工程,选择iOS-Application-SingleViewApp,点击Next:
输入项目名称,组织名称,bundleid,和使用的语言。由于不要需要使用单元测试和CoreDate,所以去掉底部的三个勾,如图配置好以后,点击Finish:
1.2 installpod
本项目使用CocoaPod管理第三方框架,所以需要将Pod引入项目。
使用命令行,切换到项目根目录,使用如下命令创建Pod:
此时在工程目录下Pod会创建一个文件,名为Podfile。需工程需要导入的第三方框架就添加在这个文件当中。使用vim编辑Podfile,加入DeviceManage-swift需要使用的第三方库,修改Podfile文件为:
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
use_frameworks!
target 'DeviceManage-swift' do
# Pods for DeviceManage-swift
pod 'AFNetworking' , '~> 3.1.0'
pod 'SVProgressHUD'
pod 'SDCycleScrollView', '~> 1.73'
pod 'SDAutoLayout'
pod 'MJRefresh'
pod 'JPush'
pod 'AMapLocation'
pod 'SDWebImage'
pod 'SnapKit'
pod 'HandyJSON', '~> 4.2.0'
pod 'SwiftyJSON'
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:高德地图定位组件,用于获取设备当前位置,可以根据需求设置定位精度,对设备进行单次定位或是持续定位,获取高德坐标。
- SDWebImage:这个类库提供一个UIImageView类别以支持加载来自网络的远程图片。具有缓存管理、异步下载、同一个URL下载次数控制和优化等特征。
- SnapKit:一个经典的Swift版的第三方库,专门用于项目的自动布局。
- HandyJSON:采用Swift反射+内存赋值的方式来构造Model实例,保持原汁原味的Swift类定义。
- SwiftyJSON:SwiftyJSON是个使用Swift语言编写的开源库,可以让我们很方便地处理JSON数据(解析数据、生成数据)。
保存Podfile后,在Podfile当前目录下执行pod install命令,安装第三方库。
如图所示安装成功后,pod会把第三方库和工程合并到一个workspace中,所以现在需要从DeviceManage-swift.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.swift(SDSY/SDSY/Classes/Tools(工具)/NetworkTool.swift):
import Foundation
import AFNetworking
// 定义枚举类型
enum HTTPRequestType : Int{
case GET = 0
case POST
}
class NetworkTools: AFHTTPSessionManager {
// 设计单例 let是线程安全的
static let shareInstance : NetworkTools = {
let tools = NetworkTools()
tools.responseSerializer.acceptableContentTypes?.insert("text/html")
return tools
}()
}
// 封装请求方法
extension NetworkTools {
func request(methodType : HTTPRequestType, urlString : String, parameters : [String : AnyObject], finished :@escaping (_ result : AnyObject?, _ error : Error?)-> ()) {
// 1 成功回调
let successCallBack = {(task :URLSessionDataTask, result : Any) in
finished(result as AnyObject?, nil)
}
// 2 失败回调
let failureCallBack = {(task : URLSessionDataTask?, error :Error) in
finished(nil, error)
}
if methodType == .GET {
// get请求
get(urlString, parameters: parameters, progress: nil, success: successCallBack, failure: failureCallBack)
}else {
// post请求
post(urlString, parameters: parameters, progress: nil, success: successCallBack, failure: failureCallBack)
}
}
}
注意到上面的几个宏定义,需要加入到Const.swift文件中:
import UIKit
let BASE_URL = "http://192.168.0.108:8080/DeviceManage/"
对以上宏定义进行解释:
- BASE_URL:定义请求的基地址。
2.2创建Model解析类
网络框架已经将请求服务器返回的JSON字符串转换为了NSDictionary,如果我们之间使用NSDictionary来存取数据会十分的不方便。比如:每次读取或者写入时都需要使用Key-Value的方式进行,而Value的类型是id类型,非常的不直观。如果使用对象作为数据模型就会使开发更方便,出错率更低。所以项目需要一个将JSON字符串转化为JSON对象的类。
本项目的数据模型解析类的构造如下:BasaModel类是所有数据模型的父类,BasaModel负责解析JSON字符串为子类的属性。具体实现如下,BaseModel.h(SDSY/SDSY/Classes/Tools(工具)/BaseModel.swift):
import UIKit
import HandyJSON
class BaseModel: HandyJSON {
required init() {
}
}
1HandyJSON支持 JSON直接转Model,定义class时,有两点注意:
必须遵循HandyJSON协议
需要实现空的initializer (当然Struct结构体 可以不需要init())
3. 实现登录模块功能
3.1 创建用户信息模型UserInfoModel类
User登录使用的Model为UserInfoModel。该Model类封装了如下功能:
- 作为Model类最基本的用途,存储数据(学生ID,登录凭证,凭证过期时间等)
- 单例,在一个App的声明周期内存储用户登录信息
- 获取当前用户登录状态
- 退出登录
- 持久化储存用户登录信息,用于第二次打开App的自动登录
UserInfoModel作为一个全局单例,控制用户的登录状态与用户登录信息。
UserInfoModel.swift(SDSY/SDSY/Classes/Login(登录)/Model/UserInfoModel.swift):
import Foundation
import SwiftyJSON
enum LoginType:Int {
case AutoLogin = 1 // 自动登录
case GeneralLogin = 2 // 普通账号密码登录
case NoToken = 3 // 不存在token
case PastToken = 4 // token过期
}
class UserInfoModel:NSObject,NSCoding{
// 单例 - 避免重复从沙盒加载归档文件
static var shareInstance = UserInfoModel()
var UserID:Int?
var UserName:String?
var UserPassword:String?
//获取数据
override init() {
super.init()
let fileData = NSMutableData(contentsOfFile: self.savePath())
if fileData != nil {
let unarchiver = NSKeyedUnarchiver(forReadingWith: fileData! as Data)
let model = unarchiver.decodeObject(forKey: "userKey") as! UserInfoModel
UserID = model.UserID
UserName = model.UserName
UserPassword = model.UserPassword
unarchiver.finishDecoding()
}
}
//MARK:-自定义构造函数
init(dic:[String:JSON])
{
super.init()
UserID = dic["UserID"]?.intValue
UserName = dic["UserName"]?.stringValue
UserPassword = dic["UserPassword"]?.stringValue
}
//归档的方法
func encode(with aCoder: NSCoder) {
aCoder.encode(UserID, forKey: "UserID")
aCoder.encode(UserName, forKey: "UserName")
aCoder.encode(UserPassword, forKey: "UserPassword")
}
//解档的方法
required init?(coder aDecoder: NSCoder) {
super.init()
UserID = aDecoder.decodeObject(forKey: "UserID") as! Int?
UserName = aDecoder.decodeObject(forKey: "UserName") as! String?
UserPassword = aDecoder.decodeObject(forKey: "UserPassword") as! String?
}
//检查是否已登录
func checkLogin() -> Bool{
// 1. 是否存在token
if(UserPassword == nil || UserPassword?.count == 0){
self.loginLog(type: LoginType.NoToken);
return false;
}
// 2. 自动登录成功
self.loginLog(type: LoginType.AutoLogin);
return true;
}
//输出日志信息
func loginLog(type:LoginType){
var loginType:String = ""
if(type == LoginType.AutoLogin){
// 自动登录
loginType = "自动登录";
}else if(type == LoginType.GeneralLogin) {
// 普通
loginType = "账号密码登录";
}else if (type == LoginType.NoToken) {
// 不存在token
loginType = "不存在token,自动登录失败";
}else if (type == LoginType.PastToken) {
// 不存在token
loginType = "不存在token,自动登录失败";
}
print("-------------------------------------------------")
print("-------------------登录服务器信息-------------------")
print("-------------------------------------------------")
print("登录类型: %@",loginType)
if self.UserID != nil {
print("expiration: \(self.UserID!)")
}
if self.UserName != nil {
print("stu_id: \(self.UserName!)")
}
if self.UserPassword != nil {
print("token: \(self.UserPassword!)")
}
print("保存路径: %@",self.savePath())
print("-------------------------------------------------")
print("-------------------------------------------------")
}
// 保存数据
func saveToFile(model:UserInfoModel){
let data = NSMutableData()
let archive = NSKeyedArchiver(forWritingWith: data)
archive.encode(model, forKey: "userKey")
archive.finishEncoding()
data.write(toFile: self.savePath(), atomically: true)
}
//获取保存路径
func savePath() ->String {
// 1、获得沙盒的根路径
let home = NSHomeDirectory() as NSString
// 2、获得Documents路径
let docPath = home.appendingPathComponent("Documents") as NSString
// 3、获取文本文件路径
return docPath.appendingPathComponent("UserInfo.plist") as String
}
//删除信息
func logout(){
let manager:FileManager = FileManager.default
// 1. 是否文件
if(!manager.fileExists(atPath: self.savePath())){
print("没有找到token文件")
return
}
// 2.删除token信息
do{
try manager.removeItem(atPath: self.savePath())
} catch{
print("登出失败")
}
// 3.释放模型
UserInfoModel.shareInstance = UserInfoModel.init() ;
}
}
3.2 创建登录xib和Controller
需要实现的登录页面如下,用户输入学号和密码后,点击登录。APP调用登录接口,服务器根据登录学号与密码,返回相关登录信息,APP将登录信息解析为UserInfoModel模型:
本页面使用xib构建登录页面,Controller用来控制UI细节、网络请求和Model解析,首先创建xib:
import UIKit
import SwiftyJSON
import SVProgressHUD
class LoginViewController: UIViewController,UITextFieldDelegate,UIScrollViewDelegate {
// 学号id
@IBOutlet weak var idTextField: UITextField!
// 密码
@IBOutlet weak var passwdTextField: UITextField!
// 登录按钮
@IBOutlet weak var loginBtn: UIButton!
// label背景
@IBOutlet weak var labelBackView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// 检查是否已经登录,如果已经登录,则进入主界面
if (UserInfoModel.shareInstance.checkLogin() == true){
// 已登录
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.gray.cgColor
// textField
self.idTextField.delegate = self as UITextFieldDelegate;
self.passwdTextField.delegate = self as UITextFieldDelegate;
//测试账号自动填充
self.idTextField.text = "tom";
self.passwdTextField.text = "654321";
}
@IBAction func loginBtnTapped(_ sender: Any) {
self.login()
}
//登入
func login(){
SVProgressHUD.show()
let params = ["username":self.idTextField.text, "password": self.passwdTextField.text] // 请求参数
let loginUrl = BASE_URL+"loginValidate" // 请求地址
// 使用 AFNetworking 发送POST请求
NetworkTools.shareInstance.request(methodType: .POST, urlString: loginUrl, parameters: params as [String : AnyObject]) { (result : AnyObject?, error : Error?) in
if error != nil {
print(error!)
SVProgressHUD.dismiss()
SVProgressHUD.showError(withStatus: "web接口服务连接失败,请确保主机ip地址是否正确,然后打开tomcat服务器")
return
}
print(result!)
// 使用 SwiftyJSON 解析json -- 这里解析的是 jsonObject
// 如果要解析 jsonArray, SwiftyJSON 更加丝滑, 参考 http://www.hangge.com/blog/cache/detail_968.html
let json = JSON(result as Any)
if let dataDict = json["result"][0].dictionary {
// 字典转模型
let user : UserInfoModel = UserInfoModel.init(dic: (dataDict as [String : JSON] ))
// 设置用户模型
UserInfoModel.shareInstance.saveToFile(model: user)
self.showMainViewController();
}else{
SVProgressHUD.dismiss()
SVProgressHUD.showError(withStatus: "web接口服务连接失败,请确保主机ip地址是否正确,然后打开tomcat服务器")
}
}
}
//跳转到主界面
func showMainViewController(){
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window?.rootViewController = MainViewController()
}
//UITextFieldDelegate协议
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if (textField.tag == 1) {
// 学号textField,点击next,密码textField聚焦
self.passwdTextField.becomeFirstResponder();
}else {
// 密码textField,点击return,直接登录
self.login();
self.idTextField.resignFirstResponder();
self.passwdTextField.resignFirstResponder();
}
return true;
}
//系统回调
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.idTextField.resignFirstResponder();
self.passwdTextField.resignFirstResponder();
}
}
这里有三点需要注意:
1. 需要导入json解析框架SwiftyJSON
import SwiftyJSON
2.MainViewController的父类是UITabBarController,用来管理显示在主屏上的四个模块(首页、资讯、购物车、设置),在之后的章节会详细介绍。目前为了运行起来,可以先创建一个MainViewController类,继承于UITabBarController在/DeviceManage/DeviceManage/Classes目录下。
在App启动时,需要检查用户的登录状态,如果本地有用户登录的Token,并且Token未过期,代表当前用户的登录状态有效,展示主页;如果未能找到本地的用户信息,或者Token已失效,展示登录页面。本项目,将登录状态的检测与跳转的页面交给了LoginViewController类实现。在上面的LoginViewController.m的- (void)viewDidLoad的方法中有判断。所以,APP启动后的rootViewController为LoginViewController,待LoginViewController检查Token后,跳转对应的页面。
删除ViewController类,并在AppDelegate.swift文件中修改- (BOOL)application: didFinishLaunchingWithOptions:方法为:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//设置提示框最长显示时间
SVProgressHUD.setMaximumDismissTimeInterval(1.0)
//设置导航条点击颜色为橘色
UINavigationBar.appearance().tintColor = UIColor.orange
//设置底部tabBar颜色为橘色
UITabBar.appearance().tintColor = UIColor.orange
//设置导航栏标题颜色
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.orange]
//初始化窗体,设置第一个启动的Controller
window = UIWindow.init(frame: UIScreen.main.bounds)
window?.rootViewController = LoginViewController()
window?.makeKeyAndVisible()
return true
}
将rootViewController指向了LoginViewController。运行代码可以看到我们实现的界面了,并且能看见控制台如下输出:
4. 实现首页模块功能
4.1 实现TabBar功能
在这里需要实现,点击底部的TabBarItem切换四个模块的主要页面(首页、资讯、搜索、设置)。首先创建四个空的类,在之后会实现四个模块的代码:
func setupViewControllers(){
// 1.设置首页导航控制器
let homeViewController:HomeViewController = HomeViewController()
let homeNavi:UINavigationController = UINavigationController.init(rootViewController: homeViewController)
self.addChild(homeNavi)
// 2.设置咨询导航控制器
let infoViewController:InfoViewController = InfoViewController()
let infoNavi:UINavigationController = UINavigationController.init(rootViewController: infoViewController)
self.addChild(infoNavi)
// 3.设置购物车导航控制器
let shoppingCartViewController:ShoppingCartViewController = ShoppingCartViewController()
let shopNavi:UINavigationController = UINavigationController.init(rootViewController: shoppingCartViewController)
self.addChild(shopNavi)
// 4.设置我的导航控制器
let mineViewController:MineViewController = MineViewController()
let mineNavi:UINavigationController = UINavigationController.init(rootViewController: mineViewController)
self.addChild(mineNavi)
}
4.2实现HomeViewController,并使用xib适配
创建HomeViewController,同时勾选also create xib file
来到HomeViewController.xib,创建一个uiview视图,和一个tableView视图,
- 在右边的控制栏中设置好这两个个视图的在父视图中的位置
- 点击右下角,添加约束,轮播图的高度为200,所以让Device View距顶部的高度200,Device View的高度为170,其他的约束贴合边框即可。
3.添加宏判断,适配iphoneX系列
//屏幕尺寸定义和iPhoneX机型判断代码
let SCREEN_WIDTH = UIScreen.main.bounds.size.width
let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
//判断是否是ipad
let isiPad = (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad)
//判断iPhone4系列
let kiPhone4 = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 640, height: 960)))!) && isiPad
//判断iPhone5系列
let kiPhone5 = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 640, height: 1136)))!) && isiPad
//判断iPhone6系列
let kiPhone6 = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 750, height: 1334)))!) && isiPad
//判断iphone6+系列
let kiPhone6Plus = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 1242, height: 2208)))!) && isiPad
//判断iPhoneX
let IS_IPHONE_X = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 1125, height: 2436)))!) && isiPad
//判断iPHoneXr
let IS_IPHONE_Xr = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 828, height: 1792)))!) && isiPad
//判断iPhoneXs
let IS_IPHONE_Xs = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 1125, height: 2436)))!) && isiPad
//判断iPhoneXs Max
let IS_IPHONE_Xs_Max = ((UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 1242, height: 2688)))!) && isiPad
//iPhoneX系列
let Height_StatusBar = ((IS_IPHONE_X == true || IS_IPHONE_Xr == true || IS_IPHONE_Xs == true || IS_IPHONE_Xs_Max == true) ? 44.0 : 20.0)
let Height_NavBar = ((IS_IPHONE_X == true || IS_IPHONE_Xr == true || IS_IPHONE_Xs == true || IS_IPHONE_Xs_Max == true) ? 88.0 : 64.0)
let Height_TabBar = ((IS_IPHONE_X == true || IS_IPHONE_Xr == true || IS_IPHONE_Xs == true || IS_IPHONE_Xs_Max == true) ? 83.0 : 49.0)
- 设置顶部轮播图,Height_NavBar代表了导航栏的高度
//1.设置顶部轮播图
self.bannerView = SDCycleScrollView.init(frame: CGRect.init(x: 0.0, y: Height_NavBar, width: Double(SCREEN_WIDTH), height: 200.0), delegate: self, placeholderImage: UIImage.init(named: "Error"))
4.创建设备分类按钮ClassifyButton类,借鉴与尚德书院活动分类
ClassifyButton用于显示设备分类(办公设备、生活实践等等),拥有一个居上的icon和一个在下的文字描述:
import UIKit
class ClassifyButton: UIButton {
// MARK:- 自定义初始化方法
init(frame : CGRect,title : String,image: UIImage) {
super.init(frame: frame)
// 设置Btn标题
setTitle(title, for: .normal)
// 设置title颜色
setTitleColor(UIColor.black, for: .normal)
// 设置title文字大小
titleLabel?.font = UIFont.systemFont(ofSize: 12)
// 设置Btn icon
setImage(image, for: .normal)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
// 1.调整image位置
if(self.imageView != nil){
var imageFrame:CGRect = 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位置
if(self.titleLabel != nil && self.imageView != nil){
self.titleLabel!.sizeToFit()
var titleFrame:CGRect = self.titleLabel!.frame
titleFrame.origin.x = (self.frame.size.width - titleFrame.size.width) * 0.5
titleFrame.origin.y = self.imageView!.frame.maxY + 5
self.titleLabel!.frame = titleFrame
}
}
}
5.新建deviceTableViewCell
添加一系列控件用于制作tableViewCell
编写回调方法didSet
var model : DeviceModel? {
didSet{
// nil值校验
guard model != nil else {
return
}
// 设备图片
if self.model?.DeviceName == "打印机" {
self.dev_image.image = UIImage.init(named: "printer")
}
else if self.model?.DeviceName == "耳机" {
self.dev_image.image = UIImage.init(named: "earphone")
}
else if self.model?.DeviceName == "鼠标" {
self.dev_image.image = UIImage.init(named: "mouse")
}
else if self.model?.DeviceName == "笔记本电脑" {
self.dev_image.image = UIImage.init(named: "computer")
}
else if self.model?.DeviceName == "U盘" {
self.dev_image.image = UIImage.init(named: "udisk")
}
else if self.model?.DeviceName == "头盔" {
self.dev_image.image = UIImage.init(named: "helmet")
}
// 设备名
self.dev_name.text = self.model?.DeviceName
self.dev_name.setNeedsLayout()
// 设备价格
self.dev_price.text = self.model?.DevicePrice
self.dev_name.setNeedsLayout()
}
}
6.编写购物车按钮的点击事件
//添加商品到购物车
@IBAction func addShopingcart(_ sender: Any) {
let parameters:NSDictionary = ["addDeviceID":self.model?.DeviceID as Any,"addBuyNum":"1",
"addUserID":UserInfoModel.shareInstance.UserID as Any]
let manager:AFHTTPSessionManager = AFHTTPSessionManager.init()
manager.responseSerializer = AFHTTPResponseSerializer.init()
manager.post(BASE_URL+"addShopingcart", parameters: parameters, progress: { (Progress) in
}, success: { (task:URLSessionDataTask, responseObject:Any?) in
if(self.model?.DeviceID != nil){
let str:String = "设备编号\(self.model!.DeviceID)加入购物车成功"
SVProgressHUD.showInfo(withStatus: str)
SVProgressHUD.dismiss(withDelay: 1)
}
})
}
7.运行结果
可以看到已经成功适配了iphoneX。
5. 实现咨询模块功能
1.新建InfoViewController
2.添加上下左右4个约束0,使得这个tableView布满整个屏幕
3.新建一个viewController,同时添加一个webView,同样使其布满整个屏幕
4.最后在infoViewController中添加tableViewCell点击事件
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// 取出cell
guard let cell:InfoTableViewCell = tableView.cellForRow(at: indexPath) as? InfoTableViewCell else{
return
}
// 弹出咨询详情页
let infoDetailViewController:InfoDetailViewController = InfoDetailViewController()
guard let content = cell.model?.InformationContent! else {
return
}
infoDetailViewController.html = content
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.设置 didSet方法,设置每个tableViewCell控件的内容
var model:ShopListModel? {
didSet{
// nil值校验
guard model != nil else {
return
}
// 商品名
self.shop_name.text = self.model?.Device?.DeviceName
self.shop_name.setNeedsLayout()
// 商品价格
self.shop_price.text = self.model?.Device?.DevicePrice
self.shop_price.setNeedsLayout()
// 设备图片
if self.model?.Device?.DeviceName == "打印机" {
self.shop_image.image = UIImage.init(named: "printer")
}
else if self.model?.Device?.DeviceName == "耳机" {
self.shop_image.image = UIImage.init(named: "earphone")
}
else if self.model?.Device?.DeviceName == "鼠标" {
self.shop_image.image = UIImage.init(named: "mouse")
}
else if self.model?.Device?.DeviceName == "笔记本电脑" {
self.shop_image.image = UIImage.init(named: "computer")
}
else if self.model?.Device?.DeviceName == "U盘" {
self.shop_image.image = UIImage.init(named: "udisk")
}
else if self.model?.Device?.DeviceName == "头盔" {
self.shop_image.image = UIImage.init(named: "helmet")
}
//商品数量
self.shop_num.text = String(self.model!.BuyNum!)
self.shop_num.setNeedsLayout()
//设置该商品是否被选中图像
if(self.model!.isChoice == false){
shop_choice.setImage(UIImage.init(named: "choice"), for: .normal)
}else{
shop_choice.setImage(UIImage.init(named: "choiced"), for: .normal)
}
}
}
3.以xib拖线的方式添加按钮点击事件
//增加购物车中商品数量
@IBAction func addShop(_ sender: Any) {
guard model != nil else {
return
}
if(model?.isChoice == false){
return
}
var curBuynum:Int = (model?.BuyNum)!
curBuynum = curBuynum+1
self.model!.BuyNum = curBuynum
print("设备编号\(self.model!.Device?.DeviceID ?? -1)购物车数量加1")
//刷新视图
self.shop_num.text = String(self.model!.BuyNum!)
self.updateMoneySum()
}
//减少购物车中商品
@IBAction func reduceShop(_ sender: Any) {
guard model != nil else {
return
}
if(model?.isChoice == false){
return
}
var curBuynum:Int = (model?.BuyNum)!
if(curBuynum > 0){
curBuynum = curBuynum-1
}
self.model!.BuyNum = curBuynum
print("设备编号\(self.model!.Device?.DeviceID ?? -1)购物车数量减1")
//刷新视图
self.shop_num.text = String(self.model!.BuyNum!)
self.updateMoneySum()
}
//选中或取消选中该商品
@IBAction func shopChoice(_ sender: Any) {
if(self.model!.isChoice == false){
self.model!.isChoice = true;
self.shop_choice.setImage(UIImage.init(named: "choiced"), for: .normal)
}
else{
self.model!.isChoice = false;
self.shop_choice.setImage(UIImage.init(named: "choice"), for: .normal)
}
updateMoneySum()
}
4.在自定义cell里 获取其控制器viewController,获取控制器viewController并计算总价格
extension ShopTableViewCell{
//获取控制器viewController并计算总价格
func updateMoneySum() {
let shoppingCartViewController:ShoppingCartViewController = self.viewController() as! ShoppingCartViewController
var moneySum:Int = 0;
for i in stride(from: shoppingCartViewController.shopArray.count-1, to: -1, by: -1) {
let model:ShopListModel = shoppingCartViewController.shopArray[i] as! ShopListModel
if(model.isChoice == true){
moneySum += model.BuyNum! * Int((model.Device?.DevicePrice)!)!
}
}
shoppingCartViewController.money.text = String(moneySum)
}
//在自定义cell里 获取其控制器viewController
func viewController() -> UIViewController? {
for view in sequence(first: self.superview, next: { $0?.superview }) {
if let responder = view?.next {
if responder.isKind(of: UIViewController.self){
return responder as? UIViewController
}
}
}
return nil
}
}
5.在tableView中指定按xib进行加载
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell :ShopTableViewCell? = tableView.dequeueReusableCell(withIdentifier: "CellID") as? ShopTableViewCell
if(cell == nil){
cell = Bundle.main.loadNibNamed("ShopTableViewCell", owner: nil, options: nil)?.first as? ShopTableViewCell
}
if let model = shopArray[indexPath.row] as? ShopListModel {
cell?.model = model
}
return cell!
}
7. HandyJSON的使用
HandyJSON另辟蹊径,采用Swift反射+内存赋值的方式来构造Model实例,保持原汁原味的Swift类定义。
1、pod引入
pod 'HandyJSON'
2、使用时,先头部import
import HandyJSON
1、HandyJSON支持 JSON直接转Model,定义class时,有两点注意:
必须遵循HandyJSON协议
需要实现空的initializer (当然Struct结构体 可以不需要init(),下文有说明)
2.为了方便使用,创建BaseModel继承HandyJSON
import UIKit
import HandyJSON
class BaseModel: HandyJSON {
required init() {
}
}
3.使用示例
import UIKit
class ShopModel: BaseModel {
var DeviceClassId:Int? //设备分类id
var DeviceID:Int? //设备id
var DeviceName:String? //设备名称
var DevicePrice:String? //设备价格
required init() {
}
}
8. SwiftyJSON的使用
SwiftyJSON是个使用Swift语言编写的开源库,可以让我们很方便地处理JSON数据(解析数据、生成数据)。下面这个网站详细说明了如何使用:
http://www.hangge.com/blog/cache/detail_968.html
使用示例
// 使用 SwiftyJSON 解析json -- 这里解析的是 jsonObject
// 如果要解析 jsonArray, SwiftyJSON 更加丝滑, 参考 http://www.hangge.com/blog/cache/detail_968.html
let json = JSON(result as Any)
guard let array = json["result"].array else{
ZJFLog(message: "获取数据出错")
// 正常结束刷新
self.tableView.mj_header.endRefreshing()
return
}
for dataDict in array{
let response = dataDict.dictionaryObject
// 字典转模型
guard let model:ShopListModel = ShopListModel.deserialize(from: response) else{
ZJFLog(message: "字典转模型出错")
continue
}
self.shopArray.add(model)
}