iOS Swift:父视图应用旋转变换时禁止子按钮旋转且避免变形
这个问题我做罗盘类功能的时候也踩过坑!核心原因是当你给父视图应用CGAffineTransform后,子视图的坐标系会跟着父视图一起变换,这时候直接给子视图加反向变换,会因为父视图已经改变的bounds(比如旋转后宽高互换)导致尺寸拉伸——因为反向变换是基于父视图变换后的坐标系计算的,自然会出现变形。
最稳妥的解决方案:调整视图层级
不要把按钮放在需要旋转的compass_view里面,而是让compass_view和所有按钮处于同一父视图层级(比如直接加到控制器的view上),然后用Auto Layout约束按钮和compass_view的相对位置。这样compass_view旋转时,按钮完全不受影响,既不会跟着转,尺寸也不会变形。
步骤1:调整视图层级(代码或Storyboard都可以)
如果是代码创建视图,把按钮从compass_view的子视图改成控制器view的子视图:
// 移除按钮从compass_view的子视图(如果之前加过) optionbutton1.removeFromSuperview() // ... 其他按钮同理 // 把按钮和compass_view都加到控制器的view上 self.view.addSubview(compass_view) self.view.addSubview(optionbutton1) self.view.addSubview(option_button2) self.view.addSubview(option_button3) self.view.addSubview(option_button4) self.view.addSubview(option_button5)
步骤2:用Auto Layout绑定按钮和compass_view的位置
比如让按钮固定在compass_view的顶部、底部或侧边,保持相对位置不变:
// 先关闭自动布局转换 optionbutton1.translatesAutoresizingMaskIntoConstraints = false // 示例:让按钮居中在compass_view顶部,距离顶部10pt NSLayoutConstraint.activate([ optionbutton1.centerXAnchor.constraint(equalTo: compass_view.centerXAnchor), optionbutton1.topAnchor.constraint(equalTo: compass_view.topAnchor, constant: 10), // 可以加上按钮自身的宽高约束 optionbutton1.widthAnchor.constraint(equalToConstant: 60), optionbutton1.heightAnchor.constraint(equalToConstant: 60) ]) // 其他按钮同理设置约束
步骤3:修改旋转逻辑,只处理compass_view
现在你只需要旋转compass_view,按钮不需要任何变换——因为它们不在旋转的父视图层级里:
func locationManager(_ manager: CLLocationManager, didUpdateHeading heading: CLHeading) { let maxLat: Double = fmax(userLocation.latitude, userLocation.latitude) let maxLon: Double = fmax(userLocation.longitude, userLocation.longitude) let minLat: Double = fmin(userLocation.latitude, userLocation.latitude) let minLon: Double = fmin(userLocation.longitude, userLocation.longitude) let center: CLLocationCoordinate2D = CLLocationCoordinate2DMake((maxLat + minLat) / 2, (maxLon + minLon) / 2) angle2U = getBearing(toPoint: center, longitude: userLocation.longitude, latitude: userLocation.latitude) let direction2G = CGFloat(heading.magneticHeading * Double.pi/180) DispatchQueue.main.async { let direction2GN = CGFloat(self.angle2U) - direction2G let final_transform = CGAffineTransform.identity.rotated(by: direction2GN) self.compass_view.transform = final_transform // 这里移除所有按钮的transform设置,不需要了! } }
为什么原来的反向变换会导致拉伸?
当你给compass_view应用旋转变换后,它的bounds宽高会互换(比如竖屏的view旋转90度后,bounds.width变成原来的height,bounds.height变成原来的width)。这时候子视图的反向变换是基于父视图已经扭曲的坐标系计算的,所以应用后按钮的尺寸会被拉伸变形。
备选方案(不调整层级的情况)
如果因为某些原因必须让按钮作为compass_view的子视图,可以通过修正反向变换的缩放比例来解决,但这种方法只适合固定角度旋转,对于任意角度的罗盘旋转不太稳定:
DispatchQueue.main.async { let direction2GN = CGFloat(self.angle2U) - direction2G let final_transform = CGAffineTransform.identity.rotated(by: direction2GN) self.compass_view.transform = final_transform // 计算修正后的反向变换,抵消父视图旋转带来的宽高变化 let inverseTransform = final_transform.inverted() // 根据父视图旋转后的bounds修正缩放比例 let scaleX = self.compass_view.bounds.width / self.compass_view.bounds.height let scaleY = self.compass_view.bounds.height / self.compass_view.bounds.width let correctedInverse = inverseTransform.scaledBy(x: scaleX, y: scaleY) // 给按钮应用修正后的变换 self.optionbutton1.transform = correctedInverse self.option_button2.transform = correctedInverse // ... 其他按钮同理 }
不过还是更推荐第一种调整层级的方案,逻辑简单且稳定,完全不会出现变形问题。
内容的提问来源于stack exchange,提问作者Kavitha Madhu

