一、常用的第三方
名称 | 描述 |
---|---|
Alamofire | OC的AFNetworking |
SnapKit | OC的Masonry |
Kingfisher | OC SDWebImage |
ESTabBarController-swift | 高度自定义的 TabBarController 组件(各种你想到的和想不到的哦) |
FSPagerView | 各种类型的banner滚动图片 |
HandyJSON、SwiftyJSON | 数据转模型 |
DNSPageView 、SegementSlide(也很牛逼) | 类似于新闻类的分页,有共同的头部 |
XLPagerTabStrip | 分页视图和 DNSPageView类似 |
JXMarqueeView | 跑马灯 |
LTScrollView | ScrollView嵌套ScrolloView的库,解决多个列表公用一个头部的问题 |
SwiftMessages | 消息提示(类似于flutter的snackBar) |
StreamingKit | 播放在线音频 |
YPImagePicker | 照片视频选择器 |
SKPhotoBrowser | 图片浏览器 |
Lantern | 蓝灯,图片浏览器,很强大 |
KMPlaceholderTextView | 可显示多行 placeholder 的 textView |
PopupDialog | 一个简单的,可定制的弹出对话框为iOS编写的Swift,替换UIAlertController警报样式 |
FaveButton | 动感按钮点击效果(是的就是你想的那样,DuangDuang的) |
ImageSlideshow | 图片幻灯片和图片轮播器 |
GrowingTextView | 一个非常棒的UITextView库 |
Hero | 转场动画 |
DeviceKit | 设备信息统计 |
各种你想要的知识:
iOS开发第三方库图像篇-swift版
iOS开发动画库-swift版
二、页面布局
1、区头设置
使用CollectionView来处理,里面涉及到的技术点。
1.1 闭包
//声明
public var cellTitleClick: ((_ title: String)->())?
//调用
self.cellTitleClick!(dataArray[indexPath.row] as String)
//在controller中使用
private lazy var headerView:LBFMFindHeaderView = {
let view = LBFMFindHeaderView.init(frame: CGRect(x:0, y:0, width:LBFMScreenWidth, height:190))
view.backgroundColor = UIColor.white
view.cellTitleClick = { (title) in
print("外部LBFMFindController点击区头的内容:",title)
}
return view
}()
1.2 UICollectionView的初始化和cell的注册
import UIKit
class LBFMFindHeaderView: UIView {
//点击头部返回数据
public var cellTitleClick: ((_ title: String)->())?
let dataArray = ["电子书城","全民朗读","大咖主播","活动","直播微课","听单","游戏中心","边听变看","商城","0元购","电子书城","全民朗读","大咖主播","活动","直播微课","听单","游戏中心","边听变看","商城","0元购"]
private lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout.init()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = UICollectionView.ScrollDirection.horizontal
layout.itemSize = CGSize(width: (LBFMScreenWidth - 30) / 5, height:90)
let collectionView = UICollectionView.init(frame:CGRect(x:15, y:0, width:LBFMScreenWidth - 30, height:180), collectionViewLayout: layout)
collectionView.isPagingEnabled = true //添加翻页效果
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.white
collectionView.register(LBFMFindCell.self, forCellWithReuseIdentifier:"LBFMFindCell")
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.collectionView)
let footerView = UIView()
footerView.backgroundColor = LBFMDownColor
self.addSubview(footerView)
footerView.snp.makeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(10)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension LBFMFindHeaderView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:LBFMFindCell = collectionView.dequeueReusableCell(withReuseIdentifier: "LBFMFindCell", for: indexPath) as! LBFMFindCell
cell.dataString = self.dataArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.cellTitleClick!(dataArray[indexPath.row] as String)
print("点击顶部内容:",dataArray[indexPath.row] as String)
}
}
2、底部滑动Controller
2.1 引用LTScrollView三方,HeadView使用我们自定义的LBFMFindHeaderView,如果不要的话可以设置高度为0
2.2 设置滚动头部的title和对应的Controller的数组
2.3 设置LTAdvancedManager的view以及相关设置
3、导航栏左右item设置以及事件
import UIKit
import LTScrollView
class LBFMFindController: UIViewController {
// - headerView 区头
private lazy var headerView:LBFMFindHeaderView = {
let view = LBFMFindHeaderView.init(frame: CGRect(x:0, y:0, width:LBFMScreenWidth, height:190))
view.backgroundColor = UIColor.white
view.cellTitleClick = { (title) in
print("外部LBFMFindController点击区头的内容:",title)
}
return view
}()
private lazy var viewControllers: [UIViewController] = {
let findAttentionVC = LBFMFindAttentionController()
let findRecommendVC = LBFMFindRecommendController()
let findDuDYVC = LBFMFindDudController()
return [findAttentionVC, findRecommendVC, findDuDYVC]
}()
private lazy var titles: [String] = {
return ["关注动态", "推荐动态", "趣配音"]
}()
private lazy var layout: LTLayout = {
let layout = LTLayout()
layout.isAverage = true
layout.sliderWidth = 80
layout.titleViewBgColor = UIColor.white
layout.titleColor = UIColor(r: 178, g: 178, b: 178)
layout.titleSelectColor = UIColor.orange//UIColor(r: 16, g: 16, b: 16)
layout.bottomLineColor = UIColor.orange
layout.sliderHeight = 56
/* 更多属性设置请参考 LTLayout 中 public 属性说明 */
return layout
}()
private lazy var advancedManager: LTAdvancedManager = {
let statusBarH = UIApplication.shared.statusBarFrame.size.height
let advancedManager = LTAdvancedManager(frame: CGRect(x: 0, y: LBFMNavBarHeight, width: LBFMScreenWidth, height: LBFMScreenHeight - LBFMNavBarHeight), viewControllers: viewControllers, titles: titles, currentViewController: self, layout: layout, headerViewHandle: {[weak self] in
guard let strongSelf = self else { return UIView() }
let headerView = strongSelf.headerView
return headerView
})
/* 设置代理 监听滚动 */
advancedManager.delegate = self
/* 设置悬停位置 */
// advancedManager.hoverY = navigationBarHeight
/* 点击切换滚动过程动画 */
// advancedManager.isClickScrollAnimation = true
/* 代码设置滚动到第几个位置 */
// advancedManager.scrollToIndex(index: viewControllers.count - 1)
return advancedManager
}()
// - 导航栏左边按钮
private lazy var leftBarButton:UIButton = {
let button = UIButton.init(type: UIButton.ButtonType.custom)
button.frame = CGRect(x:0, y:0, width:30, height: 30)
button.setImage(UIImage(named: "msg"), for: UIControl.State.normal)
button.addTarget(self, action: #selector(leftBarButtonClick), for: UIControl.Event.touchUpInside)
return button
}()
// - 导航栏右边按钮
private lazy var rightBarButton:UIButton = {
let button = UIButton.init(type: UIButton.ButtonType.custom)
button.frame = CGRect(x:0, y:0, width:30, height: 30)
button.setImage(UIImage(named: "搜索"), for: UIControl.State.normal)
button.addTarget(self, action: #selector(rightBarButtonClick), for: UIControl.Event.touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.automaticallyAdjustsScrollViewInsets = false
view.addSubview(advancedManager)
advancedManagerConfig()
// 导航栏左右item
self.navigationItem.leftBarButtonItem = UIBarButtonItem.init(customView: leftBarButton)
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(customView: rightBarButton)
}
// - 导航栏左边消息点击事件
@objc func leftBarButtonClick() {
print("点击了左边的信箱")
}
// - 导航栏左边消息点击事件
@objc func rightBarButtonClick() {
print("点击了右边的搜索按钮")
}
}
extension LBFMFindController : LTAdvancedScrollViewDelegate {
// 具体使用请参考以下
private func advancedManagerConfig() {
// 选中事件
advancedManager.advancedDidSelectIndexHandle = {
print("选中了 -> \($0)")
}
}
}
三、UITableViewCell的定义和高度的计算
1、Cell的布局
cell分为四个区域:
1.1 头像和名称
高度固定
1.2 发布的文本
计算文本的高度
1.3 CollectionView
每一行有三个图片,计算3的倍数,然后算出来CollectionView高度。
1.4 底部时间、点赞、留言数
高度固定
在viewModel中请求并计算数据。
import UIKit
import SwiftyJSON
import HandyJSON
class LBFMFindAttentionViewModel: NSObject {
var eventInfos:[LBFMEventInfosModel]?
// - 数据源更新
typealias LBFMAddDataBlock = () ->Void
var updataBlock:LBFMAddDataBlock?
}
// - 请求数据
extension LBFMFindAttentionViewModel {
func refreshDataSource() {
// 1. 获取json文件路径
let path = Bundle.main.path(forResource: "FindAttention", ofType: "json")
// 2. 获取json文件里面的内容,NSData格式
let jsonData=NSData(contentsOfFile: path!)
// 3. 解析json内容
let json = JSON(jsonData!)
if let mappedObject = JSONDeserializer<LBFMEventInfosModel>.deserializeModelArrayFrom(json: json["data"]["eventInfos"].description) {
self.eventInfos = mappedObject as? [LBFMEventInfosModel]
self.updataBlock?()
}
}
}
extension LBFMFindAttentionViewModel {
// 每个分区显示item数量
func numberOfRowsInSection(section: NSInteger) -> NSInteger {
return self.eventInfos?.count ?? 0
}
// 高度
func heightForRowAt(indexPath: IndexPath) -> CGFloat {
let picNum = self.eventInfos?[indexPath.row].contentInfo?.picInfos?.count ?? 0
var num:CGFloat = 0
//计算图片高度
if picNum > 0 && picNum <= 3 {
num = 1
}else if picNum > 3 && picNum <= 6{
num = 2
}else if picNum > 6{
num = 3
}
let OnePicHeight = CGFloat((LBFMScreenWidth - 30) / 3)
let picHeight = num * OnePicHeight
let textHeight:CGFloat = height(for: self.eventInfos?[indexPath.row].contentInfo)
return 60+50+picHeight+textHeight
}
//计算文本高度
func height(for commentModel: LBFMFindAContentInfo?) -> CGFloat {
var height: CGFloat = 44
guard let model = commentModel else { return height }
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 15)
label.numberOfLines = 0
label.text = model.text
height += label.sizeThatFits(CGSize(width: LBFMScreenWidth - 30, height: CGFloat.infinity)).height + 10
return height
}
}
四、动画
1、浮动动画
1.1 首先旋转动画
self.addSubview(self.animationView)
self.animationView.layer.masksToBounds = true
self.animationView.layer.cornerRadius = 10
self.animationView.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 120, height: 80))
make.top.equalTo(120)
make.right.equalTo(-20)
}
/// vip动画view的旋转角度
self.animationView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 12)
1.2 启动和关闭动画
/// 开始动画
func setAnimationViewAnimation(){
let yorig:CGFloat = 100.0 + 64
let opts: UIView.AnimationOptions = [.autoreverse , .repeat]
UIView.animate(withDuration: 1, delay: 1, options: opts, animations: {
self.animationView.center.y -= 20
}) { _ in
self.animationView.center.y = yorig
}
}
// 停止动画
func stopAnimationViewAnimation(){
self.animationView.layer.removeAllAnimations()
}
2、水波动画(我要录音)
import UIKit
class LBFMCVLayerView: UIView {
var pulseLayer : CAShapeLayer! //定义图层
override init(frame: CGRect) {
super.init(frame: frame)
let width = self.bounds.size.width
// 动画图层
pulseLayer = CAShapeLayer()
pulseLayer.bounds = CGRect(x: 0, y: 0, width: width, height: width)
pulseLayer.position = CGPoint(x: width / 2, y: width / 2)
pulseLayer.backgroundColor = UIColor.clear.cgColor
// 用BezierPath画一个原型
pulseLayer.path = UIBezierPath(ovalIn: pulseLayer.bounds).cgPath
// 脉冲效果的颜色 (注释*1)
pulseLayer.fillColor = UIColor.init(r: 213, g: 54, b: 13).cgColor
pulseLayer.opacity = 0.0
// 关键代码
let replicatorLayer = CAReplicatorLayer()
replicatorLayer.bounds = CGRect(x: 0, y: 0, width: width, height: width)
replicatorLayer.position = CGPoint(x: width/2, y: width/2)
// 三个复制图层
replicatorLayer.instanceCount = 3
// 频率
replicatorLayer.instanceDelay = 1
replicatorLayer.addSublayer(pulseLayer)
self.layer.addSublayer(replicatorLayer)
self.layer.insertSublayer(replicatorLayer, at: 0)
}
func starAnimation() {
// 透明
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
// 起始值
opacityAnimation.fromValue = 1.0
// 结束值
opacityAnimation.toValue = 0
// 扩散动画
let scaleAnimation = CABasicAnimation(keyPath: "transform")
let t = CATransform3DIdentity
scaleAnimation.fromValue = NSValue(caTransform3D: CATransform3DScale(t, 0.0, 0.0, 0.0))
scaleAnimation.toValue = NSValue(caTransform3D: CATransform3DScale(t, 1.0, 1.0, 0.0))
// 给CAShapeLayer添加组合动画
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [opacityAnimation,scaleAnimation]
// 持续时间
groupAnimation.duration = 3
// 循环效果
groupAnimation.autoreverses = false
groupAnimation.repeatCount = HUGE
groupAnimation.isRemovedOnCompletion = false
pulseLayer.add(groupAnimation, forKey: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
五、自定义tabbar
ESTabBarController各种想要的功能以及lottie动画都可实现。
static func lottieSytle() -> ESTabBarController {
let tabBarController = ESTabBarController()
let v1 = ExampleViewController()
let v2 = ExampleViewController()
let v3 = ExampleViewController()
let v4 = ExampleViewController()
let v5 = ExampleViewController()
v1.tabBarItem = ESTabBarItem.init(ExampleLottieAnimateBasicContentView(), title: nil, image: UIImage(named: "home"), selectedImage: UIImage(named: "home_1"))
v2.tabBarItem = ESTabBarItem.init(ExampleLottieAnimateBasicContentView(), title: nil, image: UIImage(named: "find"), selectedImage: UIImage(named: "find_1"))
//ExampleLottieAnimateContentView内部引入了lottie文件以及播放动画
v3.tabBarItem = ESTabBarItem.init(ExampleLottieAnimateContentView(), title: nil, image: nil, selectedImage: nil)
v4.tabBarItem = ESTabBarItem.init(ExampleLottieAnimateBasicContentView(), title: nil, image: UIImage(named: "favor"), selectedImage: UIImage(named: "favor_1"))
v5.tabBarItem = ESTabBarItem.init(ExampleLottieAnimateBasicContentView(), title: nil, image: UIImage(named: "me"), selectedImage: UIImage(named: "me_1"))
tabBarController.viewControllers = [v1, v2, v3, v4, v5]
return tabBarController
}
引入动画,具体详见Demo
import UIKit
import ESTabBarController_swift
class LBFMIrregularityBasicContentView: LBFMBouncesContentView {
override init(frame: CGRect) {
super.init(frame: frame)
textColor = UIColor.init(white: 175.0 / 255.0, alpha: 1.0)
highlightTextColor = UIColor.init(red: 254/255.0, green: 73/255.0, blue: 42/255.0, alpha: 1.0)
iconColor = UIColor.init(white: 175.0 / 255.0, alpha: 1.0)
highlightIconColor = UIColor.init(red: 254/255.0, green: 73/255.0, blue: 42/255.0, alpha: 1.0)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class LBFMIrregularityContentView: ESTabBarItemContentView {
override init(frame: CGRect) {
super.init(frame: frame)
self.imageView.backgroundColor = UIColor.init(red: 250/255.0, green: 48/255.0, blue: 32/255.0, alpha: 1.0)
self.imageView.layer.borderWidth = 2.0
self.imageView.layer.borderColor = UIColor.init(white: 235 / 255.0, alpha: 1.0).cgColor
self.imageView.layer.cornerRadius = 25
self.insets = UIEdgeInsets.init(top: -23, left: 0, bottom: 0, right: 0)
let transform = CGAffineTransform.identity
self.imageView.transform = transform
self.superview?.bringSubviewToFront(self)
textColor = UIColor.init(white: 255.0 / 255.0, alpha: 1.0)
highlightTextColor = UIColor.init(white: 255.0 / 255.0, alpha: 1.0)
iconColor = UIColor.init(white: 255.0 / 255.0, alpha: 1.0)
highlightIconColor = UIColor.init(white: 255.0 / 255.0, alpha: 1.0)
backdropColor = .clear
highlightBackdropColor = .clear
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let p = CGPoint.init(x: point.x - imageView.frame.origin.x, y: point.y - imageView.frame.origin.y)
return sqrt(pow(imageView.bounds.size.width / 2.0 - p.x, 2) + pow(imageView.bounds.size.height / 2.0 - p.y, 2)) < imageView.bounds.size.width / 2.0
}
override func updateLayout() {
super.updateLayout()
self.imageView.sizeToFit()
self.imageView.center = CGPoint.init(x: self.bounds.size.width / 2.0, y: self.bounds.size.height / 2.0)
}
}
6、计算文字高度
// 计算文字的高度
func height(for commentModel: LBFMFindRStreamList?) -> CGFloat {
var height: CGFloat = 30
guard let model = commentModel else { return height }
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 15)
label.numberOfLines = 0
label.text = model.content
height += label.sizeThatFits(CGSize(width: LBFMScreenWidth - 30, height: CGFloat.infinity)).height //算出高度
return height
}