@FocusState هو property wrapper في SwiftUI، تم تقديمه في iOS 15، يتيح لك التحكم برمجياً في تركيز الإدخال على حقول النص والعناصر الأخرى. قبل ظهوره، كان على المطورين استخدام UIViewRepresentable للوصول إلى طرق UIKit becomeFirstResponder و resignFirstResponder. يحل @FocusState هذه المشكلة بطريقة أصلية: تربط خاصية بحقل عبر المُعدِل .focused()، وبعد ذلك يتم تعيين التركيز أو إلغاؤه بمجرد تعيين قيمة. وفقاً لـ Apple Developer Documentation — FocusState (2025)، يدعم @FocusState وضعين: Bool للإدارة البسيطة (التركيز مفعل أو لا) وenum لحقول متعددة، حيث تتوافق كل حالة مع حقل إدخال محدد.
الوجبات الرئيسية
@FocusState هو property wrapper يربط حالة التركيز بحقل إدخال محدد أو عنصر آخر قابل للتركيز في 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 نظام preference keys في 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). كل حالة من 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 و enums الاختيارية متوافقة بالفعل). إذا كنت تحاول استخدام بنية مخصصة، تأكد من أنها تطبق 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، أو مكتبات خارجية مع تنفيذ مخصص لإدارة التركيز.
نعم، تحتاج إلى تنفيذ دعم FocusState في UIViewRepresentable المخصص من خلال بروتوكول UIViewRepresentable. يجب أن يحتوي العرض المخصص على becomeFirstResponder و resignFirstResponder. سيقوم SwiftUI بربط @FocusState بهذه الطرق تلقائياً إذا حددت المُعدِل .focused().
في List أو Form، يمكن إعادة استخدام الخلايا، مما يكسر ربط @FocusState بالحقل. الحل: أضف المُعدِل .id() مع معرف فريد لكل TextField. على سبيل المثال: .id(fieldName). هذا يجبر SwiftUI على إنشاء مثيل View منفصل لكل حقل.
أضف .onTapGesture إلى الحاوية الجذرية (VStack, ZStack) وأعد تعيين التركيز: focusedField = nil. ومع ذلك، قد يحجب .onTapGesture النقرات على الأزرار الداخلية — استخدم حاوية مع .contentShape(Rectangle()) و .onTapGesture عليها، أو UIKitBackgroundView مخصص.
@FocusState لا يوفر API مباشر لتحريك لوحة المفاتيح — هذا سلوك نظام iOS. ومع ذلك، يمكنك التفاعل مع تغييرات التركيز باستخدام .onChange(of: focusedField) أو .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) لتحريك المحتوى المخصص.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا