فرم‌ها در اپلیکیشن iOS دیوار

برای اعمال تغییرات جزئی در فرم‌های یک اپلیکیشن، بهترین راه حل چیست؟ انتشار نسخه جدید اپلیکیشن برای تغییراتی مثل جابجایی سطرها یا تغییر تایتل یک سطر، راهکار جالبی به نظر نمی‌رسد. به خصوص تا زمانی که کاربران اپلیکیشن را بروزرسانی نکنند، متوجه این تغییرات نمی‌شوند. ما برای حل این مشکل تصمیم گرفتیم فرم‌هایی با قابلیت پویا ایجاد کنیم.

منظور از فرم پویا، فرمی‌ است که بدون نیاز به بروزرسانی اپلیکیشن، سطرهای آن قابلیت تغییر داشته باشند.

در این نوشته، در مورد روند تکامل فرم‌های اپلیکیشن دیوار و نحوه استفاده از فرم‌های پویا توضیح خواهیم داد.

فرم‌ها در روزهای اول

حتما برای شما پیش آمده که بخواهید در اپلیکیشن‌تان از کاربر اطلاعاتی را بگیرید. احتمالا اولین کاری که انجام می‌دهید، یک صفحه با چند سطر طراحی می‌کنید و در هر سطر کاربر یکی از آن اطلاعات را تکمیل نموده و در نهایت با زدن دکمه ثبت در انتهای صفحه، کل اطلاعات سمت سرور منتقل می‌شود. به احتمال زیاد برای انتقال به سرور نیز یک مدلی دارید که مقادیر سطرها را با کلیدهای خاص، آماده انتقال به سرور می‌کنند . تا اینجا کار شما با این فرم تمام شده است.

اگر یک فرم دیگر بخواهید اضافه بکنید چه؟ ممکن است باز یک صفحه دیگر طراحی کنید. احتمالا این صفحه‌ها سطرهای مشترکی با فرم قبلی شما دارند که می‌توانید از آن‌ها مجدد استفاده کنید. باقی ماجرا مانند گذشته است. با یک مدل اطلاعات فرم را آماده ارسال به سرور می‌کنید و با زدن دکمه ثبت، اطلاعات را ارسال می‌کنید.

حال اگر بخواهید از فرم اول یک سطر کم یا زیاد کنید یا جای دو سطر را با هم عوض کنید چه کار باید بکنید؟ اگر تعداد فرم‌ها زیاد شود چه؟

دیوار در روزهای اول تشکیل خود مسیر مشابهی را طی کرد. در روزهای اول که تعداد فرم‌های دیوار محدود بود، همین رویکرد در نظر گرفته شد. در همان ابتدای اپلیکیشن یک متا دیتا دریافت می‌شد که اطلاعات شهرها، لیست دسته بندی‌ها، واحدهای قیمت و … در آن وجود داشت. برای ایجاد هر فرم، صفحه‌ای طراحی می شد و اگر سطری از صفحه به لیست دسته بندی‌ها نیاز داشت، از همان متای اول اپلیکیشن استفاده می‌کرد. تعامل با سرور هم از طریق جیسان انجام می‌شد.

به مرور و با افزایش نیازمندی‌های دیوار، این رویکرد جوابگوی نیاز نبود. دلیل اول این بود که تعداد فرم‌ها زیاد شده بود و طراحی و نگهداری تعداد زیادی از فرم‌ها با این روش زمانبر می‌شد. دلیل دوم این بود که برای هر تغییری در فرم‌هایی که به این صورت پیاده شده بودند، باید متا را تغییر می‌دادیم. به این معنا که اگر می‌خواستیم جای دو سطر از یک فرم جابجا شوند یا یک سطر حذف یا اضافه شود، یا حتی اگر می‌خواستیم لیست محدودی از دسته بندی‌ها را نمایش دهیم، نیازمند این بودیم که این تغییرات را در متا اعمال کنیم. نگهداری چنین فایلی بعد از مدتی بسیار سخت می‌شد.

به مثال بالا توجه کنید. در مثال بالا می‌خواهیم تغییرات زیر را ایجاد کنیم:

۱. سطر مربوط به متراژ جابجا شود.

۲. سطرهای سال ساخت و طبقه اضافه شوند.

۳. عنوان مربوط به اجاره تغییر کند.

اگر فرم‌های ما پویایی لازم را نداشته باشند، مجبوریم برای این قبیل تغییرات، نسخه جدیدی از اپلیکیشن را منتشر کنیم. انتشار نسخه جدید برای تغییراتی مثل جابجایی سطرها یا تغییر عنوان یک سطر عمل جالبی نیست. به خصوص تا زمانی که کاربران اپلیکیشن را بروزرسانی نکنند، متوجه این تغییرات نمی‌شوند. راهکار این روش چیست؟

فرم‌هایی با قابلیت پویا ایجاد کنیم.

فرم‌های پویا

با توجه به دلایلی که قبلا گفته شد، نیاز داشتیم که یک فرم پویا داشته باشیم. منظور از فرم پویا، فرمی‌ است که بدون نیاز به بروزرسانی اپلیکیشن، سطرهای آن قابلیت تغییر داشته باشند. برای چنین قابلیتی، نیاز داریم سطرها و ترتیب آن‌ها توسط سرور تعیین شوند تا هر وقت نیاز به تغییر بود، این تغییر انجام شود. در واقع، اپلیکیشن فقط می‌داند که دارد یک فرم را نمایش می دهد. اینکه درون این فرم چه سطرهایی وجود دارد، بی‌خبر است. برای اینکه بتوانیم چنین کاری انجام دهیم، نیاز داریم سطرهای مشخصی داشته باشیم، که هر فرم از چیدمان این سطرها ایجاد شود. همینطور نیازمند الگویی برای ارتباط با سرور هستیم که فرم‌ها را بر اساس آن ایجاد کنیم و دیتای پر شده‌ی مربوط به هر فرم را به سرور انتقال دهیم (دقت کنید که دیگر از کلیدهای ارسال دیتا بی خبریم).

تعریف ویجت

ویجت در فرهنگ دیوار، زبان مشترکی است که برای کامپوننت‌ها به کار برده می‌شود. این ویجت‌ها بین طراحان رابط کاربری، توسعه‌دهندگان بکند، موبایل و … معنای یکسانی دارند و هر گاه در مورد یک ویجت صحبت می‌شود، همه می‌دانند در مورد چه چیزی صحبت می‌کنیم. ویجت‌ها وضعیت‌های مختلفی دارند که در طراحی دیده می‌شوند. برای مثال یک ویجت می‌تواند حالت فعال و غیر فعال داشته باشد.

تصویر بالا ویجت مربوط به اطلاعات نمایش را نشان می‌دهد. در سمت چپ، وضعیت ویجت در حالت نمایش متن و در سمت راست، وضعیت ویجت در حالت نمایش لینک قرار دارد.

همانطور که گفته شد، در طراحی فرم‌های پویا نیازمند سطرهای مشخصی هستیم. ویجت‌ها سطرهای فرم ما را تشکیل می‌دهند. به این صورت که هر فرم، از یک لیست ویجت می‌تواند تشکیل شود که هر کدام از آن‌ها هم وضعیت مشخصی دارند. برای مثال فرم زیر می‌تواند از لیستی از ویجت‌های مربوط به انتخاب دسته بندی، انتخاب محدوده، ویجت تکست فیلد و ویجت مربوط به انتخاب بین چند گزینه تشکیل شود.

دقت کنید که برای اضافه کردن سال ساخت و طبقه به فرم زیر، کافیست برای آن‌ها نیز یک ویجت به لیست اضافه کنیم. برای تغییر موقعیت متراژ جای آن را در لیست تغییر دهیم. یا برای تغییر تایتل مربوط به اجاره، تنها متن مربوط به تایتل ویجت را تغییر می‌دهیم. در این حالت، تنها در یک صورت نیازمند بروزرسانی اپلیکیشن هستیم، زمانی که بخواهیم ویجت جدیدی به مجموعه ویجت‌های موجود اضافه کنیم یا اینکه وضعیت جدیدی را برای یک ویجت موجود در نظر بگیریم.

بهره گیری از جیسون اسکیما

در بخش قبلی در مورد ویجت‌ها، به عنوان سطرهای تشکیل دهنده فرم‌ها توضیح دادیم. برای اینکه بتوانیم از این سطرها به صورت پویا استفاده کنیم، نیاز داریم که سرور لیستی از ویجت‌ها را برای اپلیکیشن ارسال نماید. هر کدام از ویجت‌های این لیست فیچرهایی دارند که باید توسط سرور مشخص شود. برای مثال در ویجت مربوط به تکست فیلد احتمالا می‌خواهید ببینید کاربر چه نوع داده‌ای می‌تواند وارد کند. در تکست فیلدهای مربوط به قیمت و تعداد، احتمالا می‌خواهید کاربر فقط بتواند عدد وارد کند اما در مواردی که مربوط به توضیحات است می‌تواند هر مقداری وارد کند. همینطور سرور باید مقادیر مربوط به عنوان ویجت، مقدار پیش‌فرض و فیچرهای ظاهری ویجت را ارسال کند. به علاوه یک نیاز دیگر می‌تواند این باشد که در این لیست مواردی که پر کردن آن الزامیست مشخص شود. برای ارسال این اطلاعات از سمت سرور، نیاز داریم یک ساختاری ایجاد کنیم که در آن نوع ویجت، مقدار پیش فرض، موارد الزامی و… مشخص شود.

برای این ساختار باید در نظر بگیریم که تمام نیازمندی‌های فعلی و آینده ما را بتواند پاسخگو باشد. همینطور طراحی این ساختار اگر به درستی پیاده نشود می‌تواند باعث پیچیدگی آن شود. برای مثال فرض کنید می‌خواهیم موارد الزامی را مشخص کنیم. برای این کار دو روش زیر را می‌توانیم در نظر بگیریم:

  • اضافه کردن فیلد required به تمام ویجت‌ها
  • ایجاد لیست required و افزودن آیتم‌های الزامی به آن

کدام یک از این دو روش را انتخاب کنیم؟

برای ساختار ایجاد فرم، به جای طراحی یک ساختار جدید، از یک ساختار استاندارد استفاده می‌کنیم. این ساختار استاندارد جیسون اسکیما نام دارد. جیسون اسکیما الگوی از پیش تعیین شده‌ای دارد که می‌توان با استفاده از آن نوع داده‌ای مجاز، طول داده، موارد الزامی و … را در آن مشخص کرد. این الگو به ما اجازه می‌دهد فرم‌های خود را از روی آن ایجاد کنیم. همچنین این الگو می‌تواند اعتبار یک جیسون را بررسی کند که آیا معتبر است یا خیر. در زیر برخی از امکاناتی که جیسون اسکیما در اختیار ما می‌گذارد آورده شده است. برای اطلاعات بیشتر به سایت آن مراجعه کنید.

کلمه type نوع داده اسکیما را مشخص می‌کند . این نوع می‌تواند یکی از انواع زیر باشد:

  • string
  • number
  • object
  • array
  • boolean
  • null

در زیر مثالی از type آمده است:

در این حالت تنها اعداد به عنوان مقدار معتبر در نظر گرفته می‌شوند.

برای ایجاد محدوده می‌توان از کلمات کلیدی minimum و maximum استفاده کرد. مثال زیر بازه اعداد صفر تا صد را می‌پذیرد:

در مورد کار با رشته‌ها، معمولا طول رشته مد نظر است. برای این موضوع می‌توان از minLength و maxLength جیسون اسکیما بهره برد:

امکان دیگری که مفید است، تعریف regular expression است. جیسون اسکیما با کلمه کلیدی pattern می‌تواند الگوی ورودی را مشخص کند. مثال زیر تنها شماره‌های تلفن آمریکای شمالی را قبول می‌کند:

در مورد نوع آرایه امکانات زیر را داریم:

می‌توانیم برای آرایه نوع آیتم‌ها را مشخص کنیم. مثال زیر آرایه از نوع عدد را می‌پذیرد:

همچنین می‌توانیم برای تعداد اعضای آرایه مینیمم و ماکزیمم در نظر بگیریم:

با تعریف enum ورودی باید یکی از کیس‌های تعریف شده باشد:

در تایپ object کلمه کلیدی properties این امکان را به ما می‌دهد که یک آبجکت داشته باشیم که هر کلید در آن، نام property و مقدار آن، یک جیسون اسکیما مربوط به آن property باشد. برای مثال اسکیمای زیر مربوط به یک آدرس متشکل از شماره، نام خیابان و نوع آن است:

به صورت پیش فرض propertyهایی که تعریف می‌شوند، الزامی نیستند. برای اینکه موارد الزامی را مشخص کنیم، از کلمه کلیدی required استفاده می‌کنیم.

از آن‌ جا که مقدار هر کدام از propertyها یک اسکیما است، بنابراین با استفاده از نوع object می‌توانیم اسکیماهای پیچیده تری ایجاد کنیم.

یکی از فیچرهای پیشرفته جیسون اسکیما، ایجاد وابستگی است. در جیسون اسکیما می‌توانیم بین propertyها وابستگی ایجاد کنیم. این کار با کلمه کلیدی dependencies انجام می‌شود. دو نوع وابستگی در جیسون اسکیما داریم:

  • Property dependencies
  • Schema dependencies

در نوع اول در صورت حضور یک property، propertyهای وابسته هم باید حضور داشته باشند. مثال زیر مربوط به همین نوع از وابستگی است:

در مثال بالا حضور billing_address وابسته شده است به حضور credit_card. یعنی اگر credit_card مقدار داشت، billing_address هم باید مقدار داشته‌ باشد.

در نوع دوم وابستگی، می‌توانیم به جای ایجاد وابستگی بین دو property، یک اسکیما را وابسته به یک property کنیم. مثال بالا را با استفاده از نوع دوم وابستگی می‌توانیم به صورت زیر بنویسیم:

مواردی که در بالا نام برده شد، قابلیت بالایی در ایجاد فرم به ما می‌دهند. در بسیاری از فرم‌ها، می‌خواهیم نوع ورودی محدود به یک نوع داده‌ای باشد (مثلا کاربر فقط عدد وارد کند). یا می‌خواهیم یک بازه ورودی مجاز ایجاد کنیم. یا حتی در مواردی نیاز داریم اطلاعات ورودی کاربر را اعتبار سنجی کنیم. همه این موارد و موارد مشابه دیگر، با کلمه‌های کلیدی که در بالا نام برده شدند، قابل پیاده‌سازی هستند.

حال با توجه به اینکه سطرهای مشخصی برای فرم داریم و نحوه تعامل با سرور را هم مشخص کرده‌ایم، به سراغ پیاده‌سازی فرم‌ها می‌رویم.

پیاده‌سازی فرم‌های پویا

در پیاده‌سازی فرم‌ها، اپلیکیشن تنها چیزی که می‌داند این است که یک فرم دارد نمایش داده می‌شود و نسبت به اینکه این فرم از چه سطرهایی تشکیل شده است یا چه اعتبارسنجی‌هایی به صورت لوکال نیاز است روی آن انجام شود، بی خبر است. برای انجام چنین کاری نیاز است اطلاعات ساخت فرم از سمت سرور مشخص شود. جیسون اسکیما این قابلیت را دارد که بتوان از روی آن یک فرم ساخت. برای همین ما جیسون اسکیمایی تعریف می‌کنیم که با دریافت آن از سرور، فرم خود را بسازیم.

جیسون اسکیما برای توصیف اینکه یک نوع داده چگونه باید نمایش داده شود، محدودیت دارد. برای مثال اطلاعات مربوط به گذرواژه و آدرس هر دو از جنس string است اما فیچرهای سطرهای مربوط به نمایش این دو متفاوت است. برای همین از مفهوم دیگری به نام UISchema استفاده می‌کنیم. UISchema به ما این قابلیت را می‌دهد که فیچرهای ظاهری مربوط به فرم را مدیریت کنیم. در واقع UISchema یک جیسون است که مشخص می‌کند اطلاعات جیسون اسکیما را چگونه نمایش دهیم. UISchema از همان ساختار سلسله مراتبی فرم تبعیت می‌کند . مثال زیر، یک UISchema برای گذرواژه را نمایش می‌دهد:

برای درک بهتر اینکه چگونه UISchema نحوه نمایش را مشخص می‌کند، مثال زیر را در نظر بگیرید:

فرض کنیم یک فرم برای اطلاعات مربوط به یک ساختمان داریم و می‌خواهیم از کاربر در مورد داشتن یا نداشتن آسانسور اطلاعات بگیریم. برای این کار به جیسون اسکیما چنین چیزی اضافه می‌کنیم:

تصویر زیر دو نمایش متفاوت از این اسکیما را نشان می‌دهد:

جیسون UISchema ی مربوط به نمایش‌های بالا به ترتیب از راست به چپ به صورت زیر هستند. همان‌طور که مشخص است برای یک جیسون اسکیمای یکسان نمایش متفاوتی می‌توان ایجاد کرد.

مورد دیگری که در مورد فرم‌ها باید در نظر بگیریم، خطاهای مربوط به اعتبارسنجی است. معمولا در فرم‌ها نیاز داریم اعتبارسنجی‌هایی را در زمان پر کردن فرم انجام دهیم (قبل از ارسال اطلاعات برای سرور). یکی از مثال‌های این موضوع تعیین گذرواژه در زمان ثبت نام است که نباید کمتر از ۸ کاراکتر باشد و یا شامل حرف بزرگ باشد. فرم‌های پویا باید بتوانند این اعتبارسنجی‌ها را هم مدیریت کنند. برای این موضوع می‌توانیم از کلیدواژه‌های موجود در جیسون اسکیما مانند pattern, minLength, required و ... استفاده کنیم. در کنار اینها از errors برای متن مربوط به هر کدام از این کلید واژه‌ها استفاده می‌کنیم. جیسون اسکیمای زیر اعتبارسنجی مربوط به تعداد کاراکتر مینیمم و ماکزیمم، الزامی بودن توضیحات و خطای نوع داده را در بر دارد:

با مواردی که تا اینجا بررسی کردیم، اکنون می‌توانیم از روی یک جیسون اسکیما، فرم خود را بسازیم. مثال زیر یک جیسون اسکیما و فرم متناظرش را نشان می‌دهد:


کلید required در جیسون اسکیما لیست موارد الزامی را مشخص می‌کند . همچنین برای اینکه بتوانیم ترتیب سطرهای فرم را مشخص کنیم، پارامتر order را در UISchema تعریف کردیم.

برای فرم بالا اطلاعاتی که برای سرور ارسال می‌کنیم، جیسونی شبیه به زیر است:

فرم‌ها بسته به نیاز، می‌توانند تک مرحله‌ای یا چند مرحله‌ای باشند. برای اینکه بتوانیم از سمت سرور این موضوع را مشخص کنیم، از کلید "pages" استفاده می‌کنیم که در آن صفحه فعلی و تعداد کل صفحات را مشخص می‌کنیم:

اگر فرم ما تک مرحله‌ای باشد، با برابر بودن مقدار current و total، فرم با زدن دکمه تایید به انتهای آن می‌رسد. در غیر این صورت با زدن دکمه، وارد مرحله بعد می‌شود. برای تمام مراحل، از یک صفحه مشترک استفاده می‌کنیم. در مرحله اول فرم، اسکیمای آن را می‌گیریم و برای رفتن به مرحله بعدی، با استفاده از اسکیمای جدید فرم را ایجاد می‌کنیم. در تمام مراحل فرم، اندپوینت و مدل ریکوئست و ریسپانس ما یکسان است. در مرحله آخر که مقدار current و total یکسان است، فرم را می‌بندیم و اکشن متناسب را اجرا می‌کنیم (برای مثال باز کردن صفحه مدیریت پست).

مزیت

موارد زیر مهم‌ترین مزیت‌هایی است که این روش از ایجاد فرم دارد:

  • پویایی: همان‌طور که از ابتدا اشاره کردیم، مهم‌ترین مزیت این فرم پویایی آن است. به راحتی می‌توان بدون نیاز به بروزرسانی، سطرهای فرم را جابجا کرد، سطرهایی از آن را کم یا زیاد کرد، یا فرم را تک یا چند مرحله‌ای کرد.
  • سرعت توسعه: با استفاده از این روش سرعت توسعه به میزان بسیار زیادی افزایش خواهد یافت. از آن جا که فرم پایه از قبل طراحی شده است، برای ایجاد فرم جدید تنها نیاز است ریکوئست و ریسپانس فرم را مشخص کنیم.
  • مقیاس‌پذیری: با تعریف ویجت‌های جدید، به راحتی می‌توان نیازمندی‌های جدید مرتبط با فرم را پوشش داد.

محدودیت

این روش، در کنار مزیت‌هایی که دارد، محدودیت‌هایی نیز دارد که به آن اشاره می‌کنیم:

  • بکند: ایجاد چنین فرمی تنها یک تصمیم کلاینتی نیست، بلکه نیازمند توافق بین کلاینت و بکند است.
  • زمانبر بودن ایجاد فرم پایه: طراحی و ایجاد فرم پایه که تمام فرم‌ها از آن استفاده می‌کنند کمی زمانبر است. البته این فرم تنها یک بار پیاده می‌شود و در دفعات بعدی تنها استفاده می‌شود و سرعت توسعه بالا می‌رود.
  • قدرت مانور کمتر نسبت به فرم‌های معمولی: در پیاده‌سازی این روش، فرم ما نسبت به چیزی که نمایش می‌دهد ناآگاه است. برای همین ‌پیاده‌سازی‌های خاص منظور نسبت به فرم‌های معمولی در آن سخت‌تر است (برای مثال ارتباط پیچیده بین سطرها).

چالش

یکی از چالش‌هایی که در کار با جیسون اسکیما داریم، ساختار پیچیده و تو در توی آن است. بعضی زبان‌ها(مانند جاوا اسکریپت) لایبرری ای برای decode کردن جیسون اسکیما دارند که کار با آن را راحت می‌کند . در سمت آی او اس چنین لایبرری ای نداریم. برای همین مجبور بودیم decode کردن آن را خودمان انجام دهیم. به علت پیچیدگی جیسون آن، تعیین مدل مربوطه یک کار چالشی خواهد بود.

درباره نویسنده

من، حسین اسدی، از مرداد سال ۹۸ در تیم آی او اس دیوار فعالیت می‌کنم. در کنار توسعه و نگهداری فیچرهای جدید، منتورشیپ افراد تازه‌وارد را انجام ‌می‌دهم و در جذب افراد جدید هم به تیم کمک می‌کنم. در این مدت به بهبود فرایند منتورینگ نیز کمک کردم. کمک به منتورینگ و جذب افراد جدید به تیم، باعث شد تا درک بهتری از اهمیت فرهنگ سازمانی پیدا کنم.

فرهنگی که در آن آزادی عمل و امتحان کردن راه‌حل‌های جدید تشویق می‌شود، زمینه پیشرفت را به بهترین شکل فراهم می‌کند. برای مثال من اولین بار کار با جیسون اسکیما را در دیوار تجربه کردم و تا قبل از آن در موارد دیگر، از جیسون معمولی استفاده می‌کردم. استفاده از جیسون اسکیما تجربه جالب و نویی برای من بود؛ بنابراین سعی می‌کنم زمینه کسب تجربه‌های جدید را برای افراد تازه‌وارد به تیم هم فراهم کنم.

اگر مطالعه‌ی این مقاله برای شما مفید بوده و دوست دارید در محیطی کار کنید که امکان رشد و یادگیری برای شما فراهم است، به تیم ما در دیوار بپیوندید.