美文网首页开发iOS 程序员iOS
ios在ScrollView中嵌套多个TableView

ios在ScrollView中嵌套多个TableView

作者: EgeTart | 来源:发表于2015-08-16 11:16 被阅读25116次

    说明:示例在XCode7 beta5完成

    创建一个Single View Application,填写相应的信息. Language选择Swift,把项目存储到一个目录,单击Create完成创建.

    Storyboard设计阶段
    1. 打开Main.storyboard文件,取消勾选Use Size Classes,让设计视图呈现iphone的大小.



      下面是取消勾选后,设计面板的样子.



      现在开始在storyboard中添加要使用到的控件.
      从控件库中拖出一个Segmented Control到设计面板的上方.

      选中Segmented Control,给它增加如下约束.



      接下来拖一个ScrollView到到设计面板中,让它铺满剩余的空间.

      同样也要给ScrollView增加约束,这样才可以适应不同屏幕的大小.

      接着拖一个TableView到设计面板中,放置在ScrollView上,成为ScrollView的子视图.

      调整它的大小,让它的大小和ScrollView的相同.

      现在给TableView加上约束.



      此时设计面板中多了一些红色的线条,这说明缺少约束或者约束有冲突.

      通过以下步骤来增加约束,这样storyboard就有足够的信息来确定控件的位置关系.点击Document Outline左上方的红色小圆圈,在点击空心的红色小圆圈,在弹出框中选择Add Missing Constraints.

      接着个体TableView增加一个Prototype Cell.

      然后展开TableView选中Table View Cell,给它设置一个重用ID.

      为了区分接下来要添加的TableView,选中当前的TableView,给它设置一个tag,这里设置为101,再给它取一个名字.

      接下来添加第二个TableView,为了方便操作,在控件库中直接把TableView拖放至Document Outline中,让它位于ScrollView的下方,成为ScrollView的子视图.

      使用相同的方式给刚刚添加的TableView设置一个tag,这里设置为102,给它取一个名字,叫做SecondTableView.再给SecondTableView增加一个Prototype Cell,并设置它的reused identifier为second.
      接下来这一步比较关键,要改变SecondTableView的位置,这样才能给SecondTableView添加上正确的约束.把SecondTableView的x坐标设置为320,完成后,会把SecondTableView移动到设计面板之外,如下所示.

      现在选中SecondTableView为它增加约束.

      此时又出现了红色的线条.没关系,有这个方法让红色线条消失.选中FirstTableView,按住ctrl键,鼠标左键从FirstTableView拖出一条箭头到SecondTableView,松开鼠标在弹出菜单中选择Equal Widths.这样做的结果是,两个TableView具有相同的宽度.

      ok,到此为止,storyboard的设计工作完成,接下来进入代码阶段.

    代码阶段

    打开辅助视图,为设计面板中的控件生成相应的outlet.同时为Segmented Control绑定一个Action,它的事件类型为Value Changed,可以把Segmented Control上的items当作独立的button来使用.



    完成后,代码文件会类似于这样.

    import UIKit
    class ViewController: UIViewController {
        // 1. 拖拽生成控件的outlet
        @IBOutlet weak var segmented: UISegmentedControl!
        @IBOutlet weak var scrollView: UIScrollView!
        @IBOutlet weak var firstTableView: UITableView!
        @IBOutlet weak var secondTableView: UITableView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
           
        }
    
        // 2. 当Segmented Control选择的item改变时,会触发这个Action
        @IBAction func tabChanged(sender: AnyObject) {
    
        }
    }
    

    为了不让Segmented Control给状态栏遮挡,在viewDidLoad()函数的下方,添加以下代码,把状态栏隐藏掉.

    // 3. 隐藏状态栏
        override func prefersStatusBarHidden() -> Bool {
            return true
    }
    

    让当前ViewController遵循UITableViewDataSource协议,这样就能够给TableView提供数据.类定义的头部将会是下面的样子.
    class ViewController: UIViewController, UITableViewDataSource
    这里为什么不让ViewController遵循UITableViewDelegate协议呢.因为这个例子只是给TableView填充数据,并不处理发生在TableView上的行为事件.
    接着实现两个代理方法,为TableView填充数据.把这两个方法添加在prefersStatusBarHidden()函数的下方.

    // 4. 为TableView填充数据
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 20
        }
        
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            var reusedID: String!
            
            if tableView.tag == 101 {
                reusedID = "first"
            }
            else {
                reusedID = "second"
            }
            
            let cell = tableView.dequeueReusableCellWithIdentifier(reusedID) as UITableViewCell!
            
            if tableView.tag == 101 {
                cell.textLabel!.text = "第一个TableView"
            }
            else {
                cell.textLabel!.text = "第二个TableView"
            }
            
            return cell
        }
    

    这里利用tag来判断是哪一个TableView,然后使用设置好的reused identifier来获取到cell,给cell的textLabel设置简单的字符串.
    在viewDidLoad()函数中,添加以下两行代码.这样TableView的代理方法将会由当前的ViewController来执行.

    firstTableView.dataSource = self
    secondTableView.dataSource = self
    

    选择一个模拟器,运行一下看有什么效果.



    第一个TableView已经呈现出来了,试着往左滑动,把第二个TableView呈现出来.滑了几次,发现不能将SecondTableView呈现出来,为什么会这样呢??难道SecondTableView没有添加到ScrollView中.利用前面添加的Action来做个实验,看是否把SecondTableView添加到了ScrollView中.
    首先viewDidLoad()函数的上方,定义一个变量,用来记录ScrollView的内容偏移量.

    // 5. 定义一个变量来记录scrollview的内容偏移量
        var offset: CGFloat = 0.0 {
            
            // 当offset的值改变后会执行didSet代码块
            didSet {
                UIView.animateWithDuration(0.3) { () -> Void in
                    self.scrollView.contentOffset = CGPoint(x: self.offset, y: 0.0)
                }
            }
        }
    

    didSet代码块的作用是,用一个0.3秒的过度来设置ScrollView的内容偏移量.
    接着在 tabChanged(sender: AnyObject) Action中添加以下代码.

    // a. 获取到当前item的下标
    let index = (sender as! UISegmentedControl).selectedSegmentIndex
    // b. 设置scrollview的内容偏移量
    offset = CGFloat(index) * self.view.frame.width
    

    item的下标从0开始.因为TableView的宽度和屏幕的宽度相同,所以呈现FirstTableView时ScrollView的内容偏移量为0,呈现SecondTableView时ScrollView的内容偏移量为一个屏幕的宽度,即(self.view.frame.width).
    再运行一遍程序,当点击Second item时,可以把SecondTableView呈现出来,说明有把SecondTableView加到ScrollView中.



    到这个阶段的完整代码如下.

    import UIKit
    class ViewController: UIViewController, UITableViewDataSource {
        // 1. 拖拽生成控件的outlet
        @IBOutlet weak var segmented: UISegmentedControl!
        @IBOutlet weak var scrollView: UIScrollView!
        @IBOutlet weak var firstTableView: UITableView!
        @IBOutlet weak var secondTableView: UITableView!
        
        // 5. 定义一个变量来记录scrollview的内容偏移量
        var offset: CGFloat = 0.0 {
            
            // 当offset的值改变后会执行didSet代码块
            didSet {
                UIView.animateWithDuration(0.3) { () -> Void in
                    self.scrollView.contentOffset = CGPoint(x: self.offset, y: 0.0)
                }
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
           
            firstTableView.dataSource = self
            secondTableView.dataSource = self
            
        }
        
        // 2. 当Segmented Control选择的item改变时,会触发这个Action
        @IBAction func tabChanged(sender: AnyObject) {
            // a. 获取到当前item的下标
            let index = (sender as! UISegmentedControl).selectedSegmentIndex
            
            // b. 设置scrollview的内容偏移量
            offset = CGFloat(index) * self.view.frame.width
        }
        
        // 3. 隐藏状态栏
        override func prefersStatusBarHidden() -> Bool {
            return true
        }
        
        // 4. 为TableView填充数据
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 20
        }
        
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            var reusedID: String!
            
            if tableView.tag == 101 {
                reusedID = "first"
            }
            else {
                reusedID = "second"
            }
            
            let cell = tableView.dequeueReusableCellWithIdentifier(reusedID) as UITableViewCell!
            
            if tableView.tag == 101 {
                cell.textLabel!.text = "第一个TableView"
            }
            else {
                cell.textLabel!.text = "第二个TableView"
            }
            return cell
        }
    }
    

    为什么滑动操作不成功呢,网上看到有人说TableView继承自ScrollView,那么滑动手势很可能在TableView拦截了,因此为ScrollView增加两个滑动手势识别器.
    在viewDidLoad()函数中添加以下代码.

    // 6. 为scrollView增加滑动手势识别器
            let swipeLeft = UISwipeGestureRecognizer(target: self, action: "swipe:")
            swipeLeft.direction = .Left
            swipeLeft.numberOfTouchesRequired = 1
            
            let swipeRight = UISwipeGestureRecognizer(target: self, action: "swipe:")
            swipeRight.direction = .Right
            swipeRight.numberOfTouchesRequired = 1
            
            scrollView.addGestureRecognizer(swipeLeft)
            scrollView.addGestureRecognizer(swipeRight)
    

    定义了两个滑动的手势识别器,方向分别向左和向右,numberOfTouchesRequired的意思是只要单点触摸就可以完成滑动操作.
    把swipe()函数添加到最后一个花括号的上方.

    // 滑动手势处理函数
        func swipe(gesture: UISwipeGestureRecognizer) {
            
            if gesture.direction == .Left {
                // 向左滑时展示第二个tableview,同时设置选中的segmented item
                offset = self.view.frame.width
                segmented.selectedSegmentIndex = 1
            }
            else {
                offset = 0.0
                segmented.selectedSegmentIndex = 0
            }
        }
    

    0k,代码阶段也结束了.
    运行一下程序, 左右滑动可以呈现不同的TableView,选中的segmented item也会跟着改变.
    图片加载不出来的话, 这里有pdf格式的,提取码:84e9

    相关文章

      网友评论

      • PointOne:作者这种情况下不需要添加手势,你在viewDidAppear里设置scrollView的contenSize即可
      • LFDevJourney:图片加载不进来,分享的链接也失效了。
        EgeTart:@bdeee0a8ba04 兄弟,实在有点对不住!你可以看看这篇文章,用更简单的方式,实现了相同的功能。http://www.jianshu.com/p/807dadec8559
      • koreadragon:我的scrollView管理两个tableViewController的view,然后左右滑动的时候发现左右偏移量有问题,左边老是卡那么一点,了解吗?
        EgeTart:@koreadragon 我碰到类似的问题,只是因为模拟器大小和storyboard中的不一致
        koreadragon:@EgeTart 但是在加载完数据后,再次返回那个页面,发现scrollView有一点偏移,更诡异的是4.7的屏幕好着,4吋的屏幕就有问题
        EgeTart:@koreadragon 可能是滑到第二个tableview的时候,它需要加载数据,这样才导致卡顿的。
      • dff31b46c8d8:您好,我像请问一下,在scrollview上面放了3个tableView,为什么指定偏移量后不显示控件,只有滑动一下才能显示tableView呢
        EgeTart:@强子0001 可以看看你的代码不?
      • 晚熟人:有没有OC全代码写啊demo啊
        EgeTart:@忆如初 没有喔,当时还不会OC
      • e44c4f6fe069:你好,我想请问一下,cell中怎么获取数组中的值,我已经将数据库中的值存入了数组中,不知道在cell中怎么显示出来
        EgeTart:@_try 用tableview的数据源方法,返回组数和行数,再根据数组的内容去设置cell
      • 南虞:作者,我想请教一下,我是一个scrollview里面放了一个scrollview,然后再第二个scrollview里放了三个tabelview,其他问题都解决了 但是我tabelview里的数据翻页的话 最外层的scrollview没办法上拉,响应时间还是tabelview里的滚动事件 有没有办法判断scrollView.contentOffset.y == 0到顶部的时候响应最外层scrollview的办法呀,或者其他解决办法,多谢啦~
        EgeTart:@南虞 你scrollview 的上下滑动是为了实现怎样的功能
      • 梓茜DiannaWong:完美!!!换到oc一样可以用~~非常感谢,简单好用
        LaiBit:@EgeTart 謝謝你,好,我試試看,雖然不是很懂set方法如何實作?
        EgeTart:@LaiBit 使用offset 这个属性的set方法来做这个。
        LaiBit:@梓茜Dianna 這位大神,我不是很懂在這一段該如何轉乘OC,是否能請教你一下:
        // 5. 定义一个变量来记录scrollview的内容偏移量
        var offset: CGFloat = 0.0 {
        // 当offset的值改变后会执行didSet代码块
        didSet {
        UIView.animate(withDuration: 0.3, animations: { () -> Void in
        self.scrollView.contentOffset = CGPoint(x: self.offset, y: 0.0)
        })
        }
        }
      • dde517446338:你好我问下, CollectionView每个 Cell 上一个 tabelView tabelView左滑删除和 CollectionView横向滚动冲突 能解决么. 当我左滑删除想删除 tabelView 的 cell 的时候, collection 向左滚动了
        EgeTart:@CHNVI 感觉好复杂,我还想不到要怎么做。
      • 1b2ae550dc99:请问一下,我用oc写网易那种效果,我在scrollview上放了VC,VC放tableView,但是下滑的时候,tableview上的cell会删除掉,我应该怎么处理?
        EgeTart:@行进的NSLog 请问你是说网易的哪个App,你是想要有怎样的效果?
        1b2ae550dc99:@行进的NSLog 按照你的讲解,我是不是应该添加一个下滑手势识别,然后通过手指滑动的距离,来实现tableview的滑动
      • 甘蔗222:代码是怎么贴上去的?
        EgeTart:@gaoze 使用的是Markdown语法,你可以看看。
      • bf7edeaa0ddd:如果一个页面有多个tableview放在一个scrollview里,scrollview放在控制器的view上 刷新该加到哪个上?
        EgeTart:@happycoder 是看哪个tableview要重新加载数据,如果全都要的话,就让所有tableview 都重新去加载数据。
        bf7edeaa0ddd:@EgeTart 三个tableview都加吗?
        EgeTart:@happycoder 让tableview 去刷新,重新加载数据。
      • ufogxl:写得真好
        EgeTart:@ufogxl 谢谢
      • 08e79b420a92:为什么图片都加载不出来呢:flushed:
        08e79b420a92:@EgeTart 谢谢👼
        EgeTart:@眼睛里的水小小小木易 https://yunpan.cn/OcYeC5UqKUTPnf (提取码:84e9),这里有pdf版的。
      • Joe_lisa:这样才能给SecondTableView添加上正确的约束.把SecondTableView的x坐标设置为320,完成后,会把SecondTableView移动到设计面板之外, 一步步操作,在这里断了,因为上面的X坐标变不了320,不知道怎么回事
      • 0918875d21c4:用手势就没有平滑效果了
      • 土b兰博王:很赞,虽然不太会swift,但是看着思路用xib拉了个一模一样的,感谢po主!
      • LoveLinXue:用一个table刷新不同的数据可以不?
        EgeTart:@沐雪灵风 可以的,只是这样就失去了滑动的效果,可以看下网易新闻的客户端。
      • 南国青天:你们左右滑动手势, 是跟着手势滑动视图(类似苹果自带的 Pop手势), 还是通过手势触发一个滑动时间 ? 来个Demo看下呗!
        南国青天:@EgeTart Xcode6.3, Swift 2.0 貌似用不了..
        EgeTart:@南望青天 Demo在这里https://github.com/EgeTart/ScrollViewEmbedTableViews,应该说手势操作本身是一个事件,我们用手势识别器去监听它,如果有这个事件发生,就执行一些相关的操作.
      • 十一岁的加重:挺不错的,放个demo吧github上
        EgeTart:@十一岁的加重 https://github.com/EgeTart/ScrollViewEmbedTableViews,感谢支持

      本文标题:ios在ScrollView中嵌套多个TableView

      本文链接:https://www.haomeiwen.com/subject/kvmwqttx.html