抗拒“组合”的UITabBarController
2010-12-13 13:38 by 老赵, 7913 visits最近在写一个iPhone应用程序,基于MonoTouch,所以在开发方面的问题,基本都是在界面元素的搭建上。这个程序界面相对比较复杂,于是我根据自己的想法来进行组合,结果发现UITabBarController不能放入其他的视图内,而只能直接放在Window上(或Window里的UINavigationController里),否则就会出现界面向下偏移的情况。现在虽然有workaround,但是对于UITabBarController抗拒组合的情况,只能深表叹息了。
先来看一下这个界面的最终效果吧:
这里再简单描述一下效果。打开程序以后看到的是一个标准的带有两个标签界面,目前选中第一个,界面上有一个按钮。点击按钮,整个界面(连同下方的标签栏)都会切换至新界面,并可以退回。在第二个标签内则有另一个按钮,点击则会切换至新一级,注意此时下方的标签栏会保持不变。不过这个程序最关键的一点在于,标签栏上放浮动着一个文字区域,它独立于标签栏中的各个视图。
显然这里会需要一个UINavigationController作为根元素,其中放入一个UITabBarController和文字区域。在UITabBarController的第二个标签中,则放入另一个UINavigationController。由于“根导航”在切换时,UITabBarController和文字区域要同时显示和消失,于是我很自然地打算将UITabBarController和一个UILabel组合成一个自定义的UIViewController。这样的界面本该很容易,因为控件的组合是编写界面的常用手段。
于是我新建了一个CustomController.xib文件,放入了一个UITabBarController和一个UILabel,并补充了一些操作逻辑(显示拖动时的坐标)。这些文字显示的逻辑本就属于CustomController,在实际应用中它也会和UITabBarController内部的视图产生交互,因此我把这些逻辑都隐藏在CustomController中。我认为这个组合方式十分合理。不过,在界面上显示MyTabBarController后则出现了奇怪的状况:
当然,上面这个只是我直接把一个UITabBarController的View放入Window里的UIView控件之后出现的情况:向下整体偏移了。这个偏移量是最上方状态栏的高度,这让我很摸不着头脑。经过了一整下午的纠结试验,我的结论是:似乎UITabBarController抗拒组合。说地具体一些:UITabBarController会把视图中的UITabBar控件定位在屏幕下方,但是在计算位置的时候,它不会关注自己父容器,而是茫然地认为自己一定是在根窗体上,于是会把顶部状态栏的高度考虑进去。于是,如果它的父视图不是从整个界面(包括状态栏)的顶部算起,UITabBar的位置便会出现偏移了。
有资深iOS开发者告诉我,我把UITabBarController组合到另一个UIViewController里不是UITabBarController的标准用法,如果我要组合,就应该使用UITabBar控件自己搭配,自己处理切换逻辑。不过我始终认为UI元素(不单指控件)应该意识到自己会参与组合。例如UITableViewController,UIImagePickerController,组合使用是它们的天然职责。在我看来,如其他(我用过的)UI库一般,只要为每个控件设好Dock(类似iOS里的Auto Resizing),组合起来应该是随意自然的。
既然不能组合,那么扩展的方法似乎只有继承了——这样我便不能使用Interface Builder绘制界面,麻烦了不少。这里我创建一个MyTabBarController继承UITabBarController,并补充一些逻辑:
public class MyTabBarController : UITabBarController { /* Constructors */ public override void ViewDidLoad() { base.ViewDidLoad(); this.m_label = new UILabel() { Text = "Hello MonoTouch", TextAlignment = UITextAlignment.Center, Frame = new System.Drawing.RectangleF(0, 400, 320, 20), AutoresizingMask = UIViewAutoresizing.FlexibleTopMargin }; this.View.AddSubview(this.m_label); this.View.BringSubviewToFront(this.m_label); } private UILabel m_label; public override void TouchesMoved(NSSet touches, UIEvent evt) { base.TouchesMoved(touches, evt); var touch = (UITouch)touches.AnyObject; var location = touch.LocationInView(this.View); this.m_label.Text = location.ToString(); } }
这样,就差不多了,剩下的就是简单地嵌套关系,以及在切换到第二个Tab的时候隐藏“根导航”的导航栏。
如果您有更好的做法,请务必告诉我。
我一不小心把评论都删了……