完成效果:
在JFHomeViewController中首先为导航栏的titleView设置一个自定义的UIButton按钮
JFHomeViewController.swift
let titleButton = JFTitleButton(title: "周剑峰先生") navigationItem.titleView = titleButton
在自定义UIButton类里修改内部子控件实现titleLabel和imageView交换位置
JFTitleButton.swift
override func layoutSubviews() { super.layoutSubviews() // 把label移动到左边 titleLabel?.frame.origin.x = 0 // 把图片移到label的后面 imageView?.frame.origin.x = titleLabel!.frame.width + 2 }
在自定义UIButton类里实现一个便利构造方法,将上、下箭头分别设置为按钮的选中、默认样式的imageView
JFTitleButton.swift
convenience init(title: String) { self.init() setTitle(title, forState: UIControlState.Normal) adjustsImageWhenHighlighted = false titleLabel?.font = UIFont.systemFontOfSize(17) setImage(UIImage(named: "navigationbar_arrow_down"), forState: UIControlState.Normal) setImage(UIImage(named: "navigationbar_arrow_up"), forState: UIControlState.Selected) setBackgroundImage(UIImage(named: "tabbar_compose_below_button_highlighted"), forState: UIControlState.Highlighted) setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal) sizeToFit() }
创建一个继承UIViewController的自定义控制器类,并为这个普通控制器设置一个背景图片和tableView。注意背景图片是全屏的,而tableView根据背景图设置外边距,并且控制器view的背景颜色需要设置透明。这里布局我使用的是SnapKit。
JFPopViewController.swift
import UIKit class JFPopViewController: UIViewController { // MARK: - 属性 var lists = [["首页", "好友圈", "群微博", "我的微博", "新浪微博"], ["特别关注", "网络好友", "我的推荐", "明星", "科技", "兄弟连", "舞蹈", "傻逼", "企业", "我的朋友", "名人明星", "悄悄关注"], ["周边微博"] ] // tableview重用标识符 let popoverIdentifier = "popoverCell" // MARK: - 视图声明周期 override func viewDidLoad() { super.viewDidLoad() // 背景颜色透明 view.backgroundColor = UIColor.clearColor() // 注册cell tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: popoverIdentifier) // 准备UI prepareUI() } // MARK: - 准备UI private func prepareUI() { // 添加背景图片 view.addSubview(backgroundView) view.addSubview(tableView) // 约束子控件 backgroundView.snp_makeConstraints { (make) -> Void in make.edges.equalTo(view.snp_edges) } tableView.snp_makeConstraints { (make) -> Void in make.edges.equalTo(UIEdgeInsets(top: 15, left: 12, bottom: -12, right: -12)) } } // MARK: - 懒加载 // 背景图片 lazy var backgroundView: UIImageView = { let imageView = UIImageView() var image = UIImage(named: "popover_background")! image = image.resizableImageWithCapInsets(UIEdgeInsets(top: image.size.height * 0.5, left: image.size.width * 0.5 - 100, bottom: image.size.height * 0.5, right: image.size.width * 0.5 - 100), resizingMode: UIImageResizingMode.Stretch) imageView.image = image return imageView }() // tableview lazy var tableView: UITableView = { let tableView = UITableView(frame: CGRectZero, style: UITableViewStyle.Grouped) tableView.dataSource = self tableView.delegate = self return tableView }() } // MARK: - UITableViewDataSource、UITableViewDelegate数据源、代理 extension JFPopViewController: UITableViewDataSource, UITableViewDelegate { func numberOfSectionsInTableView(tableView: UITableView) -> Int { return lists.count } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return lists[section].count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(popoverIdentifier)! cell.textLabel?.text = lists[indexPath.section][indexPath.row] return cell } }
再创建一个继承自UIPresentationController的类,他的作用是作为一个呈现我们自定义的控制器视图的容器,我们可以在这个类中自定义我们modal出来的控制器的视图大小。并且为这个容器视图添加一个敲击手势,dismiss我们modal出来的控制器。
JFPresentationController.swift
import UIKit class JFPresentationController: UIPresentationController { override func containerViewWillLayoutSubviews() { super.containerViewWillLayoutSubviews() // 设置容器视图透明背景 containerView?.backgroundColor = UIColor(white: 0, alpha: 0.2) // 呈现视图 presentedView()?.frame = CGRectMake((kScreenW - 200) * 0.5, 56, 200, 300) // 添加点击手势 let tap = UITapGestureRecognizer(target: self, action: "didTappedContainerView") containerView?.addGestureRecognizer(tap) } // MARK: - 容器视图区域的点击手势 @objc private func didTappedContainerView() { NSNotificationCenter.defaultCenter().postNotificationName("PopoverDismiss", object: nil) presentedViewController.dismissViewControllerAnimated(false, completion: nil) } }
然后再创建两个类,遵守UIViewControllerAnimatedTransitioning协议并实现对应方法,这两个类的作用是自定义我们modal时的转场动画。
JFPopoverModalAnimation.swift
import UIKit class JFPopoverModalAnimation: NSObject, UIViewControllerAnimatedTransitioning { // 动画时间 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.25 } // modal动画 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 获取到需要modal的控制器的view let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! // 将需要modal的控制器的view添加到容器视图 transitionContext.containerView()?.addSubview(toView) toView.transform = CGAffineTransformMakeScale(1, 0) toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0) // 动画缩放modal的控制器的view到正常大小 UIView.animateWithDuration(transitionDuration(nil), delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 2, options: UIViewAnimationOptions(rawValue: 0), animations: { () -> Void in toView.transform = CGAffineTransformIdentity }, completion: { (_) -> Void in transitionContext.completeTransition(true) }) } }
JFPopoverDismissAnimation.swift
import UIKit class JFPopoverDismissAnimation: NSObject, UIViewControllerAnimatedTransitioning { // 动画时间 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.25 } // dismiss动画 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 获取到modal出来的控制器的view let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)! // 动画缩放modal出来的控制器的view到看不到 UIView.animateWithDuration(transitionDuration(nil), delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 2, options: UIViewAnimationOptions(rawValue: 0), animations: { () -> Void in fromView.transform = CGAffineTransformMakeScale(1, 0.001) }, completion: { (_) -> Void in transitionContext.completeTransition(true) }) } }
然后回到JFHomeViewController添加导航栏标题按钮的点击事件,指定transitioningDelegate委托对象和modalPresentationStyle样式。当点击了按钮就改变箭头方向,并创建我们需要modal的控制器。这里监听按钮点击事件我使用了ReactiveCocoa,只要看闭包里的代码就行了。
JFHomeViewController.swift
titleButton.rac_signalForControlEvents(UIControlEvents.TouchUpInside).subscribeNext({ (button) -> Void in // 改变箭头方向 titleButton.selected = !titleButton.selected // 标题下的控制器 let vc = JFPopViewController() vc.transitioningDelegate = self vc.modalPresentationStyle = UIModalPresentationStyle.Custom self.presentViewController(vc, animated: false, completion: nil) })
扩展JFHomeViewController遵守协议,实现委托方法,返回我们自定义的UIPresentationController类的对象。这样我们就可以通过我们自定义的这个类去控制器modal的控制器视图了。
JFHomeViewController.swift
// MARK: - UIViewControllerTransitioningDelegate委托方法 extension JFHomeViewController: UIViewControllerTransitioningDelegate { // 返回一个控制modal视图大小的对象 func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? { return JFPresentationController(presentedViewController: presented, presentingViewController: presenting) } // 返回一个控制器modal动画效果的对象 func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return JFPopoverModalAnimation() } // 返回一个控制dismiss动画效果的对象 func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return JFPopoverDismissAnimation() } }
在viewDidLoad里注册通知,当接收到popoverDismiss的通知就改变按钮的状态,从而达到实现箭头旋转。这里通知也使用的ReactiveCocoa,如果没有接触过,请看闭包里的代码就好。
JFHomeViewController.swift
NSNotificationCenter.defaultCenter().rac_addObserverForName("PopoverDismiss", object: nil).subscribeNext({ (_) -> Void in // 获取导航栏标题按钮 let button = self.navigationItem.titleView as! JFTitleButton button.selected = false })
最后别忘了注销通知
JFHomeViewController.swift
deinit { NSNotificationCenter.defaultCenter().removeObserver(self) }