كيف تطور تطبيق iOS بشكل أسرع (Swift 4)
هذا المقال يحتوي على نصائح وطرق تساعدك على بناء تطبيقك بشكل أسرع وأسهل عند التعديل
بعد عملي كمطورة تطبيقات iOS لما يقارب السنتين، توصلت إلى عدة حلول لتسهيل وتنظيم الكود التي ستحافظ على الكثير من الوقت والجهد. هذا المقال سيغطي على أكثر المتطلبات استخداماً والمتواجدة تقريباً في كل تطبيق، مثل: تغيير الألوان واستخدام الخطوط، والتنقل بين الصفحات.
هذا المقال يتطلب معرفة مبدئية بأساسيات Swift.
استخدام الخطوط المخصصة:
عندما تكتب: label.font = UIFont(name: "foo", size: 12)!
في كل مرة تحتاج فيها إلى استخدام الخط، قد تواجه عدة مشاكل نتيجة للتكرار، على سبيل المثال: أخطاء إملائية، والكثير من الوقت والجهد لتعديل الكود في حالة تغيير الخط. كحل لهذه المشاكل، يمكنك استغلال ميزة الـ Extension.
Extension:
extension UIFont {
class func foo(size: CGFloat) -> UIFont {
return UIFont(name: "foo", size: size)!
}
}
مثال لطريقة الإستخدام:
label.font = UIFont.foo(size: 12)
استخدام الألوان:
بدلاً من تحديد لون معين باستخدام قيم الـ RGB في كل مره تحتاج لاستخدام هذا اللون، ولتجنب المشاكل التي قد تحدث نتيجة التكرار، يمكنك أيضاً الإستفادة من ميزة الـ Extensions وبناء Extension يحتوي على جميع قيم الألوان التي ترغب باستخدامها.
Extension:
extension UIColor {
static let foo = UIColor(red: 234/255, green: 232/255, blue: 232/255, alpha: 1)
}
مثال لطريقة الإستخدام:
label.color = UIColor.foo
أسماء الـ Notification Center:
وبنفس الطريقة، يمكنك إنشاء Extension يحمل جميع أسماء الـ Notification Center تجنباً للأخطاء الإملائية.
Extesnsion:
extension Notification.Name {
static let foo = Notification.Name("foo")
}
مثال لطريقة الإستخدام:
NotificationCenter.default.post(name: .foo, object: nil)
أسماء الـ View Controllers:
عند كتابة أوامر الإنتقال بين الواجهات، ستحتاج غالباً إلى اسم الـ View Controller المرتبط بالواجهة المقصودة. لتجنب الأخطاء الإملائية ولاختصار العديد من الأسطر البرمجية يمكنك أيضاً الإستفادة من ميزة الـ Extension.
Extesnsion:
extension UIStoryboard {
static var main: UIStoryboard {
return UIStoryboard(name: "Main", bundle: nil)
}
var foo: FooViewController {
guard let vc = UIStoryboard.main.instantiateViewController(withIdentifier: String(describing: FooViewController.self)) as? FooViewController else {
fatalError("FooViewController couldn't be found in Storyboard file")
}
return vc
}
}
مثال لطريقة الإستخدام:
self.window?rootViewController = UIStoryboard.main.foo
النصوص (Strings):
هناك العديد من الوظائف التي تعتمد على النصوص، والكثير منها ستحتاج إلى استخدامها عدة مرات في المشروع الواحد.
قمت بتجهيز بعض من الأوامر المفيدة التي يمكنك استخدامها والتي ستوفر عليك الكثير من الوقت والجهد، بالطبع يمكنك التعديل وإضافة أي أمر بما يتناسب مع احتياجك.
Extesnsion:
extension String {
// Open any valid URL
func openURL() {
if let url = URL(string: self), UIApplication.shared.canOpenURL(url) {
if #available(iOS 10, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
} else {
print("This is not a valid URL")
}
}
// Call a number
func call() {
let url = "telprompt://\(self)"
url.openURL()
}
// Encoding a URL
func encodeUrl() -> String? {
return self.addingPercentEncoding( withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
}
// Decoding a URL
func decodeUrl() -> String? {
return self.removingPercentEncoding
}
// Validate matching a regular expression
func isMatching(regex: String) -> Bool {
let test = NSPredicate(format:"SELF MATCHES %@", regex)
return test.evaluate(with: self)
}
}
مثال لطريقة الإستخدام:
let urlString = "https://3alam.pro"
urlString.openURL()
التنبيهات (Alert Controllers):
كل تطبيق غالباً يحتاج إلى ميزة التنبيهات بأشكالها المتعددة ولحاجات مختلفة. الـ Class التالي سيساعدك على إنشاء وعرض التنبيهات باستخدام أقل عدد ممكن من الأوامر.
Class:
class Alert {
// Alert with ok button
static func create(title:String, msg: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(action)
return alert
}
// Alert with custom button and cancel button
static func createWithAction(title:String, msg: String, actionTitle: String, callback: @escaping ()->()) -> UIAlertController {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: actionTitle, style: .default) { (action) in
callback()
}
alert.addAction(action)
let cancel = UIAlertAction(title: "cancel", style: .cancel, handler: nil)
alert.addAction(cancel)
return alert
}
}
مثال لطريقة الإستخدام:
let alert = Alert.createWithAction(title: "title", msg: "message", actionTitle: "confirm") {
print("confirm button tapped")
}
self.present(alert, animated: true, completion: nil)
Network Layer
قمت ببناء هيكل لـ Network Layer وبالاستعانة بالمكتبات المشهورة Alamofire و SwiftyJSON. يمكنك استخدامه كنقطة بداية لبناء الـ Network Layer الخاص بك.
فكرة الهيكل هو بناء Class بعد تثبيت المكتبات المطلوبة، مهمة هذا الـ Class معالجة جميع ماتحتاجه من أنواع API Requests. مثال: GET, POST, PUT, DELETE
Class:
class Service: NSObject {
static let reachabilityManager = NetworkReachabilityManager(host: "https://baseURL.com")!
// GET Services
static func getService(url: String, callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
Alamofire.request(url).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
static func getServiceWithAuth(url: String, callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
let header : HTTPHeaders = ["Authorization": "token"] // Customize it as needed
Alamofire.request(url, headers: header).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
// POST Services
static func postService(url: String, parameters: [String:Any], callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
static func postServiceWithAuth(url: String, parameters: [String:Any], callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
let header : HTTPHeaders = ["Authorization": "token"] // Customize it as needed
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: header).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
// PUT Services
static func putService(url: String, parameters: [String:Any], callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
Alamofire.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
static func putServiceWithAuth(url: String, parameters: [String:Any], callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
let header : HTTPHeaders = ["Authorization": "token"] // Customize it as needed
Alamofire.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: header).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
// DELETE Services
static func deleteService(url: String, parameters: [String:Any], callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
Alamofire.request(url, method: .delete, parameters: parameters, encoding: JSONEncoding.default).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
static func deleteServiceWithAuth(url: String, parameters: [String:Any], callback: @escaping (JSON?) -> ()) {
if !reachabilityManager.isReachable {
callback(nil)
} else {
let header : HTTPHeaders = ["Authorization": "token"] // Customize it as needed
Alamofire.request(url, method: .delete, parameters: parameters, encoding: JSONEncoding.default, headers: header).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
callback(nil)
}
}
}
}
}
مثال لطريقة الإستخدام:
في كل مره تحتاج فيها إلى استخدام API، يمكنك فقط استدعاء أحد ال funcations السابقة وتمرير القيم (Arguments) المطلوبة:
Service.getService(url: "https://baseURL.com/foo") { (response) in
print(response)
}
آمل أن يحقق هذا المقال الغاية المرجوة منه وأن تبدأ باستغلال ميزة الـ Extension و الـ Custom Classes لكود أكثر ترتيباً و "نظافة✨". يمكنك استخدام الأكواد السابقة وتعديلها بحسب حاجتك، أو حتى بناء أكوادك الخاصة 😉.
وفقني الله وإياكم لكل خير.
التعليقات (3)
مفيــد جــداً، شكراً لك يا نوف
موضوع ابداااااع
لكن عندي ملاحظات
بالنسبة لل network layer
استخدام مكتبة Alamofire
افضل من كتابت الكود يدوياً
----------
بالنسبة للالوان افضل طريقة تستخدمي
Color literal
فقط اكتبي الكلمة دي راح تطلع الاقتراحات
وتقدري تختاري اللون مباشرة
وحتى تقدري تستخدمي رقم الكود
شكراً لك باسل
الكود المستخدم كما ذكرت في المقال في الحقيقة يعتمد على Alamofire
بالنسبة للـ Color literal فعلاً إضافة جميلة.. شكراً لمساهمتك
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !