02 هياكل البيانات: القوائم أحادية الرابط الجزء الثاني


مستوى المقال: متوسط

بسم الله الرحمن الرحيم 

السلام عليكم ورحمة الله وبركاته ...،

أولاً اعتذر جداً عن التأخر في طرح الشرح الذي كان من المفترض يكون بعد أسبوع من الموضوع السابق

تطرقنا في شرحنا السابق الى مفهوم القائمة أحادية الرابط وشرحنا استخداماته وطريقة عملها نظريا

شرحنا هذا سيكون عمليا من الدرجة الأولى بحيث سيكون كله حول الأكواد وتفصيلها.

 

١. إنشاء العقدة Node:

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

لذلك يجب علينا بنائها ابتداءً.

public class SLLNode {
	public int info;
	public SLLNode next;
	
	public SLLNode(int i){
		this(i, null);
	}
	
	public SLLNode(int i, SLLNode n){
		this.info = i;
		this.next = n;
	}
}

هذا الكلاس والذي سمي بـ SSLNode هو الذي يصف لنا كل عقدة على حدى. 

وكل عقدة تتكون من info وهو المتغير الذي يحتوي على البيانات (المعلومة) وفي حالتنا هذه تكون البيانات من نوع int.

وكذلك next وهو عبارة عن الرابط الذي يصل العقدة الحالية بالعقدة التي تليها ومن الطبيعي أن يكون من نوع  SLLNode.

هذا الكلاس يحتوي على two Constructor وهما عبارة عن وظائف تستخدم لإنشاء العقد Nodes.

الأولى تستخدم لإنشاء عقدة لكنها لا ترتبط بأخرى كما نرى فيها فقد قمنا باستدعاء الميثود الثانية وقمنا بوضع null مكان الرابط.

أما الثانية فتنشأ عقدة ترتبط بأخرى. (قمت بتفصيلها في الموضوع السابق)

 

٢. إنشاء القائمة أحادية الرابط:

الكلاس السابق يمكننا من إنشاء عقدة واحدة لكن في هذا الكلاس سنتمكن من إنشاء قائمة كاملة تتكون من عدد من العقد Nodes.

أ. مؤشر لأول عنصر وآخر عنصر بالقائمة:

لكي نسهل عملية التنقل داخل القائمة سنقوم بإنشاء مؤشرين أحدهما يشير إلى أول عنصر بالقائمة (head: الرأس)،

والآخر يشير إلى آخر عنصر بالقائمة (tail: الذيل). هذين المؤشرين عبارة عن متغيريين من نوع SSNode.

protected SLLNode head, tail;

في بداية إنشاء القائمة (أو في حالة كانت القائمة خالية) فإن هذين العنصريين يكونان خاليين:

	public SLList(){
		head = tail = null;
	}

كما يتبين في هذه الـ Constructor.

 

ب. فحص القائمة إذا كانت خالية أو لا:

هذه الـ Method مهمة جداً ولها أستخدام في جل الـ Methods الأخرى، ومهمتها هي فحص إذا ما كانت القائمة خالية أو لا.

وتقوم بإرجاع قيمة Boolean إما true أو false.

	public boolean isEmpty(){
		return head == null;
		//or return tail == null;
	}

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

 

ب. إضافة عنصر جديد إلى الرأس (بداية القائمة):

	public void addToHead(int el){
    	head.next = head;
		head = new SLLNode(el);
		if (tail = null){ 
			tail = head;
		}
	}

أولا نجعل الرأس يشير إلى نفسه لكي نربط العنصر الجديد بالعنصر الأول السابق.

عندها نقوم بإنشاء عنصر Node ونجعله الرأس الجديد

في حالة كانت القائمة خالية نجعل الذيل يشير إلى نفس العنصر الذي يشير له الرأس.

ملاحظة: عندما يشير الرأس والذيل إلى نفس العنصر فهذا يعني بأن لدينا عنصر واحد فقط في القائمة.

 

ج. إضافة عنصر جديد إلى الذيل (نهاية القائمة):

	public void addToTail(int el){
		if(!isEmpty()){
			tail.next = new SLLNode(el);
			tail = tail.next;
		}else{
			head = tail = new SLLNode(el);
		}
	}

نفحص القائمة إذا كانت خالية:

- في حالة كانت غير خالية: ننشأ عنصر جديد ونجعل الذيل الحالي يشير إليه، ومن ثم نجعله الذيل الجديد.

- في حال كانت القائمة خالية: ننشأ عنصر جديد ونجعل الرأس والذيل يشيران إليه.

 

د. حذف العنصر الموجود بالرأس (أول عنصر):

	public int deleteHead(){
		int el = head.info;
		if(head == tail){
			head = tail = null;
		}else{
			head = head.next;
		}
		
		return el; 
	}

هذه الـ method تقوم بحذف العنصر الموجود بالرأس ومن ثم ترجع قيمته.

أولا نحفظ القيمة داخل متغير el لكي لا تضيع بعد حذف العنصر.

ومن ثم نفحص إذا كانت القائمة:

- تحتوي على عنصر واحد: فعندها نجعل الرأس والذيل يسيران إلى لا شيء null وتكون القائمة حينها خالية.

- تحتوي على أكثر من عنصر: فعندها نجعل العنصر الذي بعد الرأس هو الرأس الجديد، وفي هذه الحالة سيكون العنصر

لا يوجود ما يشير إليه ويحذف.

ومن ثم ترجع القيمة التي تم حذفها.

 

هـ- حذف العنصر الموجود بالذيل (آخر عنصر):

	public int deleteTail(){
		int el = tail.info;
		if(tail == head){
			head = tail = null;
		}else{
			SLLNode temp;
			for(temp = head; temp.next != tail; temp = temp.next);

			tail = temp; 
			tail.next = null;
		}
		
		return el;
	}

كما في السابق نحتفظ بقيمة العنصر لكي نقوم بإعادته بعد الإنتهاء من عملية الحذف لكن الحذف من الذيل ليس بسهولة الحذف من الرأس.

نفحص التالي:

- في حالة أن القائمة تحتوي على عنصر واحد فقط: فعندها نحذف القائمة كلها بجعل الرأس والذيل null.

- في حالة أن القائمة تحتوي على أكثر من عنصر:

أولا علينا أن نبحث عن العنصر السابق للذيل (العنصر قبل الأخير)، وذلك بأستخدام for loop قبلها علينا أن ننشأ مؤشر مؤقت باسم temp 

وفي بداية الـ loop نجعله يشير إلى الرأس، وننقله من عنصر إلى آخر إلى أن يصل إلى العنصر ما قبل الأخير وذلك بفحص إذا ما كان 

temp.next أي العنصر الذي يليه هو tail فعندها نكون وصلنا إلى العنصر ما قبل الأخير.

بعد أن نجد العنصر ما قبل الأخير نجعل الذيل الجديد ومن ثم نجعل الذيل يشير إلى null لكي يتم حذف العنصر الأخير تماماً.

 

و. طباعة جميع عناصر القائمة:

	public void printAll(){
		System.out.print("[");
		for(SLLNode temp = head; temp != null; temp = temp.next){
			System.out.print(temp.info + " ");
		}
		System.out.println("]");
	}

كما شرحنا سابقا نستخدم for loop للتنقل بين عناصر القائمة وطباعة قيمة كل عنصر على الشاشة.

 

ز. فحص وجود العنصر في القائمة أو لا:

	public boolean isInList(int el){
		if(isEmpty()){
			return false;
		}else{
			SLLNode temp;
			for(temp = head; temp.info != el && temp.next != null; temp = temp.next);
			return temp != null;
		}
	}

للبحث عن وجود قيمة داخل القائمة من عدمه يجب علينا أن نتنقل داخل القائمة عنصر بعنصر، وسنستخدم الأسلوب السابق

وهو باستخدام for loop. ولكن قبلها يجب علينا أن نتأكد من أن القائمة غير خالية.

الـ for loop ستتوقف في إحدى حالتين في حالة وجدة العنصر داخل القائمة وذلك ببطلان الشرط (tmep.info != el)

أو في حالة وصول temp إلى نهاية القائمة. 

عندها سنرجع القية المعاكسة لهذا الشرط (tmep != null) في حالة وصول الـ temp إلى نهاية القائمة بدون أن يجد العنصر

فعندا سيكون temp يساوي null وفي هذه الحالة سيرسل false أما في حالة إجاد العنصر فعندها temp لن يكون  null ويرسل true.

 

ح. حذف عنصر من القائمة:

public void delete(int el){
		if(!isEmpty() && isInList()){
			if(head == tail && head.info == el){
				head = tail = null;
			}else if(el == head.info){
				head = head.next;
			}else{
				SLLNode pre, temp;
				for(temp = head.next, pre = head; temp.info != el;
						pre = temp, temp = temp.next);
				if(temp != null){
					pre.next = temp.next;

					if(temp == tail){
						tail = pre;
					}

				}
			}
		}
	}

في حالة أردنا حذف عنصر من القائمة علينا أن نفكر في جميع الإحتمالات وهي كالتالي:

- هل القائمة غير خالية؟! وهل العنصر في القائمة ولتححق سنستخدم isEmpty و isInList، في حالة لم تكن القائمة خالية وكان العنصر بالقائمة ننتقل للخطوة التالية:

* هل القائمة تحتوي على عنصر واحد؟!: في هذه الحالة نقوم بحذف هذا العنصر ونجعل القائمة خالية.

* هل القائمة تحتوي على أكثر من عنصر والعنصر المراد حذفة بالرأس؟: عنذها نحذف الرأس كما أشرنا في الفقرة (د).

*الخيار الأخير هو أن العنصر موجود داخل القائمة: 

في هذه الحالة نحتاج للبحث عن العنصر بنفس الأسلوب السابق لكننا هنا على خلاف الخطوات السابقة نحتاج إلى متغيرين مؤقتين

أحدهما يشير إلى العنصر المراد حذفه والآخر يشير إلى العنصر الذي يسبقه. ولكي نقوم بذلك فعند إنشاء temp وهو العنصر المؤقت الذي سنبحث

عن طريقه للعنصر المراد حذفه نجعل pre العنصر الذي يسبقه وذلك كما يلي:

pre = head , temp = pre.next 

كما نعلم فنحن لسنا بحاجة لفحص الرأس فقد تأكدنا من أن العنصر ليس بالرأس. تنتهي for loop في حال إجاد العنصر المراد حذف مع العلم بأننا لن نصل إلى نهاية القائمة

لأننا تأكدنا من أن العنصر موجود في القائمة في الخطوة الأولى. وعند نهاية كل دورة من for loop نقوم بتحديث قيمتي pre and temp.

بعد ذلك نجعل الـ pre يشير للعنصر الذي يلي temp أي يشير للعنصر الذي بعد العنصر المراد حذفه عندها لن يكون هناك ما يشير إليه ويحذف.

** تأكد أخير في حال أن العنصر الذي حذفنها هو الأخير فعندها يجب أن نجعل الذيل يشير إلى null.

 

#شرح مفصل للـ for loop المستخدمة مع القوائم بشكل عام:

لأهميتها القصوى سوف أفصلها أكثر، إبتداءً بالشكل العام لها:

for (SLLNode temp = head; temp != null; temp = temp.next);

أولا نقوم بإنشاء متغير مؤقت ونسميه بأي أسم كان هذا المتغير في بداية الـ loop سيشير لأول عنصر بالقائمة

وهو الرأس head بعد ذلك يتأكد من تحقق الشرط وهو أن هذا المتغير لا يساوي null (أي أنه غير خالي)، ومن ثم

يحدث قيمة المتغير وذلك بجعله العنصر التالي له عن طريق (temp = temp.next) ومن ثم يقوم بالتأكد من الشرط مجدداً

هكذا حتى يصل إلى أن temp مساوي لـ tail وعندها يسنتقل للعنصر الذي يليه وهو في الحقيقة null لأنه tail.next دائما يساوي null

وهكذا تنتهي  for loop

ملاحظة: قمت بوضع ملفات للـ SLLNode and SLList classes في المرفقات تحتوي على تعليقات توضيحية.

 

أتمنى من الله أن أكون قد وفقت في تفصيل موضوعنا هذا

في حفظ الله إلى لقاء آخر

SLList.java

SLLNode.java

Abather

1


اراء المستخدمين


لاتوجد تعليقات لعرضها .



انشئ حساب جديد او قم بتسجيل دخولك لتتمكن من اضافه تعليق جديد

يجب ان تكون عضوا لدينا لتتمكن من التعليق

انشئ حساب جديد

سجل حسابك الجديد لدينا في الموقع بمنتهي السهولة .


سجل حساب جديد

تسجيل الدخول

هل تمتلك حساب بالفعل ؟ سجل دخولك من هنا.


سجل دخولك الان

Ads Belongs To This website

  • بسم الله الرحمن الرحيم 
    أطلقت google مؤخراً Android IoT وفي هذه المقالة البسيطة سنقوم بثبيت النسخة على ٌRaspberry Pi 3 وعمل مشروع بسيط.
    بداية عليك تحميل النسخة الخاصة بالـ Raspberry Pi 3 من هنا وبعد ذلك تثبيته على SD card حسب النظام الذي تستخدمه, بالنسبة لمستخدمي Ubuntu كالتالي 
    sudo dd if=Downloads/androidthings_rpi3_devpreview_4.img of=/dev/yourFlash  بعد ذلك ادخل SD card في Raspberry Pi وأوصل الكهرباء, وسلك Ethernet يفترض أن ترى مثل هذه الشاشة 
     
     
    لاحظ وجود IP في أسفل الصفحة فهو الذي سنستخدمه في التواصل مع Raspberry Pi وإيصاله بالشبكة.
    الان نفتح  Android studio وعن طريق terminal الموجود فيه نقوم بكتابة الأمر التالي 
    adb connect <ip-address> ip address هو الرقم المكتوب لديك في أسفل الشاشة بعد Ethernet IP, لو تم الاتصال بشكل صحيح سيظهر هذا السطر 
    connected to <ip-address>:5555 , بهذه الطريقة نكون متصلين بالـ Raspberry Pi لكن, نريد أن نجعل الأتصال عن طريق wifi 
     فنقوم بكتابة الأمر التالي 
    adb shell am startservice \ -n com.google.wifisetup/.WifiSetupService \ -a WifiSetupService.Connect \ -e ssid <Network_SSID> \ -e passphrase <Network_Passcode> ssid أسم الشبكة المراد الاتصال بها 
    passcode الرقم السري الخاص بالشبكة 
    انتهينا من فقرة التثبيت إن صح التعبير وقد يتخللها الكثير من الإشكالات وdebug وأرحب بأي استفسار لتجاوز هذه المهمة, الان لننتقل إلى برمجة الـ board.
     أولا علينا أن نقوم بتوصيل كما في الصورة 
     
     

     
    وهنا شرح لـ pins الخاصة بـ Rpi
    الكود كالتالي 
    public class MainActivity extends Activity { private Gpio mLedGpio; private boolean mLedState = false; Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.button); PeripheralManagerService service = new PeripheralManagerService(); //system service responsible for managing peripheral connections try { String pinName = "BCM4";//pin number mLedGpio = service.openGpio(pinName); mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);//Output pin ,start with 0v } catch (IOException e) { e.printStackTrace(); } mButton.setOnClickListener(v -> { try { if (mLedState) { mLedGpio.setDirection(Gpio.ACTIVE_HIGH);//5v mLedState = false; }else { mLedGpio.setDirection(Gpio.ACTIVE_LOW);//0v mLedState = true; } } catch (IOException e) { e.printStackTrace(); } }); } }  أما الـ UI فيحتوي button واحد id الخاص فيه هو button 

    بداية لدينا 
    PeripheralManagerService service = new PeripheralManagerService();  PeripheralManagerService هي Service من من نظام الأندرويد تسهل لك التعامل مع pin الخاصة بـ Rpi
    بعد ذلك قمنا بختيار pin عن طريق اسمه 
    String pinName = "BCM4"; mLedGpio = service.openGpio(pinName); mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW); وربطه بمتغير من نوع Gpio, ثم قمنا بضبط إعداده بأن يكون output ويبدأ بـ 0 فولت ويوجد العديد من Constant التي تساعد في هذه الاعدادت
    mButton.setOnClickListener(v -> { if (mLedState) { mLedGpio.setDirection(Gpio.ACTIVE_HIGH); mLedState = false; }else { mLedGpio.setDirection(Gpio.ACTIVE_LOW); mLedState = true; } هنا قلنا في حال تم الضغط على button تأكد من حالته, في حال كان  on اجعله off والعكس, ونلاحظ أننا قمنا بأستخدام المزيد من Constant  التي تحدثنا عنها في السطر السابق.
    ونلاحظ أيضا أننا كتبنا الدالة الخاصة بنا بطريقة lambda.
     
    لمعلومات أكثر عن أساسيات hardware
    واللاستزادة :
    https://developer.android.com/things/sdk/index.html
    مستوى المقال: مبتدئ

    بواسطه 3zcs , في

  • كثير من مطوري الاندرويد يقومون باستخدام AsyncTask وخاصة عندما يريدون تنفيذ بعض الاكواد في thread منفصل, ولكن كثير منهم يتعامل مع هذا الكلاس بطريقة خاطئة ودون فهم لدورة حياة الاندرويد وما الذي يحدث بالضبط في الخلفية, في هذه المقالة سنوضح الخطأ الشائع وكيف تقوم بمعالجة المشكلة.
    الاستخدام الخاطئ
    الطريقة الشائعة والتي تجدها في معظم المواقع والتي ستجدها بالتاكيد في معظم مشاريع مطوري الاندرويد هي كالتالي:
    public class MainActivity extends Activity {     @Override protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);   }     // Somewhere the AsyncTask is started     public class MyAsyncTask extends AsyncTask<Void, Void, String> {       @Override protected String doInBackground(Void... params) {       // Do work       return result;     }       @Override protected void onPostExecute(String result) {       Log.d("MyAsyncTask", "Received result: " + result);     }   } } المشكلة بالكود هي ان كلاس  AsyncTask مضمن بداخل الـ Activity وبالتالي اي تغيير في حالة الـ Activity والتي قامت باستدعاء الـ AsyncTask الموجود بداخلها قد يقتل عمل الـ AsyncTask, ولكن لن يتم ازالة الـ Activity من الذاكرة الا في حالة انتهاء الـ AsyncTask.
    الان لتتخيل معي المثال التالي: لدي اكتفتي واحدة وبداخلها AsyncTask وفي كل مرة اقوم باستدعاء الاكتفتي فانا استدعي AsyncTask جديد واي تغيير في حالة الاكتفتي ربما استدعي AsyncTask اخر, وكل واحد من الـ AsyncTask لديه عملية يجب عليه ان ينفذها قبل ان يُقتل وقبل ان يسمح للـ Activity بان تزال من الذاكرة.
    ماسيحدث بالمثال بالاعلى هو مشكلتين, الاولى هي مشكلة في الذاكرة memory issues والثانية هي انك ستفقد العمليات التي قام الـ AsyncTask بتنفيذها وستقوم بتكرار العملية اكثر من مرة.
    معالجة المشكلة
    لمعالجة المشكلة بالاعلى فالافضل ان تقوم باستخدام مبدأ الـ event bus وطريقة تنفيذه كالتالي:
    نقل الـ AsyncTask الى كلاس منفصل عن الـ  Activity. انشاء كلاس جديد يقوم بتخزين ناتج  AsyncTask عند اكتماله. ربط الـ الـAsyncTask بالـ Activity الخاصة بك عن طريق الـ bus. لذلك سنقوم بانشاء كلاس جديد تحت مبدأ الـ Singleton ويكون كالتالي:
     public class MyBus { private static final Bus BUS = new Bus();  public static Bus getInstance() { return BUS;    } } ولنفترض ان الـ AsyncTask سيقوم بارجاع String فقط عن اكتمال عمله, لذلك سنقوم بانشاء  كلاس جديد وليكن اسمه AsyncTaskResultEvent :
    public class AsyncTaskResultEvent { private String result; public AsyncTaskResultEvent(String result) { this.result = result; } public String getResult() { return result; } } نقوم بانشاء كلاس الـ AsyncTask والذي سيقوم بتنفيذ اي كود تريده:
    public class MyAsyncTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) { Random random = newRandom(); final long sleep = random.nextInt(10); try{ Thread.sleep(sleep * 1000); } catch(InterruptedException e) { e.printStackTrace(); } return"Slept for "+ sleep + " seconds"; } @Override protected void onPostExecute(String result) { MyBus.getInstance().post(newAsyncTaskResultEvent(result)); } } اخيرا سنقوم بربط الـ AsyncTask  مع الـ Activity ولكن يجب عليك بالغاء الربط بينهم في ميثود onDestroy حتى لانقع في نفس المشكلة من جديد:
    public class MainActivity extends Activity {   @Override protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);     findViewById(R.id.button).setOnClickListener(newView.OnClickListener() {       @OverridepublicvoidonClick(View v) {         newMyAsyncTask().execute();       }     });     MyBus.getInstance().register(this);   }   @Override protected void onDestroy() {     MyBus.getInstance().unregister(this);     super.onDestroy();   }   @Subscribe public void onAsyncTaskResult(AsyncTaskResultEvent event) {     Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show();   } } اخيراً
    هذه الطريقة ليست محصورة مع الـ AsyncTask بل تستطيع استخدامها مع اي امر اخر, فعلى سبيل المثال الـ services لديه نفس المشكلة اذا استخدمتها بطريقة خاطئة وحلها هو باستخدام الـ event bus.
    مستوى المقال: متوسط

    بواسطه Alhazmy13 , في



  • البعض اخبرني بأنه يرغب بأمثله في الـ Animation وليس فقط شرح الأساسيات
     
    لذلك قررت اشرح مثال لعمل Menu مشابه للمدرج في تطبيق Telegram
     
     
    الفكرة باختصار هي إضافة زر بدلاَ من النص في الـ Navigation bar
    ومن ثم تغير موقع الـ View عند الضغط على الزر


    اذا لنبدأ في الشرح
     
    أولا نقوم بإضافة الـ Navigation Controller


    عن الطريق تطبيق اتباع الصورة التالية :
     
     


     

    ومن ثم نضيف View اسفل الـ Navigation bar


    بحجم عرض 375 وارتفاع 50

    طبعا يعتمد على الابعاد الي انت تبغاها انا استخدمت هذه الابعاد

    نضيف القيود التالية :

     

     

    ومن ثم نضيف 3  ازرار

    احجام الايقونات كانت متفاوتة  ، لكن اعتمدت على حجم 60 في 60
    او اصغر بقليل بالنسبة لأحجام ايقونات الـ x2
    الخاصة بحجم iPhone 7 و iPhone SE

    الان سوف نحتاج نستخدم الـ Stack View
    لأنه سوف يسهل علينا جعل الايقونات بمسافة متساوية !
     
    قم بتحديد جميع الايقونات في أنٍ واحد

    ومن ثم ثم باتباع الصورة التالية :
     
     

     



    سوف تلاحظ تلاصق الايقونات مع بعض

    قوم بالذهاب الى خصائص الـ Stack View

    واجعل المسافة Spacing بـ 60


    كما في الصورة التالية :

     

     


    واجعل الايقونات في منتصف الـ View

    الان قم بإضافة قيود الى الـ Stack View:
     



    لاحظ باني اضفت القيود بشكل يدوي
    لجعله الـ Stack View
    ياخد حجم الـ View الذي خلفه

    عند التعامل مع الـ Stack View يتوجب عليك استخدام القيود لتغير ابعاده

    لاحظ الفرق

    قبل القيود كان بالشكل التالي :
     
     



    بعد إضافة القيود اصبح بالشكل التالي :
     
     

     


    الان سوف تلاحظ بان الازرار والـ View متناسبة مع جميع احجام الاجهزة

    أيضا لاحظ بأنه عند استخدام الـ Stack View

    تكون لست بحاجة الى وضع قيود لكل زر على حدى !

    كل ما عليك هو وضع القيود على نفس الـ Stack View فقط

    هذه هي قوة الـ Stack View

    سوف تحتاج الى استخدامه بكثره وخاصة عند عمل الـ Animation
     
    الان نعود للدرس
     
    قبل ان نبدأ تأكد بأن الـ Stack View
    بداخل الـ View الذي اضفناها لأننا سوف نضيف الـ View فقط الى ملف الاكواد !
     
    يمكن التأكد بمشاهده الترتيب

    الـ Stack يكون بداخل الـ View الذي اضفناه سابقا

    كما في الصورة التالية:
     

     


    الان نقوم بإضافة الـ View الى ملف الاكواد
    ونجعله باسم Menu View

    وأيضا نضيف قيد الـ top  الخاص بـ View

    ونجعله اسم menuTop



    يصبح ملف الاكواد بالشكل التالي

     
    import UIKit class ViewController: UIViewController {     @IBOutlet weak var menuTop: NSLayoutConstraint!          @IBOutlet weak var menuView: UIView!          override func viewDidLoad() {         super.viewDidLoad()                               }


    الان نقوم بإضافة الاكواد التالية في الـ ViewDidLoad


     
     override func viewDidLoad() {         super.viewDidLoad()                  menuTop.constant = -65                  let button =  UIButton(type: .custom)         button.frame = CGRect(x: 0, y: 0, width: 200, height: 40)         button.setTitle("Menu", for: .normal)         button.addTarget(self, action: #selector(self.menuButton), for: .touchUpInside)         self.navigationItem.titleView = button                  menuView.layer.borderWidth = 0.5         menuView.layer.borderColor = UIColor.gray.cgColor                       }
    في البداية جعلنا الـ View مخفي بجعل القيمة -65 وبالتالي يصبح الـ View
    في الأعلى خلف الـ Navigation bar

    ومن ثم انشأنا زر من نوع .custom
     
    اعطينا ابعاد له وهيا
    عرض 200 وارتفاع 40
     
    العرض تستطيع تغيره
    انا جعلته 200 لأنه في حال وجود زر في اليمين واليسار لن يغطي عليه

    اما الارتفاع فهو 40
    ارتفاع الـ Navigation bar
    بدون شريط الساعة
    هو 44 لذلك حجم 40 يعتبر مناسب


    ومن ثم اضفنا عنوان للزر باسم Menu

    وبعدها اضفنا Target
    الي هو جعل الزر يستجيب للضغط
    طبعا اضفنا اسم للـ Function وهو menuButton

    سوف نضيفه لاحقا

    اخيراً قمنا بإضافة الزر بداخل العنوان الـ Navigation bar

    في الأسفل اضفنا الحدود حولينا الـ view
    وغيرنا لون الحدود الى اللون الرصاصي
     
    الان نضيف Function الزر

    باضافة التالي :

     
        func menuButton(button: UIButton) { }

    الان سوف نضيف الـ Aniamtion

    الفكره باختصار

    عند الضغط على الزر لأول مره نغير القيود الى 0
    ليظهر الـ View

    لماذا 0 ؟

    لانه عند اضافة الـ Navigation bar
    تصبح المسافه اسفله بقيمة 0
    بالنسبة الى القيود
    فين حين بدون الـ Navigation bar
    تكون المسافة اسفل شريط الساعه هي الـ 0
     
    وعند الضغط على الزر للمره الثانية نغير القيود الى -65
    وبالتالي يختفي الـ View

    كيف نعلم بأمر الـ Menu هو ظاهر او مختفي ؟
    الطريقة هي باستخدام متغير من نوع bool

    يكون true وتعني بانه مخفي
    ويصبح false
    اذا كان غير مخفي

    لذلك نضيف متغير في خارج الـ Function وأيضا بخارج الـ ViewDidLoad

    باسم
        var check = true

    ومن ثم نضيف التالي في الـ menuButton
     
        func menuButton(button: UIButton) {               if check == true {                  UIView.animate(withDuration: 0.5, animations: {             self.menuTop.constant = 0             self.view.layer.layoutIfNeeded()             self.check = false                      })                  }else {                        UIView.animate(withDuration: 0.5, animations: {                 self.menuTop.constant = -65                 self.view.layer.layoutIfNeeded()                                 self.check = true                                               })                                   }            }

     
    الذي قمنا به هو عمل if
    وقبنا بالتشيك اذا كان القيمة true فهو مخفي لذلك سوف نظهره
    واذا كان غير مخفي سوف نخفيه

    صورة بالنتيجة النهائية :

     

     



    اذا كنت تتسأل كيف غيرت لون الـ Navigation Bar

    فقم باتباع الخطوات التالية :
     




    يمكن تحميل الكود من هنا 
    مستوى المقال: محترف

    بواسطه X901 , في


  • تحدثنا سابقا عن أنواع مختلفة للـ Animation
     
    وفي هذا الموضوع سوف نتحدث أيضا عن نوع اخر
     
    النوع هذا مختلف عن الانواع الأخرى فهو يتركز على موضوع التسلسل الزمني
     
    ذكرت في موضوع الاول من هذه السلسلة عن كيفية عمل Animation للمربع بحيث يتحرك بشكل حرف L

    اذا لاحظت في الكود السابق سوف تلاحظ عدة امور

     
    UIView.animate(withDuration: 1.0, animations: { self.squareView.transform = CGAffineTransform(translationX:0, y: 300) }) { (true) in UIView.animate(withDuration: 1.0, animations: { self.squareView.transform = CGAffineTransform(translationX:-122, y: 300) }) }  
    الاول : هو كثرة الاقواس وتداخلها ، في حال كان التحرك اكثر تعقيداً سوف يزداد تعقيد الكود وتكثر الاقواس وبالتالي يصعب تعديله


    الثاني : اذا اردت جعل الحركة متسلسلة بنفس التوقيت سوف ينبغي عليك تعديل توقيت كل Animation على حدى



    هذه هي فوائد النوع الجديد 
    ازلت الاقواس المتداخله سهولة تعديل الكود او الوقت الكلي للـ Animation  
    قبل أن نبدأ في الشرح العملي ، يتوجب عليا شرحه بصورة نظرية
     
    كيف يعمل ؟

    عند استخدام UIView.animate
    فنحن نقوم بإضافة وقت زمني واذا استخدمنا Function معين سوف نستطيع اضافة أيضا زمن تأخير
    الـ Animation سوف يبدا وينتهي في الزمن المعين بصورة ثابته

    واذا اردنا ان نحرك في اتجاه مختلف فإننا نقوم بإضافة UIView.animate
    أخر بداخل اقواس الاستكمال التي نضعها أحيانا بقيمة true
    بما يعني في كل مره سوف نضع زمن مختلف ونعيد كتابة UIView.animate
     
    في هذا النوع الذي سوف نقوم به هو التالي :
     
    سوف نعطي زمن كلي محدد ولنقول مدة ثانية واحده
    ومن ثم ننفذ الـ Aniamtion بالنسبة المئوية !


    ما المقصود بالنسبة المئوية؟
     
    افرض بأن مدة الثانية الواحدة كامله منذ بدايتها الى نهاتيها تكون بنسبة 100%

    اذا سوف نضع Animation يبدا مباشرة وتكون بنسبة 0% الى نسبة 50%
    يتم التحريك بصورة عامودية ومن النسبة 50% الى 100%
    يتم التحريك بصورة افقيه
     
    صورة توضيحية :
     

     
    هذا هو فكرة النوع هذا باختصار
     
    وقت واحد ، يتم تجزئيه بالنسبة وعمل تحريك مختلف
    لذا ذكرت في بداية الموضوع بأن هذا النوع يعتمد على التسلسل الزمني
    فهو يعمل كالخط الزمني !
     
    أخيرا يتوجب عليك ان تعلم بأن :

    النسبة لن تكون 100%
    ولكن سوف تكون 1 ، بمعنى نقوم بالقسمة على 100
    اذا اردنا نسبة 25% سوف تكون 0.25
    اذا اردنا نسبة 50 سوف تكون 0.5
    وهكذا
     
    اذا استوعبت وفهمت الكلام السابق ، تكون فهمت فكرة هذا النوع !
     
    اسم النوع الجديد هو UIView.animateKeyframes
     
    الان لجعلك تلاحظ الفرق الجوهري بين استخدام UIView.animate

    وبين استخدام UIView.animateKeyframes
     
    سوف اعطيك نفس المثال مرتين
     
    الاولى بالطريقة التقليدية باستخدام UIView.animate
     
    الثانية باستخدام UIView.animateKeyframes
     
    في هذا الدرس سوف نعود الى استخدام المربع الأزرق التي استخدمناه في الجزء الاول والثاني من هذه السلسلة
     
    ما الذي سوف نقوم به ؟

    سوف نقوم بتحريك المربع الأزرق بشكل مستطيل
    يبدا من الزاوية السفلية اليسرى
    ويتحرك الى الزاوية السفلية اليمنى ومن ثم يتحك الى الزاوية العلوية اليمنى
    ومن ثم يتحرك الى الزاوية العلوية اليسرى
    واخيراً يعود الى الزاوية السفلية اليسرى
     
    قبل اذهب الى الـ Storyboard واجعله بالشكل التالي :
     



    ومن ثم اضيفهم الى ملف الاكواد
     
    الان سوف نبدأ الدرس
     
    باستخدام الطريقة القديمة

    الكود سوف يصبح بالشكل التالي :

     
     @IBAction func Animation(_ sender: Any) { UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: 0)         }) { (true) in             UIView.animate(withDuration: 0.75, animations: {                 self.square.transform = CGAffineTransform(translationX: 225, y: -500)                         }, completion: { (true) in                          UIView.animate(withDuration: 0.75, animations: {                 self.square.transform = CGAffineTransform(translationX: -0, y: -500)                }, completion: { (true) in                       UIView.animate(withDuration: 0.75, animations: {                 self.square.transform = .identity                }, completion: { (true) in                })                })             })         }     }  
    صورة توضيحية بالنتيجة :
     

     
    هل لاحظت المشكلة في الكود ؟

    كثرة الاقواس وتداخلها يصعب من عملية تعديل الكود
    ماذا اذا اردت حذف احدى الـ Animation ؟

    ستواجه صعوبة في إيجاد كود الـ Animation
    المراد حذفه
     
    ماذا اذا اردت تعديل الوقت الكلي ؟
    سوف يتوجب عليك تعديل كود كل Animation على حده
    في المثال السابق استخدمنا وقت 0.75
     
    لماذا  ؟
    لنصل الى توقيت كلي 3.0
    فكل Animation سوف يستغرق 0.75 ثانية فقط
     
    الان للتنقل الى النوع الجديد

     
        @IBAction func Animation(_ sender: Any) {                  UIView.animateKeyframes(withDuration: 3.0, delay: 0.0, options: [], animations: {                          UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {                 self.square.transform = CGAffineTransform(translationX: 225, y: 0)             })                          UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5, animations: {                 self.square.transform = CGAffineTransform(translationX: 225, y: -500)             })                          UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.75, animations: {                 self.square.transform = CGAffineTransform(translationX: -0, y: -500)             })                          UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 1.0, animations: {                self.square.transform = .identity                          })                                   }, completion: nil)                                }  
     
    النتيجة الذي وصلنا اليها :
     

     
     
    ما الجديد ؟

    أولا اضفنا animateKeyframes
    ومن ثم اضفنا التوقيت الكلي وهو 3 ثواني
    ولم نضيف تأخير لأننا نريد ان يبدا فوراُ فجعلنا القيمة 0.0
    في الـ options لم نضيف شيء لذا وضعنا اقواس اريه فاضية
    مع العلم أنواع الـ options مختلفة تماما عن الانواع السابقة الموجودة في UIView.animate !
    (لن نتطرق لها في هذا الموضوع)


    والان لاحظ كل Animation ينضاف لحاله بداخل اقواس animateKeyframes
     
    ويطلق عليه UIView.addKeyframe
     
    يتكون من امرين وهي withRelativeStartTime
    والتي تعبر بداية الـ Animation
    كما ذكرنا في بداية الموضوع تعتبر نسبة
    في اول Animation وضعنا قيمة 0.0
    ليبدا في البداية
     
    بالنسبة الى relativeDuration
    يعتبر وقت انتهاء الـ Animation
    في اول Animation وضعنا نسبة 0.25
    والتي تساوي نسبة 25%

    لجعل الامر اكثر وضوحاً

    اذا نظرنا في animateKeyframes
    وضعنا وقت كلي هو 3 ثواني
     
    فاذا قمنا بعملية حسابية بسيطة
     
    3 ضرب 25 قسمة 100 النتيجة سوف تكون 0.75 ثانية
     
    اذاً اول Animation سوف يستغرق 0.75 ثانية فقط
     
    والامر ينطبق مع البقية
     
    في الـ Animation الثاني وضعنا قيمة withRelativeStartTime بـ 0.25
    وقيمة الـ relativeDuration بـ 0.50
     
    اذا نقصنا القيمتين من بعض سوف تكون النسبة 0.25
    وذا اتبعنا نفس الحسبة السابقة سوف تكون النتيجة 0.75 ثانية
     
    من الشرح السابق اتضح لنا فائدة الـ UIView.animateKeyframes
     
    بما يعني :
    اذا اردنا تغير الوقت الكلي فقط نقوم بتغير القيمة الموجودة في UIView.animateKeyframes اذا اردنا تغير مدة Animation معين ، نقوم بتغير نسبة الـ Animation فقط  
    والاهم من ذا كله الكود اصبح نضيف وسهل القراءة والتعديل !
     
     

    مستوى المقال: محترف

    بواسطه X901 , في

  • كيف تحصل على HTTPS: إعداد  (SSL Certificate) في خطوات
     
    شهادة( SSL ( Secure Sockets Layer هي ملف نصي يحتوي على بيانات مشفرة تقوم بتثبيتها على الخادم بحيث يمكنك تأمين / تشفير الاتصالات الحساسة بين موقعك وعملائك.
    هنا الخطوات اللازمة لإعداد SSL Certificate لموقعك الالكتروني دون الحاجة لشراءها من الخادم 
     
    الخطوة الأوليّة هي الاتصال ب SSH الخاصة بك عن طريق السيرفر
    بسم الله نبدأ:
    الخطوة الأولى:
    --------------------------------------------------------
    لإعطاء الأوامر كمشرف Super User
    Sudo su الخطوة الثانية:
    --------------------------------------------------------
    في الخطوة الثانية نقوم بتحديث  مجموعة ال APT قبل اعطاء امر اضافة الSSL Certificate كالتالي:
    Apt-get update Apt-get upgrade الخطوة الثالثة:
    --------------------------------------------------------
    نقوم بعمل ريستارت ل Apatche 
    sudo service apache2 restart  
    الخطوة الرابعة:
    --------------------------------------------------------
    الموقع التالي مصدر رائع يمدك بالأكواد اللازمه والتي قمت بتتبعها في الخطوات القادمة
    https://certbot.eff.org/#debianjessie-apache
    ٤- أ) أولا سيكون عليك إتباع التعليمات هنا لتمكين the Jessie backports repo (هذا الملف يسمح بتحميل ssl) ، إذا لم تكن قد فعلت ذلك. قم بما يلي:
    sudo apt-get install python-certbot-apache -t jessie-backports ٤- ب) The modal window does not pop up. Everything happens in the SSH window. See the attached picture
    certbot --apache


     
    --------------------------------------------------------
    الخطوة الخامسة:
    --------------------------------------------------------
    فتح الملف defult-ssl.conf لنقوم بإضافة نص كما في الخطوة السادسة
    sudo nano /etc/apache2/sites-enabled/default-ssl.conf الخطوة السادسة:
    --------------------------------------------------------
    اضف النص التالي في المنتصف بين (ServerAdmin…) و (DocumentRoot…) ثم قم بحفظ الملف
    Add the text below in between top line (ServerAdmin…) and bottom line (DocumentRoot…) in default-ssl-.conf
    Then exit and save file
     
    ServerAdmin webmaster@localhost
    <Directory /var/www/html/> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory>  DocumentRoot /var/www/html
     
    ثم لحفظ الملف ( ctrl+x)
     
    الخطوة التاسعه:
    --------------------------------------------------------
     نعمل اعادة تشغيل ل Apatche 
     
    sudo service apache2 restart  
     
    --------------------------------------------------------
    قمت برفع الخطوات على ملف PDF لمن يرغب بحفظها
    SSLInstallationReview.pdf
    تحياتي
    لمياء الشمري
     
    مستوى المقال: مبتدئ

    بواسطه لمياء الشمري , في

  • Ads Belongs To This website

    عالم البرمجة

    عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل