UIView+Ext.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. //
  2. // UICollectionView+Ext.swift
  3. // PQSpeed
  4. //
  5. // Created by SanW on 2020/6/6.
  6. // Copyright © 2020 BytesFlow. All rights reserved.
  7. //
  8. import KingfisherWebP
  9. import UIKit
  10. // MARK: - UIView的分类扩展
  11. /// UIView的分类扩展
  12. extension UIView {
  13. public func addCorner(roundingCorners: UIRectCorner = .allCorners, corner: CGFloat = cDefaultMargin) {
  14. if roundingCorners == .allCorners {
  15. layer.cornerRadius = corner
  16. layer.masksToBounds = true
  17. } else {
  18. let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: roundingCorners, cornerRadii: CGSize(width: corner, height: corner))
  19. let cornerLayer = CAShapeLayer()
  20. cornerLayer.frame = bounds
  21. cornerLayer.path = path.cgPath
  22. layer.mask = cornerLayer
  23. }
  24. }
  25. /// 添加阴影
  26. /// - Parameters:
  27. /// - color: <#color description#>
  28. /// - offset: <#offset description#>
  29. /// - Returns: <#description#>
  30. public func addShadowLayer(isAll: Bool = false, color: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5), offset: CGSize = CGSize(width: 1, height: 1)) {
  31. layer.shadowColor = color.cgColor
  32. layer.shadowOffset = offset
  33. layer.shadowRadius = 0.5
  34. layer.shadowOpacity = 1.0
  35. if isAll {
  36. layer.shadowColor = color.cgColor
  37. layer.shadowOffset = CGSize.zero
  38. // 设置偏移量为0,四周都有阴影
  39. layer.shadowRadius = 0.5 // 阴影半径
  40. layer.shadowOpacity = 0.3 // 阴影透明度
  41. layer.masksToBounds = false
  42. layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
  43. }
  44. }
  45. /// 添加虚线条
  46. public func addBorderToLayer(frame: CGRect? = nil) {
  47. // 线条颜色
  48. let borderLayer: CAShapeLayer = CAShapeLayer()
  49. borderLayer.strokeColor = UIColor.hexColor(hexadecimal: "#FFFFFF").cgColor
  50. borderLayer.fillColor = nil
  51. borderLayer.path = UIBezierPath(rect: frame == nil ? bounds : frame!).cgPath
  52. borderLayer.frame = bounds
  53. borderLayer.lineWidth = 2.0
  54. borderLayer.lineCap = .round
  55. // 第一位是 线条长度 第二位是间距 nil时为实线
  56. borderLayer.lineDashPattern = [5, 5]
  57. layer.addSublayer(borderLayer)
  58. }
  59. public func animateZoom() {
  60. transform = CGAffineTransform(scaleX: 0.4, y: 0.4)
  61. UIView.animate(withDuration: 0.5, animations: {
  62. self.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
  63. }) { _ in
  64. UIView.animate(withDuration: 0.5, animations: {
  65. self.transform = .identity
  66. }) { _ in
  67. }
  68. }
  69. }
  70. /// 添加抖动功能
  71. /// - Parameters:
  72. /// - fromValue: <#fromValue description#>
  73. /// - toValue: <#toValue description#>
  74. /// - duration: <#duration description#>
  75. /// - repeatCount: <#repeatCount description#>
  76. /// - Returns: <#description#>
  77. public func shakeAnimation(_ fromValue: Float, _ toValue: Float, _ duration: Float, _: Float) {
  78. layer.removeAllAnimations()
  79. let shake = CABasicAnimation(keyPath: "transform.rotation.z")
  80. shake.fromValue = fromValue
  81. shake.toValue = toValue
  82. shake.duration = CFTimeInterval(duration)
  83. shake.autoreverses = true
  84. shake.repeatCount = Float(CGFloat.greatestFiniteMagnitude)
  85. shake.isRemovedOnCompletion = false
  86. layer.add(shake, forKey: "imageView")
  87. // 增加锚点
  88. layer.anchorPoint = CGPoint(x: 0.5, y: 1)
  89. }
  90. /// 添加心跳动画
  91. /// - Parameters:
  92. /// - duration: <#duration description#>
  93. /// - isRepeat: <#isRepeat description#>
  94. /// - multiple: <#multiple description#>
  95. /// - Returns: <#description#>
  96. public func heartbeatAnimate(duration: TimeInterval, isRepeat: Bool, multiple: CGFloat) {
  97. UIView.animateKeyframes(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
  98. self.transform = CGAffineTransform(scaleX: 1.0 + multiple, y: 1.0 + multiple)
  99. }) { _ in
  100. UIView.animateKeyframes(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
  101. self.transform = .identity
  102. }) { _ in
  103. UIView.animateKeyframes(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
  104. self.transform = CGAffineTransform(scaleX: 1.0 + multiple * 2, y: 1.0 + multiple * 2)
  105. }) { _ in
  106. UIView.animateKeyframes(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
  107. self.transform = .identity
  108. }) { _ in
  109. if isRepeat {
  110. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.3) {
  111. self.heartbeatAnimate(duration: duration, isRepeat: isRepeat, multiple: multiple)
  112. }
  113. }
  114. }
  115. }
  116. }
  117. }
  118. }
  119. /// 活动心跳动画
  120. /// - Returns: <#description#>
  121. public func activityHeartbeatAnimate() {
  122. layer.removeAllAnimations()
  123. UIView.animateKeyframes(withDuration: 0.45, delay: 0, options: .allowUserInteraction, animations: {
  124. self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
  125. }) { _ in
  126. UIView.animateKeyframes(withDuration: 0.45, delay: 0, options: .allowUserInteraction, animations: {
  127. self.transform = .identity
  128. }) { _ in
  129. self.activityHeartbeatAnimate()
  130. }
  131. }
  132. }
  133. /// 活动心跳动画
  134. /// - Returns: <#description#>
  135. public func heartbeatAnimate() {
  136. layer.removeAllAnimations()
  137. UIView.animateKeyframes(withDuration: 1, delay: 0, options: .allowUserInteraction, animations: {
  138. self.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
  139. }) { isFinished in
  140. UIView.animateKeyframes(withDuration: 1, delay: 0, options: .allowUserInteraction, animations: {
  141. self.transform = .identity
  142. }) { isFinished in
  143. if isFinished {
  144. self.heartbeatAnimate()
  145. }
  146. }
  147. }
  148. }
  149. public func addScaleBasicAnimation() {
  150. let animation = CABasicAnimation(keyPath: "transform.scale")
  151. animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
  152. animation.duration = 0.5
  153. animation.repeatCount = 1000
  154. animation.autoreverses = true
  155. animation.fromValue = 0.8
  156. animation.toValue = 1.1
  157. layer.add(animation, forKey: nil)
  158. }
  159. public func addScaleYBasicAnimation() {
  160. let animation = CAKeyframeAnimation(keyPath: "transform.translation.y")
  161. animation.duration = 0.5
  162. animation.repeatCount = 100
  163. animation.isRemovedOnCompletion = true
  164. // animation.
  165. animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
  166. layer.add(animation, forKey: nil)
  167. // CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
  168. // CGFloat duration = 1.f;
  169. // CGFloat height = 7.f;
  170. // CGFloat currentY = self.animationView.transform.ty;
  171. // animation.duration = duration;
  172. // animation.values = @[@(currentY),@(currentY - height/4),@(currentY - height/4*2),@(currentY - height/4*3),@(currentY - height),@(currentY - height/ 4*3),@(currentY - height/4*2),@(currentY - height/4),@(currentY)];
  173. // animation.keyTimes = @[ @(0), @(0.025), @(0.085), @(0.2), @(0.5), @(0.8), @(0.915), @(0.975), @(1) ];
  174. // animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  175. // animation.repeatCount = HUGE_VALF;
  176. // [self.animationView.layer addAnimation:animation forKey:@"kViewShakerAnimationKey"];
  177. // }
  178. }
  179. /// 将view生成一张图片
  180. /// - Returns: <#description#>
  181. public func graphicsGetImage() -> UIImage? {
  182. UIGraphicsBeginImageContextWithOptions(frame.size, true, 0.0)
  183. layer.render(in: UIGraphicsGetCurrentContext()!)
  184. let newImage = UIGraphicsGetImageFromCurrentImageContext()
  185. UIGraphicsEndImageContext()
  186. return newImage
  187. }
  188. /// 动画显示View
  189. /// - Returns: <#description#>
  190. public func showViewAnimate(duration: TimeInterval = 0.3, completion: ((Bool) -> Void)? = nil) {
  191. UIView.animate(withDuration: duration, animations: { [weak self] in
  192. self?.frame = CGRect(x: 0, y: cScreenHeigth - self!.frame.height, width: self!.frame.width, height: self!.frame.height)
  193. }) { isFinished in
  194. if completion != nil {
  195. completion!(isFinished)
  196. }
  197. }
  198. }
  199. /// 动画隐藏view
  200. /// - Returns: <#description#>
  201. public func dismissViewAnimate(duration: TimeInterval = 0.3, completion: ((Bool) -> Void)? = nil) {
  202. UIView.animate(withDuration: duration, animations: { [weak self] in
  203. self?.frame = CGRect(x: 0, y: cScreenHeigth, width: self!.frame.width, height: self!.frame.height)
  204. }) { isFinished in
  205. if completion != nil {
  206. completion!(isFinished)
  207. }
  208. }
  209. }
  210. /// add by ak 添加虚线框
  211. /// - Parameter color: 框色
  212. /// - Parameter lineWidth: 框宽
  213. public func addBorderToLayer(color: CGColor, lineWidth: CGFloat) {
  214. let border = CAShapeLayer()
  215. // 线条颜色
  216. border.strokeColor = color
  217. border.fillColor = nil
  218. border.path = UIBezierPath(rect: bounds).cgPath
  219. border.frame = bounds
  220. border.lineWidth = lineWidth
  221. border.lineCap = .square
  222. // 第一位是 线条长度 第二位是间距 nil时为实线
  223. border.lineDashPattern = [9, 4]
  224. layer.addSublayer(border)
  225. }
  226. }
  227. // MARK: - UICollectionView的分类扩展
  228. /// UICollectionView的分类扩展
  229. extension UICollectionView {
  230. /// 获取当前cell
  231. /// - Returns: <#description#>
  232. public func visibleCell() -> UICollectionViewCell? {
  233. let visibleRect = CGRect(origin: contentOffset, size: bounds.size)
  234. let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
  235. guard let visibleIndexPath = indexPathForItem(at: visiblePoint) else { return nil }
  236. return cellForItem(at: visibleIndexPath)
  237. }
  238. /// 添加刷新组件
  239. /// - Parameters:
  240. /// - scroller: <#scroller description#>
  241. /// - type: 1-头部跟尾部 2-头部 3-尾部
  242. public func addRefreshView(type: REFRESH_TYPE = .REFRESH_TYPE_ALL, refreshHandle: ((_ isHeader: Bool) -> Void)?) {
  243. if type == .REFRESH_TYPE_ALL || type == .REFRESH_TYPE_HEADER {
  244. let header = MJRefreshNormalHeader.init {
  245. if refreshHandle != nil {
  246. refreshHandle!(true)
  247. }
  248. }
  249. header.setTitle("下拉刷新", for: .willRefresh)
  250. header.setTitle("正在刷新...", for: .refreshing)
  251. header.setTitle("松开刷新", for: .pulling)
  252. header.setTitle("下拉刷新", for: .idle)
  253. header.lastUpdatedTimeLabel?.isHidden = true
  254. mj_header = header
  255. }
  256. if type == .REFRESH_TYPE_ALL || type == .REFRESH_TYPE_FOOTER {
  257. // MJRefreshBackNormalFooter 不会附在上面
  258. // MJRefreshAutoFooter 不会便宜
  259. let footer = MJRefreshBackNormalFooter.init {
  260. if refreshHandle != nil {
  261. refreshHandle!(false)
  262. }
  263. }
  264. footer.setTitle("暂时没有更多了", for: .noMoreData)
  265. footer.setTitle("精彩内容正在加载中...", for: .refreshing)
  266. mj_footer = footer
  267. }
  268. }
  269. public func indexPathsForElements(in rect: CGRect) -> [IndexPath] {
  270. let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)!
  271. return allLayoutAttributes.map { $0.indexPath }
  272. }
  273. }
  274. // MARK: - UITabBar的分类扩展
  275. /// UITabBar的分类扩展
  276. extension UITabBar {
  277. /// 展示小红点
  278. /// - Parameter index: <#index description#>
  279. /// - Returns: <#description#>
  280. public func showPoint(index: Int) {
  281. let pointW: CGFloat = 8
  282. let pointView = UIView()
  283. pointView.tag = 11111 + index
  284. pointView.layer.cornerRadius = pointW / 2
  285. pointView.backgroundColor = UIColor.hexColor(hexadecimal: "#EE0051")
  286. let percentX: CGFloat = CGFloat(Double(index) + 0.7) / CGFloat(items?.count ?? 1)
  287. let pointX = ceil(percentX * frame.width)
  288. let pointY = ceil(0.1 * frame.height)
  289. pointView.frame = CGRect(x: pointX, y: pointY, width: pointW, height: pointW)
  290. addSubview(pointView)
  291. }
  292. /// 移除小红点
  293. /// - Parameter index: <#index description#>
  294. /// - Returns: <#description#>
  295. public func removePoint(index: Int) {
  296. for item in subviews {
  297. if item.tag == 11111 + index {
  298. item.removeFromSuperview()
  299. }
  300. }
  301. }
  302. /// 展示创作视频引导
  303. /// - Parameter index: <#index description#>
  304. /// - Returns: <#description#>
  305. public func showVideoMakeRemindView() {
  306. let isOldUploadClick: String? = getUserDefaults(key: cIsUploadClick) as? String
  307. let isUploadClick: String? = getUserDefaultsForJson(key: cIsUploadClick) as? String
  308. let isVerticalSlip: String? = getUserDefaults(key: cIsVerticalSlip) as? String
  309. if isOldUploadClick == nil && isVerticalSlip != nil && isVerticalSlip == "1", isUploadClick == nil || isUploadClick?.count ?? 0 <= 0 || isUploadClick != "2" {
  310. let width: CGFloat = 275 // 275
  311. let height: CGFloat = 107 // 107
  312. let videoMakeRemindBtn = UIButton(frame: CGRect(x: 0, y: -height + 5, width: width, height: height))
  313. videoMakeRemindBtn.tag = cVideoMakeRemindTag
  314. videoMakeRemindBtn.setBackgroundImage(UIImage(named: "videomk_guide"), for: .normal)
  315. addSubview(videoMakeRemindBtn)
  316. videoMakeRemindBtn.center.x = center.x
  317. videoMakeRemindBtn.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
  318. videoMakeRemindBtn.addScaleBasicAnimation()
  319. if isUploadClick == "1" {
  320. saveUserDefaultsToJson(key: cIsUploadClick, value: "2")
  321. } else {
  322. saveUserDefaultsToJson(key: cIsUploadClick, value: "1")
  323. }
  324. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { [weak self] in
  325. self?.removeVideoMakeRemindView()
  326. }
  327. }
  328. }
  329. @objc public func dismiss() {
  330. }
  331. /// 移除创作视频引导
  332. /// - Returns: <#description#>
  333. public func removeVideoMakeRemindView() {
  334. viewWithTag(cVideoMakeRemindTag)?.removeFromSuperview()
  335. }
  336. }
  337. // MARK: - UILabel的分类扩展
  338. /// UILabel的分类扩展
  339. extension UILabel {
  340. var isTruncated: Bool {
  341. guard let labelText = text else {
  342. return false
  343. }
  344. // 计算理论上显示所有文字需要的尺寸
  345. let rect = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
  346. let labelTextSize = (labelText as NSString)
  347. .boundingRect(with: rect, options: .usesLineFragmentOrigin,
  348. attributes: [NSAttributedString.Key.font: font as Any], context: nil)
  349. // 计算理论上需要的行数
  350. let labelTextLines = Int(ceil(CGFloat(labelTextSize.height) / font.lineHeight))
  351. // 实际可显示的行数
  352. var labelShowLines = Int(floor(CGFloat(bounds.size.height) / font.lineHeight))
  353. if numberOfLines != 0 {
  354. labelShowLines = min(labelShowLines, numberOfLines)
  355. }
  356. // 比较两个行数来判断是否需要截断
  357. return labelTextLines > labelShowLines
  358. }
  359. /// 添加阴影
  360. /// - Parameters:
  361. /// - color: <#color description#>
  362. /// - offset: <#offset description#>
  363. /// - Returns: <#description#>
  364. public func addShadow(color: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5), offset: CGSize = CGSize(width: 1, height: 1)) {
  365. shadowColor = color
  366. shadowOffset = offset
  367. }
  368. }
  369. extension UIImageView {
  370. /// imageView加载网络图片
  371. /// - Parameters:
  372. /// - url: 网络url
  373. public func setNetImage(url: String?, placeholder: UIImage = UIImage.init().BF_Image(named: "placehold_image")) {
  374. if url == nil || (url?.count ?? 0) <= 0 {
  375. BFLog(message: "设置按钮网络图片地址为空")
  376. return
  377. }
  378. kf.setImage(with: URL(string: url!), placeholder: placeholder, options: url?.suffix(5) == ".webp" ? [.processor(WebPProcessor.default), .cacheSerializer(WebPSerializer.default)] : nil, progressBlock: { _, _ in
  379. }) { _ in
  380. }
  381. }
  382. /// 展示加载中动画
  383. /// - Returns: <#description#>
  384. public func showLoadingAnimation(duration: Double = 1) {
  385. let rotationAnim = CABasicAnimation(keyPath: "transform.rotation.z")
  386. rotationAnim.fromValue = 0
  387. rotationAnim.toValue = Double.pi * 2
  388. rotationAnim.repeatCount = MAXFLOAT
  389. rotationAnim.duration = duration
  390. rotationAnim.isRemovedOnCompletion = false
  391. layer.add(rotationAnim, forKey: nil)
  392. }
  393. /// 播放GIF
  394. /// - Parameters:
  395. /// - data : 图片二进制数据
  396. /// - images: 图片
  397. /// - repeatCount: 循环次数
  398. /// - duration: 时长
  399. /// - Returns: <#description#>
  400. public func displayGIF(data: Data? = nil, images: [UIImage]? = nil, repeatCount: Int = Int.max, duration: Double = 1) {
  401. if images != nil, (images?.count ?? 0) > 0, !isAnimating {
  402. layer.removeAllAnimations()
  403. stopAnimating()
  404. animationImages = images
  405. animationDuration = duration
  406. animationRepeatCount = repeatCount
  407. startAnimating()
  408. } else if images == nil && data != nil {
  409. PQPHAssetVideoParaseUtil.parasGIFImage(data: data!) { [weak self] _, images, duration in
  410. if images != nil, (images?.count ?? 0) > 0 {
  411. self?.displayGIF(images: images!, repeatCount: repeatCount, duration: duration ?? 1)
  412. }
  413. }
  414. }
  415. }
  416. /// 移除
  417. public func removePlayGIF() {
  418. layer.removeAllAnimations()
  419. stopAnimating()
  420. }
  421. }
  422. extension UIButton {
  423. /// UIButton加载网络图片
  424. /// - Parameters:
  425. /// - url: 网络url
  426. public func setNetImage(url: String?, placeholder: UIImage = UIImage.init().BF_Image(named: "placehold_image")) {
  427. if url == nil || (url?.count ?? 0) <= 0 {
  428. BFLog(message: "设置按钮网络图片地址为空")
  429. return
  430. }
  431. kf.setImage(with: URL(string: url!), for: .normal, placeholder: placeholder, options: url?.suffix(5) == ".webp" ? [.processor(WebPProcessor.default), .cacheSerializer(WebPSerializer.default)] : nil, progressBlock: { _, _ in
  432. }) { _ in
  433. }
  434. }
  435. /// UIButton加载网络背景图片
  436. /// - Parameters:
  437. /// - url: 网络url
  438. public func setNetBackgroundImage(url: String, placeholder: UIImage = UIImage.init().BF_Image(named: "placehold_image")) {
  439. kf.setBackgroundImage(with: URL(string: url), for: .normal, placeholder: placeholder, options: url.suffix(5) == ".webp" ? [.processor(WebPProcessor.default), .cacheSerializer(WebPSerializer.default)] : nil, progressBlock: { _, _ in
  440. }) { _ in
  441. }
  442. }
  443. }