🐦
SwiftUIでUITextFieldを使うときに、文字数に応じてサイズが変わる問題の対処法メモ
概要
SwiftUIでUITextFieldを使うとき、 .frame(width: N)
でサイズを指定しているのに文字数に応じてサイズが可変になってしまう問題の対処方法メモ
結論
- ❌ UITextFieldをそのままUIViewRepresentableでラップする
- ✅ 【iOS13+】 UITextFieldを生成 -> UIViewにaddSubView -> Autolayoutを設定 -> UIViewをラップする
- ✅ 【iOS16+】
sizeThatFits(_:)
を実装する
❌ Bad
import SwiftUI
struct BadUITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
✅ Good - iOS13+
- UITextFieldを生成
- 親となるUIViewを生成し、addSubview
- コード上でAuto Layoutを設定
- UIViewをラップする
struct Good_iOS13_UITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
let view = UIView()
view.addSubview(textField)
// AutoresizingMaskからAutoLayoutへの自動変換を無効化
textField.translatesAutoresizingMaskIntoConstraints = false
// 対象のViewの四辺にアンカーを貼る
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
textField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0),
textField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
textField.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0)
])
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
✅ Good - iOS16+
追記 2024/07/12
最低サポートOSが iOS16+ である場合、sizeThatFits(_:) を用いることでUIViewをラップせずにすみます。
anz さん、情報提供ありがとうございます 🥰
https://zenn.dev/link/comments/fc0f465f4464e
struct Good_iOS16_UITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) { }
// iOS16未満では利用不可能
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIViewType, context: Context) -> CGSize? {
guard let width = proposal.width, let height = proposal.height else {
return nil
}
return .init(width: width, height: height)
}
}
再現コード
再現コード (フル)
import SwiftUI
struct BadUITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
struct Good_iOS16_UITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) { }
@available(iOS 16.0, *)
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIViewType, context: Context) -> CGSize? {
guard let width = proposal.width, let height = proposal.height else {
return nil
}
return .init(width: width, height: height)
}
}
struct Good_iOS13_UITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
let view = UIView()
view.addSubview(textField)
// AutoresizingMaskからAutoLayoutへの自動変換を無効化
textField.translatesAutoresizingMaskIntoConstraints = false
// 対象のViewの四辺にアンカーを貼る
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
textField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0),
textField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
textField.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0)
])
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
#Preview {
VStack {
Spacer()
VStack {
Text("可変になってしまう 🤪")
BadUITextFieldWrapperView()
.frame(width: 100, height: 40)
}
VStack {
Text("iOS13+ 固定 👍")
Good_iOS13_UITextFieldWrapperView()
.frame(width: 100, height: 40)
}
VStack {
Text("iOS16+ 固定 👍")
Good_iOS16_UITextFieldWrapperView()
.frame(width: 100, height: 40)
}
Spacer()
}
.padding(16)
}
備考①
UITextFieldを直接Wrapする方法で解決できる方法があれば教えて頂けると嬉しいです 🙇♂️
追記 2024/07/12
iOS16+ で利用可能な sizeThatFits(_:) を用いることで単体でもサイズを固定できるようです。
anzさん、教えて頂きありがとうございます! 🥰
備考②
以下のようなextensionを用いることでAutoLayoutを簡単に設定でき、コードが見やすくなります。
extension UIView {
func anchorAll(equalTo: UIView) {
// AutoresizingMaskからAutoLayoutへの自動変換を無効化
translatesAutoresizingMaskIntoConstraints = false
// 対象のViewの四辺にアンカーを貼る
topAnchor.constraint(equalTo: equalTo.topAnchor, constant: 0).isActive = true
leftAnchor.constraint(equalTo: equalTo.leftAnchor, constant: 0).isActive = true
bottomAnchor.constraint(equalTo: equalTo.bottomAnchor, constant: 0).isActive = true
rightAnchor.constraint(equalTo: equalTo.rightAnchor, constant: 0).isActive = true
}
}
struct GoodUITextFieldWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.borderStyle = .line
textField.clearButtonMode = .always
let view = UIView()
view.addSubview(textField)
- textField.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- textField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
- textField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0),
- textField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
- textField.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0)
- ])
+ textField.anchorAll(equalTo: view)
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
宣伝
MENTAはじめました! iOSアプリ開発でお困りの方はぜひご相談ください 👋
XにてiOSアプリ開発周りの情報を発信しています!
個人開発したアプリが10万DL突破しました!
Discussion
気になったので調べてみたら iOS 16+ でよければ
sizeThatFits(_:uiView:context:)
を使えばできそうでしたー。雑に書くとこんな感じです。
まぁ、SwiftUI だけで完結できるのがベストではあるのですけど、、ね 😇
情報提供ありがとうございます!!!追記しました!!
sizeThatFits
くんのことをすっかり忘れていました!!TextField 2.0 のリリースが楽しみですね! 😇