لإبقاء أعمدة التسميات والحالة في نافذة Mac ثابتة بينما يتمدد الوصف، امنح الأعمدة الطرفية عروضًا صريحة، واجعل إطار الوصف مرنًا. استخدم أولوية التخطيط عندما تتنافس العناصر الشقيقة، وحافظ على الحجم المثالي للمحاور التي تحتاج إليه فقط.
يبني هذا البرنامج التعليمي نافذة صغيرة قابلة لتغيير الحجم، وتحتوي على تسميات وعناصر وصف اصطناعية. تتمثل المهمة في إبقاء الأعمدة مصطفة مع السماح للأوصاف الأطول بالالتفاف عندما تضيق النافذة. ينتمي كل شيء إلى التطبيق التوضيحي؛ ويستخدم التخطيط عروض SwiftUI المعتادة.
ما عنصر التحكم المناسب لحجم كل عرض فرعي؟
يبدأ التخطيط في SwiftUI باقتراح عنصر أب حجمًا. يستجيب كل عنصر فرعي بحجمه المفضل، ثم يرتب الحاوي النتائج. ومن المفيد أن تبدأ بتحديد الأبعاد التي يمكن لكل عنصر فرعي تغييرها. تشرح Apple هذه العملية القائمة على الاقتراح والاستجابة في دليل التخطيط في SwiftUI.
لهذه النافذة، حدّد ثلاثة أدوار: عمودًا للتسمية بعرض مختار، وعمودًا للوصف يستوعب تغيرات العرض، وعمودًا للحالة بعرض مختار. ميّز هذا العرض الثابت المحدد رقميًا عن الحجم المثالي للعرض؛ فهما يعبران عن متطلبات مختلفة.
| المتطلب | عنصر التحكم | النتيجة العملية |
|---|---|---|
| حجز عرض محدد لعمود | .frame(width: 96) |
يكون للإطار الناتج ذلك العرض. |
| السماح للوصف باستيعاب العرض المتاح | .frame(minWidth: 160, maxWidth: .infinity, alignment: .leading) |
يقبل الإطار العرض المقترح ضمن الحد الأدنى له. |
| الحفاظ على الحجم المثالي للمحتوى على محاور محددة | .fixedSize(horizontal: false, vertical: true) |
يستجيب العرض للتخطيط، بينما يحافظ الارتفاع على حجمه المثالي. |
| تفضيل عنصر شقيق عند توزيع المساحة | .layoutPriority(1) |
يمنح العنصر الأب ذلك العنصر أولوية على العناصر الشقيقة الأقل أولوية. |
توثّق Apple الأبعاد الرقمية في مُعدِّل الإطار الثابت، والقيود الدنيا والعليا في مُعدِّل الإطار المرن، وتحديد الحجم المثالي بحسب المحور في fixedSize(horizontal:vertical:)، وتفضيل عنصر شقيق في layoutPriority(_:).
الحد الأقصى غير المحدود يعني السماح بالتمدد ضمن الاقتراح؛ ولا يعني طلب نافذة بعرض لا نهائي. وعند تقديم قيود دنيا وعليا معًا، يتبنى الإطار البُعد المقترح مع حصره ضمن تلك القيود. وتحدد قيمة المحاذاة موضع العنصر الفرعي داخل ذلك الإطار. تهم هذه التفاصيل عندما يكون النص نفسه أضيق من المساحة المخصصة لعموده. توثيق Apple للإطار المرن
أنشئ مثال macOS كاملًا قابلًا لتغيير الحجم
أنشئ مشروع تطبيق macOS في Xcode باستخدام SwiftUI، واجعل هدف النشر macOS 13 أو أحدث. استبدل تعريفَي التطبيق وعرض المحتوى اللذين أنشأهما Xcode بهذا الملف الواحد، مع الإبقاء على نقطة دخول واحدة فقط للتطبيق. يستخدم المثال عنصر التحكم في إمكانية تغيير حجم النافذة في macOS 13. توثيق Apple لـ WindowResizability
فيما يلي مثال كامل للتطبيق. يتضمن السلاسل النصية التجريبية ومفتاح تبديل للتنقل بين الأوصاف القصيرة والطويلة. تعذّر تجميع تطبيق SwiftUI في بيئة البحث لعدم توفر سلسلة أدوات Swift؛ لذا افحص سلوكه في Xcode قبل دمجه في مشروع قائم.
import SwiftUI
@main
struct ColumnAllocationApp: App {
var body: some Scene {
WindowGroup {
AllocationView()
.frame(minWidth: 420, minHeight: 260)
}
.windowResizability(.contentMinSize)
}
}
struct AllocationView: View {
@State private var showLongDescriptions = true
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Item overview")
.font(.title2)
Toggle(
"Show longer descriptions",
isOn: $showLongDescriptions
)
ScrollView {
VStack(alignment: .leading, spacing: 16) {
AllocationRow(
label: "Item A",
summary: showLongDescriptions
? "A blue folder with several sample notes and a longer descriptive label."
: "Blue folder",
status: "Ready"
)
AllocationRow(
label: "Item B",
summary: showLongDescriptions
? "A green notebook containing a short list of synthetic example entries."
: "Green notebook",
status: "Draft"
)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(16)
}
}
struct AllocationRow: View {
let label: String
let summary: String
let status: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
Text(label)
.frame(width: 96, alignment: .leading)
Text(summary)
.fixedSize(horizontal: false, vertical: true)
.frame(
minWidth: 160,
maxWidth: .infinity,
alignment: .leading
)
Text(status)
.fixedSize(horizontal: true, vertical: false)
.frame(width: 72, alignment: .trailing)
}
}
}
الوصف هو العمود الوحيد المرن أفقيًا. يُبقي استدعاء fixedSize تحديد الحجم الأفقي مستجيبًا، مع الحفاظ على المساحة الرأسية اللازمة للنص الملتف. ثم يملأ إطاره المرن العمود المخصص له. وتحافظ الحالة على عرض نصها المثالي داخل إطار منفصل بعرض 72 نقطة. وتحذر Apple من أن المحتوى ذي الحجم المثالي قد يمتد خارج حدود العنصر الأب، لذا يفترض هذا الأسلوب للحالة استخدام القيم القصيرة المقدمة، Ready وDraft. توثيق Apple لـ fixedSize
لا ارتفاع ثابتًا للصفوف. وهذا مقصود: فقد تحتاج الأوصاف الأضيق إلى أسطر أكثر. ويوفر عرض التمرير وسيلة للوصول إلى الصفوف عندما يتجاوز ارتفاعها الإجمالي المساحة المتاحة. تجنب إضافة ارتفاع صف مقيّد لمجرد جعل الترتيب الأولي يبدو موحدًا.
تستخدم النافذة .contentMinSize، الذي يشتق أبعادها الدنيا من المحتوى ولا يفرض حجمًا أقصى. هنا، يحدد الحد الأدنى البالغ 420 نقطة لجذر العرض حدًا أدنى عمليًا للمثال. وينطبق عنصر التحكم هذا على النوافذ التي ينشئها مشهد التطبيق. توثيق Apple لـ contentMinSize
تستخدم التعريفات التي تعيد some View أنواع نتائج Swift المعتمة: تظل أنواعها الفعلية مخفية، بينما يبقى توافقها مع View متاحًا. ويمكنك تعديل سلاسل المُعدِّلات من دون كتابة أنواع الإرجاع المتداخلة صراحةً. لغة Swift البرمجية: أنواع البروتوكولات المعتمة والمغلّفة
احسب ميزانية العرض قبل تغيير الأولويات
توضح ميزانية العرض حدود التخطيط. في هذا المثال، اطرح الحشو الأفقي والفجوتين والعمودين الثابتين من عرض محتوى النافذة. والباقي مخصص للوصف. هذه أبعاد تصميم توضيحية، وليست قياسات لعناصر التحكم الأصلية.
عرض الوصف
= عرض المحتوى - الحشو الأفقي - الفجوات - الأعمدة الثابتة
= عرض المحتوى - (2 × 16) - (2 × 12) - 96 - 72
= عرض المحتوى - 224
عند 640 نقطة: يتبقى 416 نقطة للوصف.
عند 420 نقطة: يتبقى 196 نقطة للوصف.
الحد الأدنى لميزانية الصف:
32 + 24 + 96 + 160 + 72 = 384 نقطة.
تم التحقق من الحساب بعملية حسابية محلية حتمية. وهو يصف هذا الصف عندما يتلقى عرض المحتوى مطروحًا منه الحشو المحدد؛ ولا يشمل زخارف النافذة. يترك الحد الأدنى المختار للنافذة، البالغ 420 نقطة، مساحة تتجاوز الحد الأدنى المحسوب للصف والبالغ 384 نقطة. إذا أضافت حاوية محيطة هوامش داخلية، فأدرجها أيضًا في الميزانية.
يكشف هذا الحساب أيضًا عن طلب مستحيل. فدون الحد الأدنى الإجمالي للميزانية، لا يمكن للأعمدة الثلاثة كلها الاحتفاظ بعروضها وفجواتها في الوقت نفسه. اختر حدًا أدنى أكبر لعرض النافذة، أو أبعادًا دنيا أصغر، أو ترتيبًا مختلفًا. لا يمكن لزيادة الأولوية أن تخلق مساحة إضافية.
متى تفيد أولوية التخطيط؟
تفيد الأولوية عندما يمكن لعنصرين شقيقين الانكماش، وتريد أن يحتفظ أحدهما بمساحة أكبر. فمثلًا، قد يستحق عنوان وصفي أولوية على تعليق ثانوي. أضف .layoutPriority(1) إلى العنصر الفرعي الذي يشارك في قرار التوزيع لدى ذلك العنصر الأب. وتوضح Apple أن العروض ذات الأولوية الأعلى تنكمش لاحقًا وتتمدد مبكرًا، مع حجز مساحة للحد الأدنى من متطلبات العناصر الفرعية الأقل أولوية. توثيق Apple لـ layoutPriority
لا يحتاج المثال الكامل إلى مُعدِّل أولوية، إذ إن سياسة التوزيع فيه معبّر عنها بالفعل بعمودين ثابتين وعمود واحد مرن. كما أن قيمة الأولوية ليست وزنًا تناسبيًا. فتعيين الأولويتين 2 و1 لا ينشئ نسبة عرض اثنين إلى واحد؛ والغرض الموثق هو تحديد التفضيل أثناء التوزيع. توثيق Apple لـ layoutPriority
تجنب إضافة Spacer أفقي بين الوصف والحالة، إلا إذا كنت تريد فجوة مرنة مستقلة. يتمدد عنصر المسافة على المحور الرئيسي للمكدس، ويدفع العناصر الشقيقة بعضها عن بعض. وبما أن إطار الوصف يستوعب تغيرات العرض بالفعل، فلا حاجة إلى هذا العنصر الإضافي هنا. توثيق Apple لـ Spacer
ماذا ينبغي أن يحدث عندما لا يعود النص ملائمًا؟
قرر بشكل منفصل ما إذا كان ينبغي لكل حقل نصي أن يلتف أو يُقتطع أو يتطلب ترتيبًا مختلفًا. يمكن لأوصاف المثال أن تلتف من دون حد للأسطر. وتطبيق .lineLimit(1) يحدد كل عنصر نصي متأثر بسطر واحد، ما يغيّر المتطلب. أما حد الأسطر nil فيزيل هذا القيد، لكنه لا يزيل القيود التي تفرضها الأطر المحيطة. توثيق Apple لـ lineLimit
بالنسبة إلى قيم الحالة الأطول، أعد النظر في عمود الـ 72 نقطة. أزل الحفاظ على الحجم المثالي أفقيًا إذا كان الالتفاف مقبولًا، أو زد عرض العمود، أو انقل الحالة إلى أسفل الوصف. قد يسمح الإبقاء على .fixedSize(horizontal: true, vertical: false) مع تضييق الإطار المحيط للنص بالامتداد خارج ذلك الإطار. توثيق Apple لـ fixedSize
إذا كان ينبغي لنافذة صغيرة أن تعيد ترتيب المحتوى، فإن macOS 13 والإصدارات الأحدث توفر ViewThatFits. قدّم ترتيبًا أفقيًا أولًا، وترتيبًا مكدسًا ثانيًا. ويختار هذا العنصر أول عنصر فرعي يناسب حجمه المثالي المحاور المقيّدة؛ لذا قيّم المساحة الأفقية باستخدام ViewThatFits(in: .horizontal) عندما يكون العرض هو العامل الحاسم. واجعل الخيار الأخير قابلًا للاستخدام عند الحد الأدنى للعرض الذي تدعمه. توثيق Apple لـ ViewThatFits
تعامل مع ذلك على أنه سياسة تخطيط بديلة. فالمثال الحالي يُبقي الأعمدة عمدًا ويحدد حدًا أدنى لعرض النافذة. استخدم ترتيبًا مكدسًا احتياطيًا عندما تكون المحافظة على الترتيب الأفقي أقل أهمية من السماح بنافذة أضيق.
كيف تغيّر عروض AppKit وTextKit المضمنة هذا الأسلوب؟
إذا أصبح الوصف لاحقًا مكوّن نصوص من AppKit، فاستخدم NSViewRepresentable لدمج العرض. وتذكر Apple صراحةً أن SwiftUI يتحكم في frame وbounds للعرض المُمثّل؛ لذا فإن تغيير هاتين الخاصيتين مباشرةً من شفرتك يتعارض مع تخطيط SwiftUI. هيّئ المحتوى عبر دورة حياة العنصر المُمثّل، وعبّر عن الحجم الخارجي باستخدام SwiftUI. توثيق Apple لـ NSViewRepresentable
في macOS 13 والإصدارات الأحدث، يمكن للطريقة sizeThatFits(_:nsView:context:) في العنصر المُمثّل أن تعيد الحجم المفضل وفق اقتراح معين. وقد تُستدعى عدة مرات باقتراحات مختلفة ضمن تمريرة تخطيط واحدة. وإعادة nil تطلب خوارزمية تحديد الحجم الافتراضية. ينبغي أن يراعي قياس مكوّن يلتف نصه العرض المقترح، بدلًا من إعادة عرض النص غير الملتف دائمًا. توثيق Apple لتحديد حجم العناصر المُمثّلة
ميّز الأبعاد غير المحددة عن الصفر: تستخدم SwiftUI اقتراحًا غير محدد لطلب الحجم المثالي، بينما يمكن للصفر اختبار الحد الأدنى للحجم. وينبغي لروتين القياس المخصص أن يتعامل مع هذه الحالات بعناية. توثيق Apple لـ ProposedViewSize
داخل تخطيط AppKit، تتحكم مقاومة الانضغاط في مدى مقاومة NSView لأن يصبح أصغر من حجمه الذاتي. افحص هذا الإعداد عندما يقاوم مكوّن ملتف الانضغاط، إلى جانب سلوكه في تحديد الحجم ضمن SwiftUI. توثيق Apple لـ setContentCompressionResistancePriority
بالنسبة إلى NSTextView الذي يستخدم TextKit 2، استخدم المسار الحديث textLayoutManager. ويميّز عرض TextKit العام الحالي من Apple بينه وبين layoutManager القديم، كما يوضح دليل الترحيل أن الوصول إلى الخاصية القديمة قد يؤدي إلى تبديل توافق أحادي الاتجاه إلى TextKit 1 لهذه النسخة. لا حاجة إلى تغيير محركات النص لتوزيع أعمدة العناصر الشقيقة. نظرة عامة على TextKit من Apple، ما الجديد في TextKit وعروض النص
افحص النتيجة عند حدود النافذة
شغّل المثال وافحص الحالات التالية قبل تطبيق النمط على واجهة أكبر:
- وسّع النافذة: ينبغي أن يتمدد عمود الوصف، بينما يحتفظ إطارا التسمية والحالة بعرضيهما.
- ضيّقها إلى الحد الأدنى المدعوم: ينبغي أن تلتف الأوصاف الأطول ويزداد ارتفاع الصف.
- أوقف خيار
Show longer descriptionsثم شغّله: ينبغي أن تظل الأوصاف القصيرة مصطفة مع الأعمدة نفسها. - استبدل الحالة الاصطناعية بأخرى أطول: قرر ما إذا كنت ستوسّع هذا الحقل أو تجعله يلتف أو تعيد ترتيبه.
- قلّل الارتفاع المتاح: تأكد من إمكانية الوصول إلى جميع الصفوف.
لمعرفة أي طبقة تتلقى المساحة، أضف مؤقتًا حدودًا مباشرة قبل إطار الوصف المرن وبعده. يكشف الحد الداخلي نطاق النص، بينما يكشف الحد الخارجي العمود المخصص له. وتوصي Apple باستخدام حدود ملونة كوسيلة عملية لتشخيص تحديد حجم المكدسات والحشو. دليل التخطيط في SwiftUI
أبقِ سياسة تحديد الحجم واضحة مع نمو الواجهة: احجز عروضًا حيثما يتطلب الاصطفاف ذلك، وخصّص منطقة مرنة محددة للمحتوى المتغير، وقرر سلوك كل حقل عند الحد الأدنى للعرض. بهذه السياسة وميزانية العرض، يمكنك تغيير التسميات أو إضافة محتوى من دون تخمين المُعدِّل الذي ينبغي أن يحصل على أولوية أعلى.
المصادر
- دليل التخطيط في SwiftUI — Apple
- frame(width:height:alignment:) — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- layoutPriority(_:) — Apple
- WindowResizability — Apple
- contentMinSize — Apple
- أنواع البروتوكولات المعتمة والمغلّفة — Swift.org
- Spacer — Apple
- lineLimit(_:) — Apple
- ViewThatFits — Apple
- NSViewRepresentable — Apple
- sizeThatFits(_:nsView:context:) — Apple
- ProposedViewSize — Apple
- setContentCompressionResistancePriority(_:for:) — Apple
- TextKit — Apple
- ما الجديد في TextKit وعروض النص — Apple