Swift — Twitter Hesap Değiştirme Menüsü Nasıl Yapılır?

Herkese merhaba, uzun zamandır iOS üzerine proje geliştiriyorum. Sık kullandığımız uygulamaların yaptığı hareketlerin aynısını yapmaya çalışıyorum.

Bu hareketleri yaparken, yapmak isteyenlere yol göstermek için medium’da vakit buldukça yazmayı düşünüyorum.

İlk olarak Twitter’daki hesap değiştir menüsü ile başlamak istiyorum. Yapmak istediğimiz kısaca şu:


Neler lazım?

Öncellikle, bir tane UIViewController bir tane de UITableViewController’e ihtiyaçımız var. UIViewController’e bir tane buton koyacağız. Butona tıklandığında UITableViewController Twitter’daki gibi açılacak.


Yapılacak Hareketler

  1. Butona tıklandığında, UIViewController küçülecek ve UITableViewController gelecek,
  2. Bu durumdayken, eğer UITableViewController dışında bir yere tıklanırsa, menü kapanacak,
  3. Aynı şekilde aşağı kaydırılırsa yine menü kapanacak, yukarı kaydırılırsa UITableViewController ekranı komple kaplayacak,
  4. Herhangi bi cell’e tıklanınca UITableViewController kapanacak.

Başlıyoruz!

Info.plist’e View controller-based status bar appearance ekleyip, değerini NO yapalım.


ViewController.swift

Status bar’ı beyaz yapmak için:

UIApplication.sharedApplication().statusBarStyle = .LightContent

NavigationBar’ı gizlemek için:

self.navigationController?.setNavigationBarHidden(true, animated: false)

Bir tane buton ekleyelim, bu butona tıklandığında openTableView() fonksiyonu çalıştıralım.

let button = UIButton()
button.frame = CGRectMake(0, 150, self.view.bounds.width, 20)
button.setTitle("Hesaplar", forState: .Normal)
button.addTarget(self, action: #selector(ViewController.openTableView), forControlEvents: .TouchUpInside)
self.view.addSubview(button)

openTableView()

let tableViewController = UINavigationController(rootViewController: TableViewController())
self.navigationController?.presentViewController(tableViewController, animated: true, completion: nil)

Burada butona tıkladığında TableViewController’ı açmasını söyledik. Şuan ki durum:


TableViewController.swift

Sade bir TableViewController oluşturalım. 4 tane kayıt dönsün.


Bu aşamadan sonra, presentViewController ve dismissViewControllerAnimated için nasıl bir animasyon oluşturacağımızı belirleyeceğiz.

presentViewController için PresentAnimationController.swift, dismissViewControllerAnimated için DismissAnimationController.swift isminde dosya oluşturalım.


PresentAnimationController.swift

transitionDuration fonksiyonun altına animateTransition fonksiyonu ekleyelim.

fromVC burada ViewController, toVC ise UITableViewController oluyor.

let finalFrame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
snapshot = fromVC.view.snapshotViewAfterScreenUpdates(false)
snapshot.frame = finalFrame
containerView.addSubview(snapshot)

fromVC’nin o anki ekran görüntüsünü aldık. Ve bu ekran görüntüsünü containerView’e ekledik.

toVC.view.frame = CGRectMake(0, finalFrame.height, finalFrame.width, finalFrame.height)
containerView.addSubview(toVC.view)

toVC yani UITableViewController’in y eksenini finalFrame’nin yüksekliği ile eşitledik. Yani:

fromVC.view.hidden = true

fromVC’nin ekran görüntüsünü(snapshot) alıp, containerView’e ekledik. O yüzden fromVC’ye ihtiyaçımız yok, gizleyebiliriz.

let duration = transitionDuration(transitionContext)

duration değişkenine transitionDuration’daki süreyi eşitledik. (Yani 0.75)

UIView.animateWithDuration(duration-0.3) {
   self.snapshot.layer.opacity = 0.75 
   self.snapshot.layer.transform = CATransform3DMakeScale(0.94, 0.94, 1)
}

Burada ki animasyon; snapshot’un görünürlüğünü 0.75 yapıyor ve snapshot’u 0.94 oranına scale ediyor. Yani küçültüyor.

UIView.animateWithDuration(duration, delay: 0.2, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: [], animations: {
   self.toVC.view.frame = CGRectMake(0, finalFrame.height-self.modalY, finalFrame.width, finalFrame.height)
}) { (finished) in
 fromVC.view.hidden = false
 transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}

Buradaki animasyon; toVC yani UITableViewController’i modalY’nin değeri kadar yukarı çekiyor.

ModalY’nin değerini sonra vereceğiz

Şimdi bu “Present” animasyonunu nasıl kullanacağız?

private let presentAnimationController = PresentAnimationController()

ViewController.swift, viewDidLoad fonksiyonunun üzerine üstteki kodu ekliyoruz.

let tableViewController = UINavigationController(rootViewController: TableViewController())
tableViewController.transitioningDelegate = self
presentAnimationController.wireToViewController(tableViewController)
self.navigationController?.presentViewController(tableViewController, animated: true, completion: nil)

ViewController.swift, openTableView() fonksiyonunu üstteki gibi düzenliyoruz.

extension ViewController: UIViewControllerTransitioningDelegate {
  func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    presentAnimationController.modalY = (4*44)+44
    return presentAnimationController
  }
}

ViewController.swift’in en altına üstteki kodu ekliyoruz. Burada modalY’ye bir değer veriyoruz. Bu değer 4 tane kayıt’ın yüksekliği + navigationBar’ın yükseliğidir.

Şimdi şöyle diyebilirsiniz, bu kişinin hep 4 hesabı mı olacak? :) Bu durumda hesap sayısını bir yerde tutuyor olmalısınız. Bu durumda modalY’yi şu şekilde düzenleyebilirsiniz.
modalY = (hesapSayisi*44)+44
Burada bir konu daha var. Hesap sayısı ne kadar artarsa toVC’ o kadar üstte çıkacak. O yüzden maksimum hesap sayısı 4 olursa daha iyi olur. Şu şekilde:
modalY = ((hesapSayisi > 4 ? 4 : hesapSayisi)*44)+44

Nerede kaldık?


DismissAnimationController.swift

PresentAnimationController’in tam tersini yapıyoruz. fromVC burada UITableViewController, toVC ise ViewController oluyor. Animasyonlarda aynı şekilde tam tersi.


Şimdi bu “Dismiss” animasyonunu nasıl kullanacağız?

private let dismissAnimationController = DismissAnimationController()

ViewController.swift, viewDidLoad fonksiyonunun üzerine üstteki kodu ekliyoruz.

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  dismissAnimationController.snapshot = presentAnimationController.snapshot
  return dismissAnimationController
}

ViewController.swift, animationControllerForPresentedController fonksiyonun altına üstteki kodu ekliyoruz.

Sonuç?

UITableViewController’da herhangi bir cell’e tıklandığında dismissAnimationController çalışacaktır.

self.dismissViewControllerAnimated(true, completion: nil)

Daha da abartalım…

Üstteki boş alana tıklanınca ve aşağı kaydırınca kapansın. Üstte kaydırınca tam ekran olsun.


Boş alana tıklanırsa?

Bunu yapabilmek için snapshot’a UITapGestureRecognizer ekleyeceğiz.

PresentAnimationController.swift, animateTransition fonksiyonun içindeki containerView.addSubview(snapshot) kodunun altına aşağıdaki kodu ekleyelim.

let closeModalTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(PresentAnimationController.closeModal))
snapshot.addGestureRecognizer(closeModalTap)

Burada, snapshot’a tıklanırsa closeModal() fonksiyonunu çalıştır dedik.

En altta closeModal() fonksiyonunu ekleyelim.

func closeModal() {
  self.viewController.dismissViewControllerAnimated(true, completion: nil)
}

Aşağı veya Yukarı kaydırılırsa?

Bunu yapabilmek için containerView’a iki tane UISwipeGestureRecognizer ekleyeceğiz.

PresentAnimationController.swift, animateTransition fonksiyonun içindeki containerView.addSubview(toVC.view) kodunun altına aşağıdaki kodu ekleyelim.

let upSwipe = UISwipeGestureRecognizer(target: self, action: #selector(PresentAnimationController.handleSwipes(_:)))
let downSwipe = UISwipeGestureRecognizer(target: self, action: #selector(PresentAnimationController.handleSwipes(_:)))
upSwipe.direction = .Up
downSwipe.direction = .Down
containerView.addGestureRecognizer(upSwipe)
containerView.addGestureRecognizer(downSwipe)

En alta, handleSwipes fonksiyonunu ekleyelim.

UIApplication.sharedApplication().statusBarStyle = .Default

StatusBar’ı siyah yaptık.

self.toVC.view.frame = CGRectMake(0, 0, self.toVC.view.frame.width, self.toVC.view.frame.height)

toVC’nin y’sini 0 yapıyoruz. Yani şeklimiz bu oluyor:

if (gesture.direction == .Up)

Yukarı doğru kaydırılırsa,

UIApplication.sharedApplication().statusBarHidden = true
UIApplication.sharedApplication().statusBarHidden = false

Buna bir fix diyebiliriz. toVC’yi tam ekran yaptıktan sonra navigationBar statusBar’ın altına geçiyordu. Üstteki kodla statusBar’ı gizliyip tekrar gösteriyoruz. Bir sorunumuz kalmıyor.

Sağdaki fix’li halidir
NSNotificationCenter.defaultCenter().postNotificationName("active", object: nil)

Burada bir active isminde notification yolluyoruz. Bu notification’u TableViewController’da kullanacağız.

if (gesture.direction == .Down) {
closeModal()
}

Eğer aşağı kaydırılırsa, closeModal() fonksiyonu çalıştırılacak. Yani UITableViewController kapacanak.


Swipe için…

TableViewController’de bir kaç değişiklik, birkaç satır kod eklememiz lazım.

viewDidLoad fonksiyonuna aşağıdaki kodu ekleyelim.

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(TableViewController.activeTableView), name: "active", object: nil)
self.tableView.scrollEnabled = false

Burada üstteki “active” notification’u yakalamak için kod yazıyoruz. Diyoruz ki “active” isminde notification olursa activeTableView() fonksiyonu çalıştır.

Ayrıca, tableView’in scroll edilmesin dedik. Bunun nedeni ise; scroll edilirse, üstteki Swipe Gesture çalışmıyor. Böyle yaparak Swipe Gesture’nin çalışmasını sağladık. UITableView tam ekran olunca Swipe Gesture’ye ihtiyaçımız olmayacak. O yüzden tekrar scroll’u aktif yapacağız. Bunun için activeTableView() fonsiyonu ekliyoruz.

func activeTableView() {
self.tableView.scrollEnabled = true
}

numberOfRowsInSection kısmındaki 4'ü 10 yapalım.

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return 10
}

Son olarak…

ViewController.swift, viewDidLoad içindeki aşağıdaki kodu silelim.

UIApplication.sharedApplication().statusBarStyle = .LightContent

ViewController.swift class’ının içine aşağıdaki kodu ekleyelim.

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
UIApplication.sharedApplication().statusBarStyle = .LightContent
}

Burada ViewController’e her geldiğinde statusBar’ı beyaz yap dedik.


Ve…


Son haline şuradan ulaşabilirsiniz.

Bu benim ilk yazımdır. Okuduğunuz için teşekkür ederim. Herhangi bir hatam, herhangi bir yazım yanlışı varsa kusura bakmayın :)