Merhaba bu haftaki konumuz Coordinator,

Büyük projelerde View Controllerımızı olabildiğince temiz tutmak gelecekte oluşacak yeni taleplere, güncellemelere hatta hatalara daha çabuk müdahale etmemizi kolaylaştıracaktır. Geçtiğimiz hafta MVVM konusunu anlatıp Controllerımızı View Model ile nasıl daha temiz hale getirdiğimizden bahsetmiştik, konunun devamı niteliğinde bu hafta View Controllerımızı navgation controller işindende nasıl temizleyeceğimizi navigasyon işlerini ilgili controllera yani navigation controllera aktarıp tekrar kullanımı yüksek kod yazımı nasıl elde edeceğimizi Coordinator patternini uygulayarak birlikte göreceğiz.

Problem:

Controller bir ekrana geçiş yapması gerektiğinde önce oluşturur, sonra modifiye eder daha sonrada gösterir. tüm bu iş yükü aşağıdaki göreceğiniz kodda oluşuyor.

if let vc = storyboard?.instantiateViewController(withIdentifier: "SomeVC") {
    navigationController?.pushViewController(vc, animated: true)
}

herhangi bir controllerda SomeVC ekranına gitmek için bu kodu yüzlerce belkide daha fazla yazmışızdır. Çakılı gömülü yada hard code dediğimiz bu şekil bi tanımla hiçte verimli olmadığı ortada şimdi işe koyulalım ve coordinator patterni ile controllerların nereden gelip nereye gideceği hakkında bir bilgiye sahip olmasına gerek olmadan özgürce navigasyon yapabilen bir yapı kuralım.

seperate

Başlarken

Coordinator ile navigasyon işlemlerini halletmek istediğimiz aşağıdaki ayarlamaları yapalım;

  • bir ios projesi oluşturun
  • storyboard dosyasını seçin ve projenin varsayılan initial view controller seçili boxını kaldırın
  • proje dosyasınızı xcode navigatorde seçin main storyboardda yazılı Main yazısını silin.

artık hazırız başlayabiliriz.

Storyboard Protokolü

Senaryomuz şöyle olacak örnek uygulamamızda, kullanıcı 3 ekran görebilmeli, login, register ve forgot password. Bu 3 ekrana ben Onboarding diyorum kullanıcının uygulamamızı ilk gördüğü andaki flow logici temsil ettiği için.

Onboarding adında bir swift dosyası oluşturalım ve aşağıdaki gibi tanımlayalım.

protocol Onboarding {
    static func instantiate() -> Self
}

extension Onboarding where Self:UIViewController {
    static func instantiate() -> Self {
        let fullName = NSStringFromClass(self)
        let className = fullName.components(separatedBy: ".")[1]
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        
        return storyboard.instantiateViewController(withIdentifier: className) as! Self
    }
}

Onboarding protokolünü yukarı bahsettiğim 3 ekrana uygulayacağız, bu protokolü istersek spesifik olarak ayrı ayrı storyboardlar için tanımlayabiliriz, örneğin kullanıcı bir fotoğraf yükleyecek profiline bu ayrı bir flow logic olduğu için upload.storyboard ile yükleme aşamalarını ayrı bir storyboardda tutabiliriz buradada Uploading adında bir protokolle yukarıdaki işlemi uygulayabiliriz.

Onboarding protokolünü açıklayalım, instantiate() adında bir metoda sahip bu metod;

let fullName = NSStringFromClass(self)

NSStringFromClass metodunu kullanarak sınıf adını uygulama adıyla birlikte alıyor ‘App.SınıfAdı’ devam eden satırda .dan sonraki alanı alıyoruz yani sınıf adını ayıklıyoruz, daha sonra sınıf adını storyboardda kimlik olarak kullanarak storyboarddan bir referans üretmiş oluyoruz.

Not: Storyboardda sınıf ismini idetifier olarakta aynen yazmayı unutmayın protokolün çalışabilmesi için önemli.

şimdi sırada coordinator tanımlamasında

Base Coordinator

BaseCoordinator adında bir swift dosyası oluşturalım ve aşağıdaki gibi tanımlayalım.

protocol BaseCoordinator {
    var childControllers: [BaseCoordinator] {get set}
    var navigationController: UINavigationController {get set}
}

BaseCoordinator protokolünü açıklamayı sonraya bırakıyorum kalan dosyalara ihtiyacım var çünkü açıklama yapabilmek için.

Home VC

HomeVC adında bir controller tanımlayın ve Main storyboarda gidip bir view controller bırakın ekrana sınıfını HomeVC olarak yazın idetifierıda aynı şekilde HomeVC olarak sınıfın adını yazın.

daha sonra aynı adımları uygulayarak

  • RegisterVC
  • LoginVC
  • ForgotPasswordVC

sınıfları aşağıdaki gibi tanımlayın, ve gerekli bağlantıları yapın

class HomeVC: UIViewController,Onboarding {  
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    @IBAction func gotoRegisterScreen(_ sender: Any) {

    }
    
    @IBAction func gotoLoginScreen(_ sender: Any) {
        

    }
}
class RegisterViewController: UIViewController,Onboarding {
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }

}
class LoginViewController: UIViewController,Onboarding {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    @IBAction func gotoForgotPassword(_ sender: Any) {

    }
    
}
class ForgotPasswordViewController: UIViewController,Onboarding {
    
    override func viewDidLoad() {
        
    }
}

App Coordinator

şimdi sıra geldi coordinatoru tanımlamaya, AppCoordinator adında bir swift dosyası oluşturalım ve aşağıdaki gibi tanımlayalım.

class AppCoordinator: BaseCoordinator {
    var childControllers = [BaseCoordinator]()
    var navigationController: UINavigationController
    
    init(navigationController:UINavigationController) {
        self.navigationController = navigationController
    }
}

yukarıdaki tanımlamayı init() metoduyla başlamak istiyorum, aldığı parametre ile BaseCoordinator protokolü aracılığıyla tanımlanan navigationControllera atanıyor böylelikle gösterilecek view controllerın navigasyonu için gerekli tanımlamayı sağlamış oluyor.

Küçük bir güncelleme

şimdi homeVC ve Register controllerını güncellememiz gerekiyor, aşağıdaki kodları gerekli sınıflara yazalım;

HomeVC -> weak var coordinator:AppCoordinator? RegisterVC -> weak var registerCoordinator:AppCoordinator?

sırada login coordinatoru oluşturmakta.

Login Coordinator

LoginCoordinator adında bir swift dosyası oluşturalım ve aşağıdaki gibi tanımlayalım.

class LoginCoordinator: BaseCoordinator {
    var childControllers = [BaseCoordinator]()
    var navigationController: UINavigationController
    
    init(navigationController:UINavigationController) {
        self.navigationController = navigationController
    }
    
    func gotoLoginScreen() {
        let loginVC = LoginViewController.instantiate()
        loginVC.loginCoordinator = self
        navigationController.pushViewController(loginVC, animated: true)
    }
    
    func gotoForgotPasswordScreen() {
        let forgotPasswordVC = ForgotPasswordViewController.instantiate()
        forgotPasswordVC.coordinator = self
        navigationController.pushViewController(forgotPasswordVC, animated: true)
    }
    
}

tanımlamamızı yaptıktan sonra LoginVC ve ForgotPasswordVC’de aşağıdaki güncellemeleri yapmamız gerekiyor.

LoginVC -> weak var loginCoordinator:LoginCoordinator?
ForgotPasswordVC -> weak var coordinator:LoginCoordinator?

şimdi LoginCoordinatorde yazmış olduğumuz kodu açıklama vakti;

init metodu AppCoordinatorde açıkladığımızla aynı tanımlamaya sahip farklı birşey yok, gotoLoginScreen() metodu içerisinde login view controllerından örnek oluşturuyoruz bu oluşturma Onboarding protokolü sayesinde sağlanıyor daha önce bahsettiğimiz gibi, loginVC.loginCoordinator = self dediğimizde burada tanımlamasını yapmış olduğumuz coordinatoru viewcontroller’a atamış oluyoruz böylelikle daha sonra göreceksiniz AppDelegate üzerinde bir değişiklik yapacağız oradaki işlemde AppCoordinator devrede olacak burada ise login coordinatoru devreye almış oluyoruz.

navigationController.pushViewController(loginVC, animated: true)

koduylada istenilen ekrana geçişi sağlamış oluyoruz, aynı tanımlama gotoForgotPasswordScreen() içinde geçerli.

App Coordinator güncellemesi

AppCoordinator sınıfımızı açıp aşağıdaki gibi güncelleyelim:

class AppCoordinator: BaseCoordinator {
    ...
    var loginCoordinator:LoginCoordinator?
	
    init(navigationController:UINavigationController) {
        loginCoordinator = LoginCoordinator(navigationController: navigationController)
        childControllers.append(loginCoordinator!)
        self.navigationController = navigationController
    }
    
    func homeVC() {
        let vc = HomeVC.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: false)
    }
    
    func goRegisterView() {
        let registerVC = RegisterViewController.instantiate()
        registerVC.registerCoordinator = self
        navigationController.pushViewController(registerVC, animated: true)
    }
    
}

yukarıdaki kodda, init metodumuz güncellenmiş oldu, login coordinatoru burada referans aldık ve init içerisinde tanımlamasını yaptık daha sonra AppCoordinatore childControllers olarak atadık bunun nedeni, AppCoordinator şuanda uygulamamızın ilk açılan olan ekranını yönetiyor burada iki buton vardı birisi login ekranına gitmemizi sağlayan butondu burada childControllersa eklediğimizde login coordinatorumuzu HomeVC de güncelleme yapacağız birazdan ve buradadaki atama işlemini daha net anlayacağız.

homeVC() ve goRegisterView() metodları Login Coordinatordeki goto metodlarıyla aynı tanımlamaya sahip, şimdi HomeVC controllerımıza geçelim ve aşağıdaki gibi bir güncelleme yapalım.

HomeVC

@IBAction func gotoRegisterScreen(_ sender: Any) {
        coordinator?.goRegisterView()
    }
    
    @IBAction func gotoLoginScreen(_ sender: Any) {
        
        for coord in (coordinator?.childControllers)! {
            if let loginCoordinator = coord as? LoginCoordinator {
                loginCoordinator.gotoLoginScreen()
            }
        }

    }

gotoLoginScreen() metodunu incelediğimizde

childControllers’ı burada döngüyle geziyoruz ve LoginCoordinatore cast edilebiliyorsa loginScreen’e geç diyerek login coordinatorde tanımlı gotoLoginScreen() metodunu tetiklemiş oluyoruz.

AppDelegate

yapmamız gereken herşeyi yaptık sıra geldi uygulamayı derlemeden önce son bir değişiklik yapmaya, yukarıda storyboardu main interfaceden silmiştik şimdi coordinator yardımıyla homeVC ekranımızı kullanıcıya gösterelim ve uygulamamızı kullanalım.

AppDelegate sınıfımızı açalım ve aşağıdaki güncellemeyi yapalım:

var coordintor: AppCoordinator?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let navigationController = UINavigationController()
        
        coordintor = AppCoordinator(navigationController: navigationController)
        coordintor?.homeVC()
        
        window = UIWindow(frame: UIScreen.main.bounds)
        
        window?.rootViewController = navigationController
        window?.makeKeyAndVisible()
        return true
    }

bir navigationController oluşturduk sebebi viewımızın navigasyon hiyerarşisinde olması için, daha sonra coordinatorumuzden HomeVC() metodunu çağırarak gösterime hazır olmasını sağladık ve window için configurasyonu yapıp aktif hale getirdik.

seperate

Coordinator kaynak kodu:

Coordinator-Learn Github