IOS控件系列--Swift 滑动标签栏
来源:互联网 发布:淘宝邮箱登录格式 编辑:程序博客网 时间:2024/05/16 19:54
这一编是俺之前OC版的翻译版本,不过做做了一些新功能的扩展,新功能如下:
1.标签支持自适应文本宽度
2.点击标签文本时,标签文本的滚动列表会跟着一起滑动
3.底部指示线宽度自适应上面标签文本宽度
4.将可滑动的标题栏与不可滑动的标题栏结合到一个接口中
设计思路与前一个版本一致,各位道友请移步查看详细的思路,
IOS 高仿boss直聘---优雅使用UIButton与UIScrollView打造滑动标签
这里只奉上源码与效果图:
效果图如下:
源码部分:
import UIKitprotocol ScrollNavBarChangeListener { func onChangeListener(index : Int) -> Void}/// 带标签滑动的滚动视图class ScrollNavBar: UIView { var delegate : ScrollNavBarChangeListener? lazy var bottomLine : UIView = UIView.init() lazy var titleList : Array = Array<String>() lazy var btnList : Array = Array<UIButton>() lazy var titleScrollView : UIScrollView = UIScrollView.init() lazy var moveAnimation : CABasicAnimation = {(make) in let tmpAnim = CABasicAnimation.init(keyPath: "position") return tmpAnim }() var nLastIndex : Int = 0 lazy var segmentScroll : UIScrollView = UIScrollView.init() var finalPos : CGPoint = CGPoint.zero var screenSize : CGRect = CGRect.zero var bIsTitleScroll : Bool = false lazy var averageItemWidth : CGFloat = 0 override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 初始标题栏 /// /// - Parameters: /// - titles: 标题列表 /// - isScroll: 标题栏是否可滑动开关,这个开关在这里主要是控制底部下划线宽度与滚动列表横向滚动的尺寸 func initTitle(titles : Array<String>,isScroll : Bool) { if titles.isEmpty { return } self.bIsTitleScroll = isScroll screenSize = UIScreen.main.bounds self.titleList = titles //创建标题滚动视图 titleScrollView.frame = CGRect.init(x: 0, y: 20, width: screenSize.width, height: 42) titleScrollView.alwaysBounceHorizontal = true titleScrollView.showsHorizontalScrollIndicator = false titleScrollView.backgroundColor = UIColor.init(red: 66.0 / 255.0, green: 133.0 / 255.0, blue: 236.0 / 255.0, alpha: 1.0) self.addSubview(titleScrollView) if !isScroll{ averageItemWidth = titleScrollView.frame.width / CGFloat(titles.count) } var offsetX : CGFloat = 0 var contentSize : CGFloat = 0 for (idx,title) in self.titleList.enumerated(){ if isScroll{ if idx == 0{ offsetX = 0 }else{ offsetX = self.btnList[idx - 1].frame.size.width + self.btnList[idx - 1].frame.origin.x } } let button : UIButton = UIButton.init(frame:CGRect.init(x: CGFloat(idx) * averageItemWidth, y: 0, width: averageItemWidth, height: 40)) button.tag = idx button.addTarget(self, action: #selector(onClickListener(btn:)), for:.touchUpInside) button.setTitle(title, for: UIControlState.normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 12) button.setTitleColor(UIColor.white, for: .normal) if isScroll{ let tmpWidth : CGFloat = UIButton.getWidthWithTitle(title: title, font: UIFont.systemFont(ofSize: 12)) + 20 button.frame = CGRect.init(x: offsetX, y: 0, width: tmpWidth, height: 40) contentSize += tmpWidth } titleScrollView.addSubview(button) self.btnList.append(button) } if isScroll{ titleScrollView.contentSize = CGSize.init(width:contentSize, height: 42) }else{ titleScrollView.contentSize = CGSize.init(width:CGFloat(titles.count) * averageItemWidth, height: 42) } //创建底部线条 let lineRect : CGRect = CGRect.init(x: 0, y: 40, width: isScroll == true ? self.btnList[0].frame.size.width : averageItemWidth, height: 1.5) bottomLine.frame = lineRect bottomLine.backgroundColor = UIColor.white titleScrollView.addSubview(bottomLine) } func initSegmentView(views : Array<Any>) { if views.count == 0 { return } self.segmentScroll = UIScrollView.init(frame:CGRect.init(x: 0, y: 62.5, width: screenSize.width, height: screenSize.height)) self.segmentScroll.contentSize = CGSize.init(width: screenSize.width * CGFloat(views.count), height: screenSize.height) self.segmentScroll.alwaysBounceHorizontal = true self.segmentScroll.isPagingEnabled = true self.addSubview(self.segmentScroll) self.segmentScroll.addObserver(self, forKeyPath: "contentOffset", options: [.new,.old], context: nil) for (idx, layout) in views.enumerated(){ if(idx > views.count){ break } self.segmentScroll.addSubview(layout as! UIView) } } func onClickListener(btn : UIButton?) { if (delegate != nil) { delegate?.onChangeListener(index: btn!.tag) } self.segmentScroll.scrollRectToVisible(CGRect.init(x: CGFloat(btn!.tag) * self.segmentScroll.frame.width, y: self.segmentScroll.frame.origin.y, width: self.segmentScroll.frame.width, height: self.segmentScroll.frame.height), animated: true) let lastItemWidth : CGFloat = self.btnList[self.nLastIndex].frame.width let curItemWidth : CGFloat = self.btnList[btn!.tag].frame.width var fromValue : NSValue? var toValue : NSValue? if self.bIsTitleScroll{ fromValue = NSValue.init(cgPoint: CGPoint.init(x: self.btnList[self.nLastIndex].frame.origin.x + 0.5 * lastItemWidth, y: 40)) toValue = NSValue.init(cgPoint: CGPoint.init(x: self.btnList[btn!.tag].frame.origin.x + 0.5 * curItemWidth, y: 40)) }else{ fromValue = NSValue.init(cgPoint: CGPoint.init(x: CGFloat(self.nLastIndex) * averageItemWidth + 0.5 * averageItemWidth, y: 40)) toValue = NSValue.init(cgPoint: CGPoint.init(x: CGFloat(btn!.tag) * averageItemWidth + 0.5 * averageItemWidth, y: 40)) } startLineMoveAnimFromValue(fromValue: fromValue!, toValue: toValue!, duration: 0.3) titleLabelMoveLogic(curBtnIdx: btn!.tag) self.nLastIndex = btn!.tag updateTitleBtnStatus(idx: btn!.tag) guard self.bIsTitleScroll == true else { self.finalPos = CGPoint.init(x : CGFloat(btn!.tag) * averageItemWidth + 0.5 * averageItemWidth, y : 40) return } self.finalPos = CGPoint.init(x:curItemWidth + 0.5 * curItemWidth, y: 40) } /// 处理标签滑动逻辑: /// - Parameters: /// - curBtnIdx: 点击的当前标签 /// 对于可以滑动,需要重新调整可见视图 func titleLabelMoveLogic(curBtnIdx : Int) { if !self.bIsTitleScroll {return} self.titleScrollView.scrollRectToVisible(CGRect.init(x: self.btnList[curBtnIdx].frame.origin.x, y: 0, width: self.titleScrollView.frame.size.width, height: self.titleScrollView.frame.size.height), animated: true) } func updateTitleBtnStatus(idx : Int){ for(index, button) in self.btnList.enumerated(){ if idx == index { button.setTitleColor(UIColor.init(red: 118 / 255, green: 198 / 255, blue: 192 / 255, alpha: 1.0), for: .normal) }else { button.setTitleColor(UIColor.white, for: .normal) } } if self.bIsTitleScroll { //对于标题栏可以滑动,需要重新调整底部下划线的宽度 resetBottomLineWidth(idx: idx) } } /// 调整顶部标签的宽度:主要根据点击的按钮宽度来更新标签的宽度 /// /// - Parameter idx: 宽度id 因为按钮id与其在列表中索引一致,所以可以直接用id作索引 func resetBottomLineWidth(idx : Int) { let btnWidth : CGFloat = self.btnList[idx].frame.width bottomLine.frame = CGRect.init(x: bottomLine.frame.origin.x, y: bottomLine.frame.origin.y, width: btnWidth, height: bottomLine.frame.size.height) } func startLineMoveAnimFromValue(fromValue : Any, toValue : Any, duration:CFTimeInterval) { moveAnimation.fromValue = fromValue moveAnimation.toValue = toValue moveAnimation.delegate = self as CAAnimationDelegate moveAnimation.isRemovedOnCompletion = false moveAnimation.fillMode = kCAFillModeForwards moveAnimation.duration = duration bottomLine.layer.removeAllAnimations() bottomLine.layer.add(moveAnimation, forKey: "onStart") } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { let newPos : CGPoint = change?[NSKeyValueChangeKey.newKey] as! CGPoint self.nLastIndex = Int(newPos.x / self.segmentScroll.frame.width); self.bottomLine.layer.removeAllAnimations() if self.bIsTitleScroll{ self.bottomLine.frame = CGRect.init(x: self.btnList[self.nLastIndex].frame.origin.x, y: self.bottomLine.frame.origin.y, width: 0, height: 1.5) }else{ var frame : CGRect = self.bottomLine.frame frame.origin.x = CGFloat(self.nLastIndex) * averageItemWidth self.bottomLine.frame = frame; } self.bottomLine.layoutIfNeeded() titleLabelMoveLogic(curBtnIdx: self.nLastIndex) updateTitleBtnStatus(idx: self.nLastIndex) } deinit{ self.segmentScroll.removeObserver(self, forKeyPath: "contentOffset") } }extension ScrollNavBar : ScrollNavBarChangeListener,CAAnimationDelegate{ func onChangeListener(index: Int) { } func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if bottomLine.layer.animationKeys()?.last == "onStart" { var frame : CGRect = bottomLine.frame frame.origin.x = finalPos.x bottomLine.frame = frame } } }
按钮自适应使用扩展的方式:
import UIKit// MARK: - 本类主要处理按钮随文本宽度自适应extension UIButton{ /** 类方法,根据宽度,计算高度 @param width 输入宽度 @param title 文本内容 @param font 字体属性 @return 计算后的高度 */ class func getHeightByWidth(width : CGFloat, title : String, font : UIFont) -> CGFloat{ let button : UIButton = UIButton.init(frame: CGRect.init(x: 0, y: 0, width: width, height: 0)) button.setTitle(title, for: .normal) button.titleLabel?.font = font button.sizeToFit() button.titleLabel?.numberOfLines = 0 return button.frame.size.height } /** 根据文本内容计算宽度 @param title 文本内容 @param font 字体属性 @return 计算后的宽度 */ class func getWidthWithTitle(title : String, font : UIFont) -> CGFloat{ // UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 0)];// label.text = title;// label.font = font;// [label sizeToFit];// return label.frame.size.width; let button : UIButton = UIButton.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0)) button.setTitle(title, for: .normal) button.titleLabel?.font = font button.sizeToFit() return button.frame.size.width } }
在vc中使用可滑动方式:
import UIKitclass WatchVC: BaseVC { lazy var topNavBar : ScrollNavBar = {[unowned self]() -> ScrollNavBar in let tmpView : ScrollNavBar = ScrollNavBar.init(frame: CGRect.init(x : 0, y : 0, width : self.getScreenSize().size.width, height : self.getScreenSize().size.height)) return tmpView }() override func viewDidLoad() { initView() } func initView(){ let titles : [String] = ["视频","资讯","趣味图集","小说","娱乐","热点","体育", "财经","军事","汽车","时尚"] topNavBar.initTitle(titles: titles,isScroll: true) self.view.addSubview(topNavBar) let view1 : UIView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.getScreenSize().size.width, height: self.getScreenSize().size.height)) view1.backgroundColor = UIColor.blue let view2 : UIView = UIView.init(frame: CGRect.init(x: self.getScreenSize().size.width, y: 0, width: self.getScreenSize().size.width, height: self.getScreenSize().size.height)) view2.backgroundColor = UIColor.orange let view3 : UIView = UIView.init(frame: CGRect.init(x: self.getScreenSize().size.width * 2, y: 0, width: self.getScreenSize().size.width, height: self.getScreenSize().size.height)) view3.backgroundColor = UIColor.yellow let view4 : UIView = UIView.init(frame: CGRect.init(x: self.getScreenSize().size.width * 3, y: 0, width: self.getScreenSize().size.width, height: self.getScreenSize().size.height)) view4.backgroundColor = UIColor.blue let views : [UIView] = [view1,view2,view3,view4] topNavBar.initSegmentView(views: views) }}
欢迎交流一起学习:
阅读全文
0 0
- IOS控件系列--Swift 滑动标签栏
- IOS控件系列--滚动列表上下滑动时顶部视图固定与滑动效果(Swift版)
- Swift 标签控件(UILable)
- IOS控件系列---文本上下滚动的标签实现广告
- 一般IOS项目中的滑动标签栏的实现
- iOS开发系列--Swift语言
- iOS开发系列--Swift进阶
- iOS开发系列--Swift语言
- iOS开发系列--Swift 3.0
- iOS开发系列--Swift语言
- iOS开发----Swift基础控件
- iOS 控件系列:UIActionSheet
- iOS Swift&OC 模仿主流App 实现滑动视图隐藏导航栏
- Android实用视图动画及工具系列之七:可定制Tab标签栏,ViewPaper和Fragment滑动标签视图
- iOS自定义控件之滑动横幅
- iOS自定义范围滑动条控件
- IOS ScrollView 子控件滑动手势冲突
- IOS控件系列--对象池模式的应用--使用UIScrollView自定义设计滚动列表(UITableView、UICollectionView)(Swift版)
- Java 多线程
- 欢迎使用CSDN-markdown编辑器
- openstack-neutron-ML2
- openvassd: Reloaded all the NVTs
- 浏览器缓存机制
- IOS控件系列--Swift 滑动标签栏
- Kafka学习整理五(Consumer配置)
- linux 环境安装Nginx
- 《Hibernate学习笔记五》Session 的update方法详解
- Autohotkey 学习记录
- Trust anchor for certification path not found.问题解决(关于okhttputils的BUG)
- 3二维数组查找python
- google common CacheBuilder
- java.lang.IllegalArgumentException: invalid comparison: java.util.ArrayList and java.lang.String