كيف تطور تطبيق iOS بشكل أسرع (Swift 4)

هذا المقال يحتوي على نصائح وطرق تساعدك على بناء تطبيقك بشكل أسرع وأسهل عند التعديل

نوف صالحمنذ 5 سنوات

بعد عملي كمطورة تطبيقات 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 لكود أكثر ترتيباً و "نظافة✨". يمكنك استخدام الأكواد السابقة وتعديلها بحسب حاجتك، أو حتى بناء أكوادك الخاصة 😉.

وفقني الله وإياكم لكل خير.

7
إعجاب
2788
مشاهدات
0
مشاركة
3
متابع
متميز
محتوى رهيب

التعليقات (3)

Rashid Farhan:

مفيــد جــداً، شكراً لك يا نوف

باسل بارقبه:

موضوع ابداااااع 

لكن عندي ملاحظات

 

بالنسبة لل network layer

استخدام مكتبة Alamofire

افضل من كتابت الكود يدوياً 

----------

بالنسبة للالوان افضل طريقة تستخدمي

Color literal 

فقط اكتبي الكلمة دي راح تطلع الاقتراحات 

وتقدري تختاري اللون مباشرة 

وحتى تقدري تستخدمي رقم الكود

 

نوف صالح:

شكراً لك باسل

الكود المستخدم كما ذكرت في المقال في الحقيقة يعتمد على Alamofire

بالنسبة للـ Color literal فعلاً إضافة جميلة.. شكراً لمساهمتك

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !