كيف تجعل النص في تطبيقك يدعم اتجاه اللغة بدلاً من اتجاه التطبيق
بمعنى جعله .natural اذا محتوى النص كان عربي يكون اتجاه النص RTL واذا كان محتوى النص انجليزي يكون اتجاه النص LTR تعرف على الطريقة في هذا المقال

اذا لاحظت فأن جميع الـ alignment في SwiftUI تعتمد على ثلاثة اتجاهات leading , trailing , center
عكس في UIKit يوجد left , right , center وهنا المشكله !
لتجربة الواجهة الحالية في حال كانت باللغة العربيه RTL او باللغة الانجليزية LTR


struct ContentView: View {
var body: some View {
ScrollView {
VStack {
Text("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")
.padding()
Text("هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة، لقد تم توليد هذا النص من مولد النص العربى، حيث يمكنك أن تولد مثل هذا النص أو العديد من النصوص الأخرى إضافة إلى زيادة عدد الحروف التى يولدها التطبيق.إذا كنت تحتاج إلى عدد أكبر من الفقرات يتيح لك مولد النص العربى زيادة عدد الفقرات كما تريد، النص لن يبدو مقسما ولا يحوي أخطاء لغوية، مولد النص العربى مفيد لمصممي المواقع على وجه الخصوص، حيث يحتاج العميل فى كثير من الأحيان أن يطلع على صورة حقيقية لتصميم الموقع.ومن هنا وجب على المصمم أن يضع نصوصا مؤقتة على التصميم ليظهر للعميل الشكل كاملاً،دور مولد النص العربى أن يوفر على المصمم عناء البحث عن نص بديل لا علاقة له بالموضوع الذى يتحدث عنه التصميم فيظهر بشكل لا يليق. هذا النص يمكن أن يتم تركيبه على أي تصميم دون مشكلة فلن يبدو وكأنه نص منسوخ، غير منظم، غير منسق، أو حتى غير مفهوم. لأنه مازال نصاً بديلاً ومؤقتاً.")
.padding()
Text("Try one line of text")
.padding()
Text("تجربة سطر واحد من النص")
.padding()
Spacer()
}
}
.environment(\.locale, .init(identifier: "ar"))
.environment(\.layoutDirection, .rightToLeft)
}
}
الكود السابق، راح تلاحظ وجود
.environment(\.locale, .init(identifier: "ar"))
.environment(\.layoutDirection, .rightToLeft)
هذه فقط لجعل الواجهه تستخدم ملف الترجمة العربية واجبارها تكون RTL بدون الحاجة اني اغير لغة الجهاز (من اجل التجربة فقط)
اذا لاحظت النص لما يكون سطر واحد يكون في المنتصف في SwiftUI هذا هو الافتراضي
كثير من المطورين يغلطوا ويضيفوا الـText داخل HStack ويستخدموا Spacer() لكن هذه الطريقة خاطئه
الطريقة الصحيحة لجعلها تعتمد على لغة التطبيق (الجهاز) هي بهذا الشكل
Text("Try one line of text")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Text("تجربة سطر واحد من النص")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
من الفقرات السابقة اتوقع عرفت المشكلة بسبب اعتماد SwiftUI على
leading , trailing تعتمد على اتجاه لغة النظام
بما يعني اذا لغة الجهاز انجليزية LTR
leading تعني يسار و trailing تعني يمين
والعكس اذا لغة الجهاز عربية RTL
leading تعني يمين و trailing تعني تعني يسار
على ايام SwiftUI 2 و iOS 14 كنت اعتمد على UILabel بمعنى استخدم العنصر من UIKit و واستخدم في SwiftUI
والسبب لانه استطيع احل المشكله السابقه به وايضا استطيع استخدم NSAttributedString التي تعطيني امكانية اتحكم في النص بتغيير لون نص معين ، اجعل جزء من النص Bold او غيره
Apple في iOS 15 ولغة Swift 5.5 اعادة بناء الـ NSAttributedString بما يعني صار 100% بلغة Swift
وبالتالي جعلت SwiftUI يعتمد عليه 100% دون الحاجة الى الاعتماد على UIKit
بس زي ما يقولوا الحلو ما يكمل =) الـ paragraphStyle الي من خلاله تقدر تختار .natrual وتخلي النص يعتمد على لغته في تحديد اتجاه غير مدعوم في SwiftUI
مع ذلك شفت انه مع دعم SwiftUI لـ AttributedString وايضا ميزة Markdown مافي حاجة اني اعتمد على UILabel فلابد من وجود طريقة اخرى لدعم .natural بحيث يكون 100% SwiftUI
بعد البحث والتجربة والخطأ وجدت طريقة فعاله وصحيحه 100%
كما ذكرت سابقا بأنه
اذا لغة الجهاز انجليزية LTR
leading تعني يسار و trailing تعني يمين
والعكس اذا لغة الجهاز عربية RTL
leading تعني يمين trailing وتعني تعني يسار
اذا اقدر اعرف اتجاه التطبيق (لغة التطبيق)
وايضا اتجاه النص فاقدر بكل سهولة أن اجعل النص natural
المشكلة الاولى سهله حلها بالاعتماد على layoutDirection
باستخدامه راح اعرف اتجاه التطبيق (لغة التطبيق)
اما تجاه الواجهة يمين الى يسار RTL او يسار الى يمين LTR
@Environment(\.layoutDirection) private var layoutDirection
لكن كيف اعرف لغة النص ؟
ايضا بسيطه بالاعتماد على Framework NaturalLanguage ابل اطلقته في iOS 12
الـ NaturalLanguage يحتوي على كثير من المميزات ولكننا هنا راح نعتمد على ميزة وحده وهي معرفة اتجاه النص
دمج الاثنين مع بعض راح نجعل النص natural بحيث اتجاه النص يعتمد فقط على اتجاهه دون الاهتمام بإتجاه التطبيق
لذا انشاء ملف جديدة نوعه SwiftUI وبإسم NaturalText
في البداية فقط اضيف متغير text مافي حاجة لجعله State لانه قيمته لن تتغير
واضيف هذه السطرين
ايش الفائدة من استخدامهم ؟ السطر الاول راح يستخدم في حال كان النص عباره عن سطر واحد الثاني راح يستخدم اذا كان النص مكون من عدة اسطر
struct NaturalTextView: View {
@Environment(\.layoutDirection) private var layoutDirection
var text : String
var body: some View {
Text(text)
.frame(maxWidth: .infinity, alignment: .trailing)
.multilineTextAlignment(naturalTextAlignment)
}
}
حاليا النص دائما راح يكون حسب اتجاه التطبيق (لغة التطبيق)
لذا في البداية سوف نضيف
import NaturalLanguage
وايضا layoutDirection
import SwiftUI
import NaturalLanguage
struct NaturalTextView: View {
@Environment(\.layoutDirection) private var layoutDirection
}
بعدها راح نضيف هذا الكود
هذا الكود يعتمد على NaturalLanguage ولاحظ مررت له النص text
private var dominantLanguage: String? {
let firstChar = "\(text.first ?? " ")"
if #available(iOS 12, *) {
return NLLanguageRecognizer.dominantLanguage(for: firstChar)?.rawValue
} else {
return NSLinguisticTagger.dominantLanguage(for: firstChar)
}
}
في هذه الخطوه راح نسوي متغير جديد بإسم naturalAlignment ونوعه Alignment
لاحظ في البداية استخدمنا المتغير السابق dominantLanguage في حال ما تعرف على اللغة بمعنى كانت رموز او ارقام مثلا راح نخلي الاتجاه حسب لغة التطبيق leading
private var naturalAlignment: Alignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
بعدها استخدمنا
NSParagraphStyle.defaultWritingDirection
بالاعتماد على اللغة راح يعرف اتجاه النص مثلا كانت قيمة dominantLanguage
“ar” بما يعني لغة عربية بالاعتماد على NSParagraphStyle.defaultWritingDirection
راح يرجع .rightToLeft
الجزء الاخير من الكود السابق قد يكون محير ولكن ركز الـ case هو اتجاه النص
وبداخل اعتمدت على layoutDirection مثلا اذا كان اتجاه النص leftToRight بما يعني نص انجليزي في حال كان لغة الجهاز عربي راح يكون اليسار trailing لكن اذا لغة الجهاز لغة انجليزي راح يكون leading
نفس الامر في حال كان لغة الجهاز عربي والنص عربي راح يكون اتجاه النص يمين leading واذا النص انجليزي راح يكون الاتجاه يسار trailing
الان راح نحتاج نكرر نفس الكود ولكن في متغير اخر باسم naturalTextAlignment ونوعه TextAlignment
الفرق بين هذا والسابق ، هذا naturalTextAlignment راح يكون للنص الي يحتوي على عدة اسطر
لكن naturalAlignment للنص الي يكون عباره عن سطر واحد فقط
private var naturalTextAlignment: TextAlignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
الخطوه الاخيره نحتاج نستخدم المتغيرين
Text(text)
.frame(maxWidth: .infinity, alignment: naturalAlignment)
.multilineTextAlignment(naturalTextAlignment)
الكود النهائي
////
//NaturalText.swift
//NaturalText
//
//Created by Basel Baragabah on 18/02/2022.
//Copyright © 2022 Basel Baragabah. All rights reserved.
//
import SwiftUI
import NaturalLanguage
struct NaturalText: View {
@Environment(\.layoutDirection) private var layoutDirection
var text : String
var body: some View {
Text(text)
.frame(maxWidth: .infinity, alignment: naturalAlignment)
.multilineTextAlignment(naturalTextAlignment)
}
private var naturalAlignment: Alignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
private var naturalTextAlignment: TextAlignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
private var dominantLanguage: String? {
let firstChar = "\(text.first ?? " ")"
return NLLanguageRecognizer.dominantLanguage(for: firstChar)?.rawValue
}
}
الان نرجع لـ ContentView ونبدل الـ Text بـ NaturalText
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
NaturalText(text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")
.padding()
NaturalText(text: "هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة، لقد تم توليد هذا النص من مولد النص العربى، حيث يمكنك أن تولد مثل هذا النص أو العديد من النصوص الأخرى إضافة إلى زيادة عدد الحروف التى يولدها التطبيق.إذا كنت تحتاج إلى عدد أكبر من الفقرات يتيح لك مولد النص العربى زيادة عدد الفقرات كما تريد، النص لن يبدو مقسما ولا يحوي أخطاء لغوية، مولد النص العربى مفيد لمصممي المواقع على وجه الخصوص، حيث يحتاج العميل فى كثير من الأحيان أن يطلع على صورة حقيقية لتصميم الموقع.ومن هنا وجب على المصمم أن يضع نصوصا مؤقتة على التصميم ليظهر للعميل الشكل كاملاً،دور مولد النص العربى أن يوفر على المصمم عناء البحث عن نص بديل لا علاقة له بالموضوع الذى يتحدث عنه التصميم فيظهر بشكل لا يليق. هذا النص يمكن أن يتم تركيبه على أي تصميم دون مشكلة فلن يبدو وكأنه نص منسوخ، غير منظم، غير منسق، أو حتى غير مفهوم. لأنه مازال نصاً بديلاً ومؤقتاً.")
.padding()
NaturalText(text: "Try one line of text")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
NaturalText(text: "تجربة سطر واحد من النص")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Spacer()
}
}
}
}
هل انتيهنا ؟ لا
هناك امراً اخراً الـ EditorText في حال تطبيقك يدعم الكتابة
مثلاً تطبيق يوميات او تطبيق To Do list بمعنى تطبيق يتطلب من المستخدم كتابة نصوص
راح تواجهه نفس المشكلة انه اتجاه نص الكتابة يعتمد على لغة التطبيق ولا لغة النص !
مثال للمشكلة
تقدر تعمل ملف جديد باسم NaturalTextEditor
وتكرر نفس الكود فقط تحتاج تغير Text الى TextEditor
struct NaturalTextEditor: View {
@Environment(\.layoutDirection) private var layoutDirection
@Binding var text: String
var body: some View {
TextEditor(text: $text)
.frame(alignment: naturalAlignment)
.multilineTextAlignment(naturalTextAlignment)
}
private var naturalAlignment: Alignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
private var naturalTextAlignment: TextAlignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
private var dominantLanguage: String? {
let firstChar = "\(text.first ?? " ")"
return NLLanguageRecognizer.dominantLanguage(for: firstChar)?.rawValue
}
}
الان تستطيع استخدامه بهذا الشكل
اولا تعرف متغير من نوع State
@State private var text = ""
وتستخدمه بهذا الشكل
NaturalTextEditor(text: $text)
النتيجة
يتضح في هذا المقال ، اوقات كثيره تحتاج فقط تفكر خارج الصندوق لتجد الحل المناسب =)
جميع مقالاتي من تجاربي وخبرتي الخاصة ، بمعنى لن تجدها في اي موقع او كتاب اخر =)
تستطيع تحميل الكود كامل من صفحتي في Gits
