Swift5纯代码UIViewController中UITextView无法滚动问题求助
问题背景
我正在为应用添加烟花特效,需要通过代码生成UIView与UIViewController,部分场景无法使用Storyboard的Segue。目前遇到核心问题:在Storyboard创建的UIViewController内,代码实现的包含UILabel及关联@objc函数UIButton的UITextView可正常滚动;但在纯代码生成的GameViewController(已设置代理)中,相同逻辑的UITextView完全无法滚动。技术栈为Swift 5、UIKit、Xcode 15.0。
原因分析
1. UITextView布局方式冲突
GameViewController中同时为gameStatusTextview设置了frame和translatesAutoresizingMaskIntoConstraints = false,导致手动frame布局与AutoLayout规则冲突,系统无法正确计算textView的实际可滚动区域。
2. Child ViewController实例复用错误
在ViewController的aCreateController方法中,连续创建了3个独立的GameViewController实例:
- 第一个实例设置了frame但未被使用
- 第二个实例修改了frame.origin.x也未被使用
- 第三个实例被添加为child controller,但未设置任何布局属性
所有frame设置均为无效操作,GameViewController的view尺寸未被正确定义,进而影响内部textView的滚动逻辑。
3. ScrollView的ContentSize与Child View约束不匹配
scrollView.contentSize仅通过view.frame.size.width累加,未与GameViewController的view实际尺寸绑定,导致scrollView的可滚动范围计算错误;同时GameViewController的view未通过约束或正确frame绑定到scrollView,布局混乱。
4. UITextView内容尺寸未正确计算
由于是直接向UITextView添加子控件(UILabel、UIButton)而非使用文本内容,UITextView无法自动计算contentSize,默认contentSize与textView自身尺寸一致,自然无法触发滚动。
解决建议
1. 统一UITextView布局方式
选择AutoLayout或手动frame其中一种方式,避免混用。推荐使用AutoLayout:
// 替换GameViewController中gameStatusTextview的frame设置代码 gameStatusTextview.translatesAutoresizingMaskIntoConstraints = false view.addSubview(gameStatusTextview) NSLayoutConstraint.activate([ gameStatusTextview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 5), gameStatusTextview.topAnchor.constraint(equalTo: screenLabel.bottomAnchor, constant: 50), gameStatusTextview.widthAnchor.constraint(equalToConstant: 380), gameStatusTextview.heightAnchor.constraint(equalToConstant: 412) ])
2. 修复Child ViewController的创建与布局
在ViewController的aCreateController中,复用同一个GameViewController实例,并通过约束绑定到scrollView:
func aCreateController() { let gameVC = GameViewController() gameVC.view.translatesAutoresizingMaskIntoConstraints = false add(child: gameVC, in: scrollView) // 约束gameVC的view尺寸与屏幕一致,位置在现有contentSize的右侧 NSLayoutConstraint.activate([ gameVC.view.topAnchor.constraint(equalTo: scrollView.topAnchor), gameVC.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), gameVC.view.widthAnchor.constraint(equalTo: view.widthAnchor), gameVC.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: scrollView.contentSize.width) ]) // 更新scrollView的可滚动宽度 scrollView.contentSize.width += view.bounds.width }
3. 手动计算并设置UITextView的ContentSize
在添加完所有子控件到gameStatusTextview后,遍历子控件计算最大Y值,手动设置contentSize:
// 在GameViewController的aRefreshView方法中,添加完所有label和button后 var maxContentY: CGFloat = 0 for subview in gameStatusTextview.subviews { if subview.frame.maxY > maxContentY { maxContentY = subview.frame.maxY } } // 增加底部间距避免内容被截断 gameStatusTextview.contentSize = CGSize(width: gameStatusTextview.bounds.width, height: maxContentY + 10)
4. 修复ViewController的aCreateView方法
原方法中view = UIView(frame: UIScreen.main.bounds)会替换Storyboard创建的原始view,导致原有控件丢失,应直接向原view添加scrollView:
func aCreateView() { view.addSubview(scrollView) NSLayoutConstraint.pin(view: scrollView, to: view) }
内容的提问来源于stack exchange,提问作者BobA

