swift4.2实现新闻首页导航

对于仿照新闻首页的页面,已经有比较好用的OC版本,现在我们来写一个swift版本的。

设备:xcode 10.2     语言:swift 4.2

效果图:

我们先创建一个多控制器的导航栏,直接上代码:

//
// JHSBarItemView.swift
// ScrollBarController
//
// Created by yaojinhai on 2019/4/15.
// Copyright © 2019年 yaojinhai. All rights reserved.
//

import UIKit

enum BarItemBorderType {
  case defualt
  case barItem
  case maskView
  case customItem
}

protocol JHSBarItemViewDelegate: NSObjectProtocol {
  func selectedIndexItem(view: JHSBarItemView,index: Int) -> Void
}

class JHSBarItemView: UIView {

  var minMargin: CGFloat = BarConfig.minMargin;

  weak var delegate: JHSBarItemViewDelegate?
  var lineBarView: UIView!

  var barType = BarItemBorderType.defualt {
    didSet{
      configBarType();
      removeBarItem(idx: selectedIndex);
    }
  }

  var selectedIndex = 0;
  var titles: [String]!{
    didSet{
      caculateItemSize();

    }
  }

  private var titlesView: UICollectionView!
  private var cachesSize = [String:CGSize]();

  override init(frame: CGRect) {
    super.init(frame: frame);
    createContentView();
  }

  convenience init(frame: CGRect,titles: [String]) {
    self.init(frame: frame);
    self.titles = titles;
    createContentView();
    caculateItemSize();

  }

  func progressWidth() -> Void {

  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

}

extension JHSBarItemView: UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {

  private func caculateItemSize() -> Void {

    guard let itemTitles = titles ,itemTitles.count > 0 else {
      return;
    }

    var maxWidth: CGFloat = 0;
    for item in itemTitles {
      let size = item.textSize(size: CGSize(width: width, height: height), font: BarConfig.normalFont);
      cachesSize[item] = CGSize(width: size.width, height: height);
      maxWidth += size.width;
    }
    let gap = (width - maxWidth) / CGFloat(itemTitles.count + 1);
    minMargin = max(gap, BarConfig.minMargin);
    titlesView.reloadData();
    removeBarItem(idx: selectedIndex);

  }

  private func createContentView() -> Void {

    if titlesView != nil {
      return;
    }
    let layout = UICollectionViewFlowLayout();
    layout.minimumLineSpacing = 0;
    layout.minimumInteritemSpacing = 0;
    layout.scrollDirection = .horizontal;
    titlesView = FMBaseCollectionView(frame: .init(x: 0, y: 0, width: width, height: height), collectionViewLayout: layout);
    addSubview(titlesView);
    titlesView.register(BarItemViewCell.self, forCellWithReuseIdentifier: "title");
    titlesView.delegate = self;
    titlesView.dataSource = self;

    let lineView = createView(rect: .init(x: 0, y: height - 1, width: width, height: 1));
    lineView.backgroundColor = rgbColor(rgb: 234);

  }

  // MARK: - collection view delegate and dataSource

  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return titles?.count ?? 0;
  }
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    return UIEdgeInsets(top: 0, left: minMargin, bottom: 0, right: minMargin);
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    if titles == nil {
      return CGSize.zero;
    }
    let item = titles[indexPath.row];
    if let size = cachesSize[item] {
      return CGSize(width: size.width + minMargin, height: height);
    }
    let size = titles[indexPath.row].textSize(size: CGSize.init(width: width, height: height), font: BarConfig.normalFont);
    let newSize = CGSize(width: size.width + minMargin, height: height);
    cachesSize[item] = size;
    return newSize;
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "title", for: indexPath) as! BarItemViewCell;
    cell.titleLabel.text = titles[indexPath.row];
    cell.titleLabel.isHighlighted = selectedIndex == indexPath.row;
    return cell;
  }

  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    didSelected(idx: indexPath.row);
    delegate?.selectedIndexItem(view: self, index: indexPath.row);
  }

  func didSelected(idx: Int) -> Void {
    let indexPath = IndexPath(item: idx, section: 0);
    titlesView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true);

    let cell = titlesView.cellForItem(at: indexPath) as? BarItemViewCell;
    cell?.titleLabel.isHighlighted = true;
    cell?.RunAnimation();

    removeBarItem(idx: indexPath.row);

    if selectedIndex != indexPath.row {
      let preCell = titlesView.cellForItem(at: .init(row: selectedIndex, section: 0)) as? BarItemViewCell;
      preCell?.titleLabel.isHighlighted = false;
      preCell?.RunAnimation();
      selectedIndex = indexPath.row;

    }
  }
}

extension JHSBarItemView {

  private func removeBarItem(idx: Int) {
    if barType == .barItem {
      let size = getMaxWidthAt(index: idx);
      lineBarView.frame = .init(x: size.width, y: height - 2, width: size.height, height: 2);
    }else if barType == .maskView {
      let size = getMaxWidthAt(index: idx);
      lineBarView.frame = .init(x: size.width - minMargin/2, y: 0, width: size.height + minMargin, height: height);
    }
  }
  func getMaxWidthAt(index: Int) -> CGSize {
    if titles == nil || titles.count == 0 {
      return CGSize.zero;
    }

    var maxWidth: CGFloat = minMargin;
    var sizeWidth: CGFloat = cachesSize[titles[0]]!.width;
    if index > 0 {
      for item in 1...index {
        let title = titles[item];
        let size = cachesSize[title]!;
        maxWidth += size.width + minMargin;
        sizeWidth = size.width;
      }
    }
    return CGSize(width: maxWidth + minMargin/2, height: sizeWidth);
  }

  private func configBarType() -> Void {
    if barType == .barItem {
      if lineBarView == nil {
        lineBarView = createView(rect: .init(x: 0, y: height - 2, width: 30, height: 2));
        lineBarView.backgroundColor = UIColor.red;
        lineBarView.layer.cornerRadius = 2;
        lineBarView.layer.masksToBounds = true;
      }
      titlesView.addSubview(lineBarView);
    }else if barType == .maskView {
      if lineBarView == nil {
        lineBarView = createView(rect: .init(x: 0, y: 0, width: 30, height: height));
        lineBarView.backgroundColor = UIColor.green.withAlphaComponent(0.2);
        lineBarView.isUserInteractionEnabled = false;
      }
      titlesView.addSubview(lineBarView);
    }else{
      titlesView?.removeFromSuperview();

    }
  }
}

class BarItemViewCell: UICollectionViewCell {
  var titleLabel: UILabel!
  override init(frame: CGRect) {
    super.init(frame: frame);
    titleLabel = createLabel(rect: bounds, text: "");
    titleLabel.textAlignment = .center;
    titleLabel.textColor = BarConfig.normalColor;
    titleLabel.highlightedTextColor = BarConfig.hlightedColor;
    titleLabel.font = BarConfig.normalFont;
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  func RunAnimation(animation: Bool = true) -> Void {
    self.titleLabel.font = self.titleLabel.isHighlighted ? BarConfig.hlightedFont : BarConfig.normalFont;

  }
}

这个封装了导航栏的操作,并且实现了自动刷新功能。我们也可以自己扩展实现。

我们来定义一个控制器:

//
// JHSBarController.swift
// ScrollBarController
//
// Created by yaojinhai on 2019/4/15.
// Copyright © 2019年 yaojinhai. All rights reserved.
//

import UIKit

protocol JHSBarControllerDelegate: NSObjectProtocol {
  func barControllerAt(controller: JHSBarController, index: Int) -> UIViewController;
  func barControllerForTitle(controller: JHSBarController,index: Int) -> String?
  func numberOfController(controller: JHSBarController) -> Int
}

class JHSBarController: JHSBaseViewController {

  private var topBarView: JHSBarItemView!
  private var cachesController = [Int:UIViewController]();
  private var cachesTitles = [Int:String]();
  private var countOfContrller = 0;

  weak var delegate: JHSBarControllerDelegate?

  var currentViewController: UIViewController{
    return getControllerAt(idx: selectedIndex);
  }

  var selectedIndex: Int {
    get {
      let offSet = contentScrollView.contentOffset;
      let index = Int((offSet.x + 10)/width());
      let idx = max(0, min(index, countOfContrller - 1));
      return idx;
    }
    set{
      let idx = max(0, min(newValue, countOfContrller - 1));
      toScrollAtIndex(atIndx: idx);
    }
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    topBarView = JHSBarItemView(frame: .init(x: 0, y: 64, width: width(), height: 40), titles: ["音乐","视频","旅游","新闻"]);
    topBarView.delegate = self;
    topBarView.barType = .maskView;

    addView(tempView: topBarView);

    configContentView();
  }

  func reloadData() -> Void {
    cachesController.removeAll();
    cachesTitles.removeAll();
    countOfContrller = delegate?.numberOfController(controller: self) ?? 0;
    setBarTitles();

    contentScrollView.setContentOffset(.init(x: selectedIndex.cgFloat * width(), y:0), animated: false);
    contentScrollView.contentSize = .init(width: countOfContrller.cgFloat * width(), height: 0);
    addSubController();

  }

  private func setBarTitles() -> Void {
    var titles = [String]();
    for idx in 0..<countOfContrller {
      let tempTitme = delegate?.barControllerForTitle(controller: self, index: idx);
      cachesTitles[idx] = tempTitme ?? "";
      titles.append(tempTitme ?? "");
    }
    topBarView.titles = titles;
  }

  func isInScreen(rect: CGRect) -> Bool {
    let offset = contentScrollView.contentOffset;
    let bounds = contentScrollView.convert(.init(x: offset.x, y: offset.y, width: width(), height: contentScrollView.height), to: self.view);
    return bounds.intersects(rect);
  }

  private func toScrollAtIndex(atIndx: Int) -> Void {

    let isRight = atIndx < selectedIndex;

    let currentCtr = currentViewController;
    let currentRect = currentViewController.view.frame;

    currentCtr.view.frame.origin.x = atIndx.cgFloat * width();

    contentScrollView.contentOffset = .init(x: atIndx.cgFloat * width(), y: 0);

    let atCtroller = getControllerAt(idx: atIndx);
    let orginRect = atCtroller.view.frame;
    atCtroller.view.frame.origin.x += isRight ? -width() : width();

    contentScrollView.bringSubviewToFront(currentCtr.view);

    UIView.animate(withDuration: 0.3, animations: {
      currentCtr.view.frame.origin.x += isRight ? self.width() : -self.width();
      atCtroller.view.frame = orginRect;
    }) { (finshed) in
      currentCtr.view.frame = currentRect;

    }
  }

  private func addSubController() -> Void {

    let start = max(selectedIndex - 1, 0);
    let end = max(min(selectedIndex + 1, countOfContrller - 1), 0);

    for idx in start...end {
      _ = getControllerAt(idx: idx);
    }

  }
  private func getControllerAt(idx: Int) -> UIViewController {
    var controller = cachesController[idx];
    if controller == nil {
      controller = delegate?.barControllerAt(controller: self, index: idx);
      cachesController[idx] = controller;
    }
    let rect = CGRect(x: idx.cgFloat * width(), y: 0, width: width(), height: contentScrollView.height);
    controller?.view.frame = rect;
    if controller!.view.superview == nil {
      contentScrollView.addSubview(controller!.view);
    }
    if controller!.parent == nil {
      addChild(controller!);
    }
    if let scrollView = controller?.view as? UIScrollView {
      scrollView.contentOffset.y = 0;
    }
    if let subViews = controller?.view.subviews {
      for item in subViews {
        guard let scroll = item as? UIScrollView else{
          continue;
        }
        scroll.contentOffset.y = 0;
      }
    }
    return controller!;
  }

  func configContentView() -> Void {
    setContentScrollView(rect: CGRect.init(x: 0, y: topBarView.maxY, width: width(), height: height() - topBarView.height));
    contentScrollView.delegate = self;
    contentScrollView.isPagingEnabled = true;

  }

  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    addSubController();
  }

  func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    for item in children {
      if !isInScreen(rect: item.view.frame) {
        item.removeFromParent();
        item.view.removeFromSuperview();
      }
    }
    addSubController();
    topBarView.didSelected(idx: selectedIndex);
  }
  func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    scrollViewDidEndDecelerating(scrollView);
  }
  func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
      scrollViewDidEndDecelerating(scrollView);
    }
  }

}

extension JHSBarController: JHSBarItemViewDelegate{

  func selectedIndexItem(view: JHSBarItemView, index: Int) {
    selectedIndex = index;
  }
}

我们只要继承这个就可以了:下面我们看使用方法:

//
// MainViewController.swift
// ScrollBarController
//
// Created by yaojinhai on 2019/4/15.
// Copyright © 2019年 yaojinhai. All rights reserved.
//

import UIKit

class MainViewController: JHSBarController {

  override func viewDidLoad() {
    super.viewDidLoad()

    delegate = self;
    reloadData();

  }

}

extension JHSBarController: JHSBarControllerDelegate {
  func barControllerAt(controller: JHSBarController, index: Int) -> UIViewController {
    let ctrl = DetialViewController()
    ctrl.index = index;
    return ctrl;
  }

  func barControllerForTitle(controller: JHSBarController, index: Int) -> String? {
    return "第\(index)个页面";
  }

  func numberOfController(controller: JHSBarController) -> Int {
    return 5;
  }

}

最后付上demo地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Swift之UITabBarController 导航控制器的自定义

    swift导航控制器,导航控制器类继承UITabBarController,具体代码如下所示: // AppDelegate.swift // Housekeeper // // Created by 卢洋 on //. // Copyright © 年 奈文摩尔. All rights reserved. // import Foundation import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicat

  • swift4.2实现新闻首页导航

    对于仿照新闻首页的页面,已经有比较好用的OC版本,现在我们来写一个swift版本的. 设备:xcode 10.2     语言:swift 4.2 效果图: 我们先创建一个多控制器的导航栏,直接上代码: // // JHSBarItemView.swift // ScrollBarController // // Created by yaojinhai on 2019/4/15. // Copyright © 2019年 yaojinhai. All rights reserved. // i

  • JS+CSS实现大气的黑色首页导航菜单效果代码

    本文实例讲述了JS+CSS实现大气的黑色首页导航菜单效果代码.分享给大家供大家参考.具体如下: 这是一款JS+CSS实现的大气的重色+红色高强对比的菜单,可用作首页导航菜单,从设计的角度来讲,挺专业,从实用角度来讲,应用广泛,是款人见人爱的经典风格菜单,超不错的一个作品. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/js-css-black-style-nav-menu-codes/ 具体代码如下: <!DOCTYPE html PUBLIC

  • vue使用vuex实现首页导航切换不同路由的方法

    vue实现首页导航切换不同路由的方式(二)[使用vuex实现的],具体代码如下所示: <nav> <!-- 导航栏 --> <div class="indexNavOut"> <div class="indexNav"> <ul class="navLi"> <li @click="checkNav()" style="width: 130px;&qu

  • CSS仿淘宝首页导航条布局效果

    以下是CSS内容部分: /*子鼠*/ body{ font-size:12px; text-align:center; margin-top:30px; font-family:Verdana;} div,img{margin:0; padding:0; border:0;} ul,li{list-style-type: none; margin:0; padding:0; float:left; } #info{ margin-left:auto; margin-right:auto;widt

  • Android 利用ViewPager+GridView实现首页导航栏布局分页效果

    最近我尝试使用ViewPager+GridView实现的,看起来一切正常,废话不多说,具体代码如下: 如图是效果图 首先分析下思路 1.首先是怎么布局:整体是一个ViewPager将GridView作为一个View添加到ViewPager的adapter中,下方是圆点 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.a

  • Android仿新闻顶部导航标签切换效果

    最近由于个人兴趣原因,写了个模仿新闻顶部导航标签的demo.具体看下图. 那么大致上我们会用到这些知识. 1.Fragment 2.FragmentPagerAdapter 3.HorizontalScrollView 4.PopupWindow ok,那么首先进入第一步. 为了实现顶部的标签,我们要用到HorizontalScrollView,因为原有的HorizontalScrollView控件已经不能满足我们的使用了.所以这里就自定义一个HorizontalScrollView impor

  • Android实现腾讯新闻的新闻类别导航效果

    效果图如下所示:  1.在Adapter中加入如下代码 <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'OCR A Std';font-size:10.2pt;"><span style="color:#cc7832;">private int </span><span style="color:#9876aa;">cl

  • Android 中 TabHost与ViewPager结合实现首页导航效果

    今天发的是TabHost结合ViewPager实现首页底部导航的效果,虽然说网上有很多这样的Demo,不过呢,我还是要把自己练习写的发出来,没错!就是这么任性: 先上效果图,如下: 代码里面有注释,就不过多解释了,说几点需要注意的问题 1:TabHost .TabWidget.FrameLayout一定添加id这个属性,否则会报错 android:id="@android:id/tabhost" android:id="@android:id/tabcontent"

  • Vue mock.js模拟数据实现首页导航与左侧菜单功能

    目录 一.mock.js的使用 mock.js的使用步骤 1.安装mock依赖 2.添加开发环境及生产环境的配置 3.引入到main.js 二.前台主页面的搭建 2.1 准备相关组件 2.2 配置路由与组件的关系 2.3 导入图片 2.4 测试 三.左侧菜单的收缩功能 3.1 定义一个总线 3.2 改变收缩图标的样式 四.退出功能 4.1 添加点击方法 4.2 测试 一.mock.js的使用 mock.js的使用步骤 ① 下载依赖 npm install mock -d(开发环境使用) ② 引入

  • iOS开发--仿新闻首页效果WMPageController的使用详解

    这一篇记录的是iOS开发中第三方库WMPageController控件的使用方法,主要是用来分页显示内容的,可以通过手势滑动来切换页面,也可以通过点击标题部分来切换页面,如下图所示: 使用方法: 新建工程DemoTest1,然后通过cocoapods引入WMPageController到项目中,Podfile文件的内容如下: platform :ios,'7.0' target 'DemoTest1' do pod 'WMPageController', '~> 1.6.4' end 方法一:

随机推荐