Neomorphism باستخدام SwiftUI. الجزء الأول

تحية يا خبروفيتس! تحسبًا لإطلاق الدورة المتقدمة "IOS Developer" ، قمنا بإعداد ترجمة أخرى مثيرة للاهتمام.




ربما يكون التصميم غير المحوري هو الاتجاه الأكثر إثارة للاهتمام في الأشهر الأخيرة ، على الرغم من أنه في الحقيقة ، استخدمته Apple كمظهر تصميم في WWDC18. في هذه المقالة ، سنلقي نظرة على كيفية تنفيذ تصميم غير مورفي باستخدام SwiftUI ، ولماذا قد ترغب في القيام بذلك ، والأهم من ذلك ، كيف يمكننا تحسين هذا التصميم لزيادة إمكانية الوصول إليه.
هام : التشكل العصبي - يسمى أحيانًا التشكل العصبي - له عواقب وخيمة على إمكانية الوصول ، لذلك على الرغم من الإغراء لقراءة الجزء الأول من هذه المقالة وتخطي الباقي ، أحثك ​​على قراءة المقالة حتى النهاية ودراسة كل من المزايا والعيوب حتى تتمكن من رؤية الصورة الكاملة .


أساسيات Neomorphism


قبل أن ننتقل إلى الكود ، أود أن أوجز بإيجاز مبدأين أساسيين لهذا الاتجاه في التصميم ، لأنهما سيكونان مناسبين مع تقدمنا:

  1. يستخدم Neomorphism الوهج والظل لتحديد أشكال الكائنات على الشاشة.
  2. يميل التباين إلى الانخفاض ؛ لا يتم استخدام الأبيض أو الأسود تمامًا ، مما يسمح لك بإبراز الإبرازات والظلال.

والنتيجة النهائية هي مظهر يشبه "البلاستيك المبثوق" - تصميم واجهة مستخدم يبدو بالتأكيد جديدًا ومثيرًا للاهتمام دون أن يصطدم بعينيك. لا يسعني إلا أن أكرر مرة أخرى أن تقليل التباين واستخدام الظلال لإبراز الأشكال يؤثر بشكل خطير على إمكانية الوصول ، وسنعود إلى ذلك لاحقًا.

ومع ذلك ، ما زلت أعتقد أن الوقت المستغرق في التعلم حول الشكل الجديد في SwiftUI يستحق كل هذا العناء - حتى إذا لم تستخدمه في تطبيقاتك الخاصة ، فإنه يشبه كاتا كتابة التعليمات البرمجية للمساعدة في صقل مهاراتك.

حسنًا ، يكفي الكلام الخامل - دعنا ننتقل إلى الرمز.

بناء خريطة غير صرفية


أبسط نقطة انطلاق هي إنشاء خريطة غير شكلية: مستطيل دائري يحتوي على بعض المعلومات. بعد ذلك ، سنلقي نظرة على كيفية نقل هذه المبادئ إلى أجزاء أخرى من SwiftUI.

لنبدأ بإنشاء مشروع iOS جديد باستخدام قالب تطبيق Single View. تأكد من استخدام SwiftUI لواجهة المستخدم ، ثم قم بتسمية المشروع Neumorphism.

نصيحة: إذا كان لديك حق الوصول لمعاينة SwiftUI في Xcode ، فإنني أوصيك بتنشيطه على الفور - سيكون من الأسهل عليك أن تجربه.

سنبدأ بتحديد لون يمثل لونًا كريميًا. هذا ليس رماديًا نقيًا ، بل ظلًا خفيفًا للغاية يضيف القليل من الدفء أو البرودة إلى الواجهة. يمكنك إضافته إلى دليل الأصول إذا أردت ، ولكن الآن أصبح من السهل القيام بذلك في التعليمات البرمجية.

أضف هذا Colorخارج الهيكل ContentView:

extension Color {
    static let offWhite = Color(red: 225 / 255, green: 225 / 255, blue: 235 / 255)
}

نعم ، إنها بيضاء تقريبًا ، لكنها مظلمة بما يكفي لجعل اللون الأبيض الحقيقي يبدو مثل التوهج عندما نحتاج إليه.

الآن يمكننا ملء الجسم من ContentViewخلال توفيره ZStack، الذي يشغل الشاشة بأكملها ، باستخدام لوننا شبه الأبيض الجديد لملء المساحة بالكامل:

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.offWhite
        }
        .edgesIgnoringSafeArea(.all)
    }
}

لتمثيل خريطتنا ، سنستخدم مستطيل مستدير الزوايا بدقة 300 × 300 لجعلها جميلة وواضحة على الشاشة. لذا أضف هذا إلى ZStackاللون تحت:

RoundedRectangle(cornerRadius: 25)
    .frame(width: 300, height: 300)

بشكل افتراضي ، سيكون أسود ، ولكن من أجل تطبيق الشكل الجديد ، نريد تقليل التباين بشكل حاد ، لذلك سنستبدله بنفس اللون الذي نستخدمه للخلفية ، في الواقع جعل الشكل غير مرئي.

لذا قم بتغييرها على هذا النحو:

RoundedRectangle(cornerRadius: 25)
    .fill(Color.offWhite)
    .frame(width: 300, height: 300)

نقطة مهمة: نحدد الشكل باستخدام الظلال ، واحد داكن وآخر فاتح ، كما لو أن أشعة الضوء يلقي من الزاوية اليسرى العليا من الشاشة.

يسمح لنا SwiftUI بتطبيق المُعدِّلات عدة مرات ، مما يسهل تنفيذ الشكل الجديد. أضف المعدلين التاليين إلى المستطيل المستدير الزوايا:

.shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
.shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)

إنها تمثل إزاحة الظل الداكن في الركن الأيمن السفلي وإزاحة الظل الفاتح في الزاوية اليسرى العليا. ظل الضوء مرئي لأننا استخدمنا خلفية شبه بيضاء ، والآن أصبحت الخريطة مرئية.

لقد كتبنا بضعة أسطر فقط من التعليمات البرمجية ، ولكن لدينا بالفعل خريطة غير مورفقية - آمل أن توافق على أن SwiftUI يسهل العملية بشكل مدهش!



إنشاء زر بسيط غير مورفي


من بين جميع عناصر واجهة المستخدم ، يمثل التشكل العصري خطرًا منخفضًا إلى حد ما على البطاقات - إذا كانت واجهة المستخدم داخل بطاقاتك واضحة ، فقد لا تحتوي البطاقة على حدود واضحة ، ولن يؤثر ذلك على إمكانية الوصول. الأزرار هي مسألة أخرى ، لأنها مصممة للتفاعل ، لذا فإن تقليل تباينها يمكن أن يضر أكثر مما ينفع.

دعنا نتعامل مع هذا عن طريق إنشاء نمط زر خاص بنا ، حيث أن هذه هي الطريقة التي يسمح بها SwiftUI بتوزيع تكوينات الأزرار في العديد من الأماكن. هذا أكثر ملاءمة من إضافة العديد من المعدلات إلى كل زر تقوم بإنشائه - يمكننا ببساطة تحديد النمط مرة واحدة واستخدامه في العديد من الأماكن.

سنقوم بتعريف نمط زر سيكون فارغًا بالفعل: سويفتوي سيعطينا تسمية الزر ، والتي قد تكون نصًا أو صورة أو أي شيء آخر ، وسوف نعيده دون تغييرات.

أضف هذا الهيكل في مكان ما خارج ContentView:

struct SimpleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
    }
}

هذا configuration.labelهو ما يحتوي على محتويات الزر ، وسرعان ما سنضيف شيئًا آخر. أولاً ، دعنا نحدد زرًا يستخدمه حتى تتمكن من رؤية كيفية تطور التصميم:

Button(action: {
    print("Button tapped")
}) {
    Image(systemName: "heart.fill")
        .foregroundColor(.gray)
}
.buttonStyle(SimpleButtonStyle())

لن ترى أي شيء خاص على الشاشة ، ولكن يمكننا إصلاحه عن طريق إضافة تأثيرنا غير المورفولوجي إلى نمط الزر. هذه المرة لن نستخدم مستطيلًا دائريًا ، لأن الأيقونات البسيطة تكون الدائرة أفضل ، لكننا نحتاج إلى إضافة بعض المسافة البادئة بحيث تكون مساحة النقر على الزر كبيرة وجميلة.
قم بتغيير طريقتك makeBody()بإضافة بعض المسافة البادئة ثم وضع تأثيرنا غير المورفي كخلفية للزر:

configuration.label
    .padding(30)
    .background(
        Circle()
            .fill(Color.offWhite)
            .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
            .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
    )



هذا يجعلنا قريبين بما يكفي من التأثير المطلوب ، ولكن إذا قمت بتشغيل التطبيق ، فسوف ترى أنه من الناحية العملية ، لا يزال السلوك غير مثالي - لا يتفاعل الزر بصريًا عند الضغط عليه ، والذي يبدو غريبًا فقط.

لإصلاح ذلك ، نحتاج إلى قراءة الخاصية configuration.isPressedداخل نمط الزر المخصص لدينا ، والذي يوضح ما إذا كان الزر مضغوطًا حاليًا أم لا. يمكننا استخدام هذا لتحسين أسلوبنا لإعطاء بعض المؤشرات المرئية عما إذا تم الضغط على الزر.

لنبدأ بدائرة بسيطة: سنستخدم Groupالأزرار للخلفية ، ثم نتحقق configuration.isPressedونرجع إما دائرة مستوية إذا تم الضغط على الزر ، أو دائرتنا المظلمة الحالية بخلاف ذلك:

configuration.label
    .padding(30)
    .background(
        Group {
            if configuration.isPressed {
                Circle()
                    .fill(Color.offWhite)
            } else {
                Circle()
                    .fill(Color.offWhite)
                    .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
                    .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
            }
        }
    )

نظرًا isPressedلاستخدام دائرة ذات لون شبه أبيض في الحالة ، فإنها تجعل تأثيرنا غير مرئي عند الضغط على الزر.

تحذير: نظرًا للطريقة التي يحسب بها SwiftUI المناطق القابلة للنقر عليها ، فقد قمنا عن غير قصد بجعل منطقة النقر لزرنا صغيرة جدًا - الآن تحتاج إلى النقر على الصورة نفسها ، وليس على التصميم غير المشكل من حولها. لإصلاح ذلك ، أضف معدِّلًا .contentShape(Circle())بعد ذلك مباشرةً .padding(30)، مما يجبر SwiftUI على استخدام كل مساحة النقر المتاحة.

يمكننا الآن إنشاء تأثير التقعر الاصطناعي عن طريق قلب الظل - من خلال نسخ معدلين shadowمن التأثير الأساسي ، واستبدال قيم X و Y باللونين الأبيض والأسود ، كما هو موضح هنا:

if configuration.isPressed {
    Circle()
        .fill(Color.offWhite)
        .shadow(color: Color.black.opacity(0.2), radius: 10, x: -5, y: -5)
        .shadow(color: Color.white.opacity(0.7), radius: 10, x: 10, y: 10)
} else {
    Circle()
        .fill(Color.offWhite)
        .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
        .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
}

قدر النتيجة.



إنشاء ظلال داخلية بنقرة زر


يعمل رمزنا الحالي ، من حيث المبدأ ، بالفعل ، لكن الناس يفسرون التأثير بشكل مختلف - البعض يرى أنه زر مقعر ، والبعض الآخر يرى أن الزر لا يزال غير مضغوط ، فقط الضوء يأتي من زاوية مختلفة.

تتمثل فكرة التحسين في إنشاء ظل داخلي يحاكي تأثير الضغط على الزر للداخل. هذا ليس جزءًا من مجموعة SwiftUI القياسية ، ولكن يمكننا تنفيذها بسهولة تامة.

يتطلب إنشاء ظل داخلي اثنين من التدرجات الخطية ، وسيكونان الأول فقط من بين العديد من التدرجات الداخلية التي سنستخدمها في هذه المقالة ، لذلك سنضيف على الفور ملحقًا صغيرًا إضافيًا LinearGradientلتبسيط إنشاء التدرجات القياسية:

extension LinearGradient {
    init(_ colors: Color...) {
        self.init(gradient: Gradient(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing)
    }
}

مع هذا ، يمكننا ببساطة توفير قائمة متغيرة من الألوان لاستعادة التدرج الخطي في الاتجاه القطري.

الآن حول النقطة المهمة: بدلاً من إضافة ظلين مقلوبين إلى دائرتنا المضغوطة ، سنقوم بتراكب دائرة جديدة بضبابية (حد) ، ثم نطبق دائرة أخرى مع التدرج كقناع. هذا أصعب قليلاً ، لكن دعني أشرح ذلك بشكل مجزأ:

  • دائرتنا الأساسية هي دارتنا الحالية مع تأثير نيومورفي ، مملوء بلون شبه أبيض.
  • نضع دائرة فوقها ، مؤطرة بإطار رمادي ، وطمس قليلاً لتنعيم حوافها.
  • ثم نطبق قناعًا بدائرة أخرى على هذه الدائرة متراكبة في الأعلى ، وهذه المرة مليئة بتدرج خطي.

عندما تقوم بتطبيق عرض واحد كقناع لآخر ، يستخدم SwiftUI قناة ألفا للقناع لتحديد ما يجب عرضه في العرض الأساسي.

لذلك ، إذا رسمنا حدًا رماديًا ضبابيًا ، ثم قمنا بإخفائه بتدرج خطي من الأسود إلى الشفاف ، فستكون الضبابية الضبابية غير مرئية من جانب وتزداد تدريجيًا على الجانب الآخر - سنحصل على تدرج داخلي سلس. لجعل التأثير أكثر وضوحًا ، يمكننا تغيير الدوائر المظللة قليلاً في كلا الاتجاهين. بعد أن جربت قليلاً ، وجدت أن رسم ظل فاتح بخط سميك أكثر من الظل الداكن يساعد على زيادة التأثير إلى أقصى حد.

تذكر أنه يتم استخدام ظلين لخلق إحساس بالعمق في الشكل الجديد: ضوء واحد وآخر داكن ، لذلك سنضيف تأثير الظل الداخلي مرتين بألوان مختلفة.

قم بتغيير الدائرة configuration.isPressكما يلي:

Circle()
    .fill(Color.offWhite)
    .overlay(
        Circle()
            .stroke(Color.gray, lineWidth: 4)
            .blur(radius: 4)
            .offset(x: 2, y: 2)
            .mask(Circle().fill(LinearGradient(Color.black, Color.clear)))
    )
    .overlay(
        Circle()
            .stroke(Color.white, lineWidth: 8)
            .blur(radius: 4)
            .offset(x: -2, y: -2)
            .mask(Circle().fill(LinearGradient(Color.clear, Color.black)))
    )

إذا قمت بتشغيل التطبيق مرة أخرى ، سترى أن تأثير الضغط على زر أكثر وضوحًا ويبدو أفضل.



في هذا الصدد ، انتهى الجزء الأول من الترجمة. في الأيام القادمة ، سننشر الاستمرارية ، والآن ندعوك لمعرفة المزيد عن الدورة القادمة .

All Articles