@FocusState يک property wrapper در SwiftUI است که در iOS 15 معرفی شد و به شما امکان میدهد تا به صورت برنامهای فوکوس ورود را در فیلدهای متنی و سایر المانها مدیریت کنید. پیش از ظهور آن، توسعهدهندگان مجبور بودند از UIViewRepresentable برای دسترسی به روشهای UIKit مانند becomeFirstResponder و resignFirstResponder استفاده کنند. @FocusState این مشکل را به شکل ذاتی حل میکند: شما یک ویژگی را از طریق مودیفکاتور .focused() به فیلد متصل میکنید، پس از آن تنظیم و بازنشستن فوکوس با یک تعیین مقدار ساده انجام میشود. به استناد به Apple Developer Documentation — FocusState (2025)، @FocusState از دو حالت پشتیبانی میکند: Bool برای مدیریت ساده (فوکوس هست یا نیست) و enum برای چندین فیلد، که هر case مربوط به یک فیلد ورودی خاص است.
اصلی
@FocusState یک property wrapper است که وضعیت فوکوس را به یک فیلد ورودی یا المان focusable دیگر در SwiftUI متصل میکند. بر خلاف UIKit که مدیریت فوکوس از طریق روشهای becomeFirstResponder و resignFirstResponder انجام میشود، SwiftUI از رویکرد اعلامی استفاده میکند: شما وضعیت (@FocusState) را اعلان و آن را از طریق مودیفکاتور .focused() به عنصر متصل میکنید. تغییر وضعیت به طور خودکار فوکوس را تغییر میدهد.
پیش از ظهور @FocusState در iOS 15، توسعهدهندگان مجبور بودند پیچیدههای UIViewRepresentable در اطراف UITextField ایجاد کنند یا از کتابخانههای شخص ثالث استفاده کنند. @FocusState مستقیماً در SwiftUI یکپارچه شده است و با TextField، TextEditor، SecureField و SearchField کار میکند. این کد را تمیزتر میکند، تعداد پلهای UIKit را کاهش میدهد و آزمونپذیری را بهبود میبخشد.
به استناد به WWDC Session 10136 — What's new in SwiftUI (2024)، @FocusState از سیستم کلیدهای ترجیح SwiftUI برای انتقال اطلاعات فوکوس بین عناصر استفاده میکند. وقتی فیلدی فوکوس را دریافت میکند، SwiftUI به طور خودکار ویژگی @FocusState مرتبط را بهروز میکند که به شما امکان را میدهد به تغییرات فوکوس در کد واکنش نشان دهید.
سادهترین راه استفاده از @FocusState نوع Bool است. وقتی فیلد در فوکوس است، ویژگی برابر true است. وقتی فوکوس برداشته شود — false. شما میتوانید با تعیین true فوکوس را به زور تنظیم کنید یا با تعیین false آن را بازنشستید.
struct LoginForm: View {
@State var email = ""
@FocusState var isEmailFocused: Bool
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($isEmailFocused)
Button("نمایش صفحه کلید") {
isEmailFocused = true
}
Button("مخفی کردن صفحه کلید") {
isEmailFocused = false
}
}
}
}
در این مثال، isEmailFocused وقتی کاربر روی فیلد متنی ضربه میزند به طور خودکار true و وقتی صفحه کلید مخفی میشود false میشود. دکمهها به شما امکان مدیریت برنامهای فوکوس را میدهند — این برای صفحات کلید سفارشی، دکمههای «بعدی» و مواقعی که بعد ارسال فرم باید صفحه کلید را به زور مخفی کنید مفید است.
برای فرمهای با چندین فیلد، @FocusState از enum مطابق با پروتکل FocusStateValue (یا Hashable) پشتیبانی میکند. هر case enum مربوط به یک فیلد خاص است. این به شما امکان جابجایی فوکوس بین فیلدها را میدهد — مثلاً با فشردن دکمه «بعدی» در صفحه کلید به فیلد بعدی بروید.
struct RegistrationForm: View {
enum Field: Hashable {
case email
case password
case confirmPassword
}
@State var email = ""
@State var password = ""
@State var confirmPassword = ""
@FocusState var focusedField: Field?
var body: some View {
Form {
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
.onSubmit { focusedField = .password }
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
.onSubmit { focusedField = .confirmPassword }
SecureField("Confirm", text: $confirmPassword)
.focused($focusedField, equals: .confirmPassword)
.onSubmit { submitForm() }
}
}
}
به مودیفکاتور .onSubmit توجه کنید — وقتی کاربر روی صفحه کلید «Return» را فشار میدهد صدا زده میشود. در داخل .onSubmit ما focusedField را به فیلد بعدی تغییر میدهیم که به طور خودکار فوکوس را منتقل میکند. آخرین فیلد برای ارسال فرم، submitForm() را فراخوان میکند.
@FocusState راهی ساده برای مخفی کردن صفحه کلید فراهم میکند — کافیست ویژگی را بر روی nil (برای enum) یا false (برای Bool) قرار دهید. اما گاهی بیدون پیوند به یک فیلد خاص باید صفحه کلید را مخفی کنید — مثلاً وقتی روی مکان خالی ضربه میزنید. در این مورد چند رویکرد وجود دارد.
struct DismissKeyboardView: View {
@State var text = ""
@FocusState var isFocused: Bool
var body: some View {
TextField("Enter text", text: $text)
.focused($isFocused)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("تمام") {
isFocused = false
}
}
}
}
}
مودیفکاتور .toolbar با placement .keyboard یک دکمه بالای صفحه کلید اضافه میکند. این یک الگوی استاندارد UX در iOS برای مخفی کردن صفحه کلید است. رویکرد جایگزین — استفاده از .onTapGesture بر روی VStack اصلی برای بازنشستن فوکوس با ضربه روی پسزمینه.
@FocusState به خوبی با اعتبارسنجی فرم ترکیب میشود. الگوی تیپیکی: پس از فشردن دکمه «ارسال»، همه فیلدها را بررسی کرده و فوکوس را بر اولین فیلدی که خطا دارد قرار دهید. این تجربه کاربر را بهبود میبخشد — کاربر بلافاصله میبیند کدام فیلد باید اصلاح شود.
struct ValidatedForm: View {
enum Field: Hashable { case name; case phone }
@State var name = ""
@State var phone = ""
@FocusState var focusedField: Field?
@State var errors: [String] = []
var body: some View {
Form {
TextField("Name", text: $name)
.focused($focusedField, equals: .name)
TextField("Phone", text: $phone)
.focused($focusedField, equals: .phone)
Button("ارسال") { validateAndSubmit() }
}
}
func validateAndSubmit() {
if name.isEmpty {
focusedField = .name
return
}
if phone.isEmpty {
focusedField = .phone
return
}
// ارسال فرم
}
}
در این مثال، وقتی فیلد name خالی است، فوکوس به آن منتقل میشود و کاربر بلافاصله میبیند خطا کجاست. اگر name پر شده باشد، phone بررسی میشود. این رفتار طبیعی برای فرمهاست — کاربر فیلدها را از بالا به پایین پر میکند و اعتبارسنجی هم همین ترتیب را دنبال میکند.
رایجترین اشتباه — تلاش برای استفاده از @FocusState با یک نوع غیر مطابق با Hashable. @FocusState نیاز دارد که نوع ویژگی Hashable باشد (Bool و enumهای اختیاری قبلاً مطابق هستند). اگر سعی میکنید از ساختار سفارشی استفاده کنید، مطمئن شوید که Hashable را پیادهسازی میکند.
// ❌ اشتباه: دو @FocusState Bool به جای enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool
// ✅ صحیح: یک enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?
سوالات متداول
@FocusState از iOS 15، iPadOS 15، macOS 12، tvOS 15 و watchOS 8 قابل دسترس است. برای پروژههایی که iOS 14 و پایینتر را پشتیبانی میکنند، از UIViewRepresentable با UITextField و becomeFirstResponder یا کتابخانههای شخص ثالث با پیادهسازی سفارشی مدیریت فوکوس استفاده کنید.
بله، برای این کار در UIViewRepresentable سفارشی باید پشتیبانی از FocusState را از طریق پروتکل UIViewRepresentable پیاده کنید. نمایش سفارشی باید becomeFirstResponder و resignFirstResponder داشته باشد. SwiftUI به طور خودکار @FocusState را به این روشها متصل میکند اگر مودیفکاتور .focused() را مشخص کنید.
در List یا Form، سلولها ممکن است مجدداً استفاده شوند که این ارتباط @FocusState را با فیلد میشکند. راه حل: مودیفکاتور .id() را با یک شناسه منحصربهفرد برای هر TextField اضافه کنید. مثال: .id(fieldName). این سبب میشود SwiftUI برای هر فیلد یک نمونه View جداگانه ایجاد کند.
.onTapGesture را به کانتینر اصلی (VStack، ZStack) اضافه کرده و فوکوس را بازنشستید: focusedField = nil. اما .onTapGesture ممکن است ضربه روی دکمههای داخلی را بلوک کند — از کانتینر با .contentShape(Rectangle()) و .onTapGesture روی آن استفاده کنید یا از UIViewBackgroundView سفارشی.
@FocusState API مستقیمی برای انیمیشن صفحه کلید ارائه نمیدهد — این رفتار سیستمی iOS است. اما شما میتوانید با .onChange(of: focusedField) یا .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) به تغییرات فوکوس واکنش نشان دهید و انیمیشن سفارشی محتوا انجام دهید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید