該demo是看
iOS-Swift開發(fā)項目-斗魚直播APP 的視頻后重寫了一下
地址:https://www.bilibili.com/video/BV1qJ411B7G3?p=69
注:本文適合有一定的OC基礎(chǔ),Swift新手閱讀
好了 斗魚直播的 Swift版本上圖
接下里分析從首頁開始分析:
自定義導(dǎo)航欄:
private func setNavigationBar(){
navigationItem.leftBarButtonItem = UIBarButtonItem(imageName: "logo")
let size = CGSize(width: 40, height: 40)
let historyItem = UIBarButtonItem(imageName: "image_my_history", higtImageName: "Image_my_history_click", size: size)
let searchItem = UIBarButtonItem(imageName: "btn_search", higtImageName: "btn_search_clicked", size: size)
let qrcodeItem = UIBarButtonItem(imageName: "Image_scan", higtImageName: "Image_scan_click", size: size)
navigationItem.rightBarButtonItems = [historyItem,searchItem,qrcodeItem]
}
extension UIBarButtonItem{
/*
不建議這么寫
class func createItem(imageName: String, higtImageName:String ,size:CGSize) -> UIBarButtonItem{
let btn = UIButton()
btn.setImage(UIImage(named: imageName), for: .normal)
btn.setImage(UIImage(named: higtImageName), for: .highlighted)
btn.frame = CGRect(origin: .zero, size:size)
return UIBarButtonItem(customView: btn)
}
*/
/*
swift 建議構(gòu)造函數(shù)
1沼瘫、構(gòu)造函數(shù)不需要寫返回值
2买乃、在extension 只能擴充便利構(gòu)造函數(shù) convenience 加上開頭便利構(gòu)造函數(shù)
3、必須 明確調(diào)用一個設(shè)計的構(gòu)造函數(shù)(self)
*/
/*
默認字符串
higtImageName:String = ""
*/
convenience init(imageName: String, higtImageName:String = "" ,size:CGSize = .zero) {
let btn = UIButton()
btn.setImage(UIImage(named: imageName), for: .normal)
if higtImageName != "" {
btn.setImage(UIImage(named: higtImageName), for: .highlighted)
}
if size == .zero {
//按鈕大小自適應(yīng)
btn.sizeToFit()
}else{
btn.frame = CGRect(origin: .zero, size:size)
}
self.init(customView:btn)
}
}
自定定頂部滾動條
class JFPageTitleView: UIView {
private var titles:[String]
//懶加載一個數(shù)組
private lazy var titleLabels:[UILabel] = [UILabel]()
private var currentIndex:Int = 0
//聲明一個代理的屬性
weak var delegate:JFPageTitleViewDelegate?
private lazy var scrollView:UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.scrollsToTop = false
scrollView.isPagingEnabled = false
scrollView.bounces = false
return scrollView
}()
private lazy var scrollLine:UIView = {
let scrollLine = UIView()
scrollLine.backgroundColor = UIColor(r: KSelectColor.0, g: KSelectColor.1, b: KSelectColor.2, a: 1)
return scrollLine
}()
init(frame: CGRect, titles:[String]) {
self.titles = titles
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension JFPageTitleView{
private func setupUI(){
scrollView.frame = bounds
addSubview(scrollView)
setupTitleLabel()
setupBottomLineAndScrollLine()
}
private func setupTitleLabel(){
//swift沒有隱式轉(zhuǎn)化的 一個是CGFloat 類型 一個是 int類型不能直接 乘除
let labelW:CGFloat = frame.width / CGFloat(titles.count)
let labelH:CGFloat = frame.height - KScrollLineH
let labelY:CGFloat = 0
for (index,title) in titles.enumerated() {
let label = UILabel()
label.text = title
label.tag = index
label.font = UIFont.systemFont(ofSize: 16)
label.textColor = UIColor(r: KNormalColor.0, g: KNormalColor.1, b: KNormalColor.2, a: 1)
label.textAlignment = .center
let labelX:CGFloat = labelW * CGFloat(index)
label.frame = CGRect(x: labelX, y: labelY, width: labelW, height: labelH)
scrollView.addSubview(label)
titleLabels.append(label)
label.isUserInteractionEnabled = true
let tapGes = UITapGestureRecognizer(target: self, action: #selector(self.labelClick(tapGes:)))
label.addGestureRecognizer(tapGes)
}
}
private func setupBottomLineAndScrollLine(){
let bottomLine = UIView()
let lineH:CGFloat = 0.5
bottomLine.backgroundColor = UIColor(r: 234, g: 234, b: 234, a: 1)
bottomLine.frame = CGRect(x: 0, y:frame.height - lineH, width: frame.width, height: lineH)
addSubview(bottomLine)
// titleLabels.first 是可選類型 用 guard進行判斷
guard let firstlabel = titleLabels.first else { return}
firstlabel.textColor = UIColor(r: KSelectColor.0, g: KSelectColor.1, b: KSelectColor.2, a: 1)
scrollView.addSubview(scrollLine)
scrollLine.frame = CGRect(x: firstlabel.frame.origin.x, y: frame.height - KScrollLineH, width:firstlabel.frame.width, height: KScrollLineH)
}
}
extension JFPageTitleView {
// label點擊
@objc private func labelClick(tapGes:UITapGestureRecognizer){
//當前的label
guard let currentLabel = tapGes.view as? UILabel else {return}
if currentLabel.tag == currentIndex {return}
//old label
let oldLabel = titleLabels[currentIndex]
currentLabel.textColor = UIColor(r: KSelectColor.0, g: KSelectColor.1, b: KSelectColor.2, a: 1)
oldLabel.textColor = UIColor(r: KNormalColor.0, g: KNormalColor.1, b: KNormalColor.2, a: 1)
//保存最新label的下標值
currentIndex = currentLabel.tag
let scrollLineX = CGFloat(currentLabel.tag) * scrollLine.frame.size.width
UIView.animate(withDuration: 0.15) {
self.scrollLine.frame.origin.x = scrollLineX
}
//通知代理
//代理必須是可選的 因為外部可以不遵守這個代理 所以是weak 修飾姥芥, "?"
delegate?.JFPageTitleViewSelectAtIndex(titleView: self, selectIndex: currentIndex)
}
}
//對外暴露方法
extension JFPageTitleView{
func setTitleViewWithProgress(progress:CGFloat,sourceIndex:Int,targartIndex:Int){
//取出label
let sourceLabel = titleLabels[sourceIndex]
let targartLabel = titleLabels[targartIndex]
//處理滑塊的邏輯
let moveTotalX = targartLabel.frame.origin.x - sourceLabel.frame.origin.x
let moveX = moveTotalX * progress
scrollLine.frame.origin.x = sourceLabel.frame.origin.x + moveX
//顏色的漸變
//取出變化范圍
let colorDelta = (KSelectColor.0 - KNormalColor.0,
KSelectColor.1 - KNormalColor.1,
KSelectColor.2 - KNormalColor.2)
//sourceLabel 是右 高亮邊灰色 色值有大變小的過程
sourceLabel.textColor = UIColor(r: KSelectColor.0 - colorDelta.0 * progress,
g: KSelectColor.1 - colorDelta.1 * progress,
b: KSelectColor.2 - colorDelta.2 * progress,
a: 1)
//targartLabel 是右 高亮邊灰色 色值有小變大的的過程
targartLabel.textColor = UIColor(r: KNormalColor.0 + colorDelta.0 * progress,
g: KNormalColor.1 + colorDelta.1 * progress,
b: KNormalColor.2 + colorDelta.2 * progress,
a: 1)
//記錄currentIndex
currentIndex = targartIndex
}
}
contentView
private let KCcontentCellID = "KCcontentCellID"
//標明只能被類遵守 (如果不寫也可以被 結(jié)構(gòu)體,枚舉遵守 不建議)這樣不能把代理屬性定義為可選類型
//定義一個協(xié)議
protocol JFPageContentViewDelegate : class {
//聲明一個協(xié)議的方法
func JFPageContentViewScrollWith(pageContentView:JFPageContentView,progress:CGFloat, sourceIndex:Int,targetIndex:Int)
}
class JFPageContentView: UIView {
weak var delegate:JFPageContentViewDelegate?
private var childVcs:[UIViewController]
private var startOffSetX:CGFloat = 0
/*
用weak修飾 是可選類型 用 “?”修飾
'weak' variable should have optional type 'UIViewController?'
*/
private var isForbidScrollDelegate:Bool = false
private weak var parentViewController:UIViewController?
private lazy var collectionView:UICollectionView = { [weak self] in
let layout = UICollectionViewFlowLayout()
/*
weak self 是可選類型
self.bounds.size self?.bounds.size 也是可選類型 但是 layout.itemSize 這個是確定類型
(self?.bounds.size)! “!”強制解包
*/
layout.itemSize = (self?.bounds.size)!
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
let frame:CGRect = .zero
let collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
collectionView.bounces = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: KCcontentCellID)
return collectionView
}()
//構(gòu)造函數(shù)改成可選類型
init(frame: CGRect,childVcs:[UIViewController],parentViewController:UIViewController?) {
self.childVcs = childVcs
//可選類型 賦值給可選類型 OK的
self.parentViewController = parentViewController
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension JFPageContentView{
private func setupUI(){
//將所有的子控制器加到父控制器中
for childvc in childVcs {
//如果是可選類型那么就是 要用可選鏈來調(diào)用
parentViewController?.addChild(childvc)
}
addSubview(collectionView)
collectionView.frame = bounds
}
}
//遵守UICOllectionView 的datasource
extension JFPageContentView:UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return childVcs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KCcontentCellID, for: indexPath)
//cell會循環(huán)引用 先移除
for view in cell.contentView.subviews {
view.removeFromSuperview()
}
//給cell設(shè)置內(nèi)容
let childVc = childVcs[indexPath.item]
childVc.view.frame = cell.contentView.bounds
cell.contentView.addSubview(childVc.view)
return cell
}
}
extension JFPageContentView:UICollectionViewDelegate{
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isForbidScrollDelegate = false
startOffSetX = scrollView.contentOffset.x
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (isForbidScrollDelegate) {return}
var progress:CGFloat = 0
var sourceIndex:Int = 0
var targetIndex:Int = 0
//判斷左滑還是右滑
let currentOffSetX = scrollView.contentOffset.x
let scrollViewW = scrollView.bounds.width
if startOffSetX > currentOffSetX { //左滑
//計算progress
// floor 取整
progress = currentOffSetX / scrollViewW - floor(currentOffSetX / scrollViewW)
//計算當前的 sourceIndex
sourceIndex = Int(currentOffSetX / scrollViewW)
//計算target
targetIndex = sourceIndex + 1
if targetIndex >= childVcs.count {
targetIndex = childVcs.count - 1
}
//如果完全滑過去
if currentOffSetX - startOffSetX == scrollViewW{
progress = 1
targetIndex = sourceIndex
}
}else{ //右滑
progress = 1 - (currentOffSetX / scrollViewW - floor(currentOffSetX / scrollViewW))
//計算target
targetIndex = Int(currentOffSetX / scrollViewW)
//計算當前的 sourceIndex
sourceIndex = targetIndex + 1
if sourceIndex >= childVcs.count {
sourceIndex = childVcs.count - 1
}
}
delegate?.JFPageContentViewScrollWith(pageContentView: self, progress: progress, sourceIndex: sourceIndex, targetIndex: targetIndex)
}
}
//對外暴露的方法
extension JFPageContentView{
func setCurrentIndex(currentIndex:Int) {
//禁止執(zhí)行scroll的代理
isForbidScrollDelegate = true
let offSetX = CGFloat(currentIndex) * collectionView.frame.width
//設(shè)置collectionview的偏移量
collectionView.setContentOffset(CGPoint(x: offSetX, y: 0), animated: false)
}
}
網(wǎng)絡(luò)請求:
import Alamofire
enum MethodType {
case GET
case POST
}
class JFNetworkTool{
//字典類型 [String:NSString]
//parameters:[String:NSString]? = nil, 默認參數(shù) 方便外面調(diào)用
//callBack:()->() 閉包的寫法 第一個()里面添加參數(shù)
//逃逸閉包 @escaping 閉包在另外一個閉包中使用需要 @escaping修飾
//result:AnyObject 改成any 則callBack(result as AnyObject) 改成 callBack(result)
/*
parameters: parameters,
encoding: URLEncoding.default,
headers: nil).responseJSON { (response) in
有默認參數(shù) 可以直接去掉
*/
class func requestData(type:MethodType , urlString:String,parameters:[String:NSString]? = nil, callBack:@escaping (_ result:Any)->()) {
//獲取類型
let method = type == .GET ? HTTPMethod.get : HTTPMethod.post
Alamofire.request(urlString,
method:method ,
parameters: parameters).responseJSON { (response) in
guard let result = response.result.value else{
print(response.result.error as Any)
return}
callBack(result)
}
}
}
KVC字典轉(zhuǎn)模型
/// 子類可以繼承父類的所有屬性和方法 可以用來抽取 公共的屬性和方法
class BaseGameModel: NSObject {
//定義屬性 swift 4.0 之后需要手動添加@objc 否則轉(zhuǎn)模型會沒有值
@objc var tag_name : String = ""
@objc var icon_url : String = ""
init(dict:[String:Any]) {
super.init()
setValuesForKeys(dict)
}
//構(gòu)造函數(shù) 在調(diào)用的時候 才可以用 AnchorGroup()來創(chuàng)建
override init() {}
override func setValue(_ value: Any?, forUndefinedKey key: String) {}
}
override func setValue(_ value: Any?, forUndefinedKey key: String) {}
這個一定要重寫 要不然 字段超出要解析的字段會奔潰
歸納下Swift一些常見且高頻注意的點:
類型推導(dǎo):
基本運算:
邏輯分支Guard:
for 循環(huán):
元祖
可選類型
舉例兩種錯誤寫法:
類型轉(zhuǎn)換
本期就總結(jié)到這 下期繼續(xù) 并上源碼地址