Swift TabBar Item转Window坐标异常:索引4错误、索引2返回索引4值
问题分析与解决方案
你遇到的这个坐标混乱问题,其实是两个核心因素共同导致的:错误的坐标转换逻辑,加上依赖了苹果未公开的私有属性来获取TabBarItem视图,这种做法本身就没有官方保障,很容易出问题。
先说说你代码里的核心错误
你写的 lastTab.convert(lastTab.frame, to: window) 逻辑完全搞反了坐标系:
lastTab.frame是这个小视图在**它的父视图(也就是UITabBar)**里的位置和大小,属于父视图的坐标系。- 而
convert(_:to:)方法的作用是,把当前视图自身坐标系下的矩形,转换成目标视图(这里是window)的坐标系。
用子视图去转换它自己的frame(父坐标系的rect),相当于把父视图里的位置又叠加了子视图的偏移,结果自然会出现坐标翻倍、超出屏幕的情况——这就是为什么你取第5个item时X坐标变成了665,完全不对。
再说说私有属性的坑
通过 value(forKey: "view") 获取UITabBarItem的视图,是在调用苹果的私有API。UITabBar内部的布局逻辑是黑盒:当TabBar项太多(比如5个超出屏幕宽度),系统会自动用滚动布局来容纳这些item,此时item的视图可能会被重用,或者它的frame是相对于内部滚动视图的contentOffset,而非TabBar本身。这就会出现“取索引2的item,却得到索引4的坐标”的诡异情况——你拿到的视图根本不是你预期的那个TabBarItem对应的视图。
正确的实现方式(安全又可靠)
我们完全可以用苹果官方提供的API来解决这个问题,不需要碰私有属性:
guard let tabBar = tabBarController?.tabBar else { return } // iOS 13+ 正确获取keyWindow的方式,适配多场景 guard let window = UIApplication.shared.connectedScenes .filter({ $0.activationState == .foregroundActive }) .compactMap({ $0 as? UIWindowScene }) .first?.windows .first(where: { $0.isKeyWindow }) else { return } // 获取第1个TabBarItem的Window坐标 if let firstItemFrame = tabBar.frameForItem(at: 0) { let firstItemWindowRect = tabBar.convert(firstItemFrame, to: window) print("第1个Item的Window坐标:\(firstItemWindowRect)") } // 获取第5个TabBarItem的Window坐标 if let lastItemFrame = tabBar.frameForItem(at: 4) { let lastItemWindowRect = tabBar.convert(lastItemFrame, to: window) print("第5个Item的Window坐标:\(lastItemWindowRect)") }
代码解释:
tabBar.frameForItem(at:):这是苹果iOS13+提供的官方API,能准确返回指定索引的TabBarItem在TabBar坐标系中的frame,完全不用担心视图重用或布局偏移的问题。- 正确的坐标转换:因为
firstItemFrame是TabBar坐标系下的矩形,所以我们用tabBar.convert(...)来转换,把TabBar里的位置转成window的坐标,逻辑完全正确。 - 适配多场景的Window获取:iOS13以后支持多窗口场景,用
connectedScenes来获取当前活跃的keyWindow,比直接遍历windows更可靠。
验证一下
用这个代码重新测试,不管是第1个、第3个还是第5个TabBarItem,你都会得到准确的Window坐标,不会再出现索引混乱或者坐标超出屏幕的情况。
内容的提问来源于stack exchange,提问作者Lance Samaria
相关产品推荐
相关产品推荐

