Pada artikel instalasi perlengkapan coding kotlin, penulis telah membahas sedikit mengenai bagaimana cara instalasi kotlin dengan sdkman. Oke, pada artikel ini, penulis akan membahas mengenai dasar - dasar dari bahasa pemrograman kotlin. Project yang akan kita buat adalah berbasis gradle, jika anda pengguna linux dan windows silahkan ikutin tutorial langsung dari websitenya disini. Untuk penggunakan OSX, anda dapat melakukan instalasi dengan bantuan Homebrew seperti berikut.

brew install gradle

Seperti biasa, penulis menggunakan IDE IntelliJ IDEA, anda dapat menggunakan versi community maupun versi ultimate karena dua - dua nya telah mendukung bahasa pemrograman kotlin.

Silahkan buka IDE anda, lalu untuk membuat sebuah project silahkan pilih menu create new project, lalu pilih gradle dan cek list di bagian kotlin seperti gambar berikut.

Screen Shot 2017-06-03 at 9.51.09 PM.png

Lalu pilih next, di bagian groupid silahkan isikan org.rizki.mufrizal.belajar.kotlin dan pada bagian artifact nya isikan dengan Belajar-Kotlin, lalu klik next hingga akan muncul project location, silahkan sesuaikan dengan folder project anda dan klik finish. Berikut adalah project yang berhasil digenerate oleh IntelliJ IDEA.

Screen Shot 2017-06-03 at 9.58.37 PM.png

Secara default, project belum memiliki package untuk class - class kotlin, silahkan akses project anda dengan menggunakan terminal, lalu jalankan perintah berikut.

mkdir -p src/main/kotlin/org/rizki/mufrizal/belajarKotlin
mkdir -p src/test/kotlin/org/rizki/mufrizal/belajarKotlin

Silahkan lihat kembali project anda yang ada di IntelliJ IDEA, maka akan terbentuk 2 sub directory dari src yaitu main untuk source code nya, sedangkan test untuk keperluan testing. Langkah selanjutnya silahkan buka file build.gradle lalu ubah menjadi seperti berikut.

group "org.rizki.mufrizal.belajar.kotlin"
version "1.0-SNAPSHOT"

buildscript {
    ext.kotlin_version = "1.1.2-4"

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.junit.platform:junit-platform-gradle-plugin:1.0.0-M4"
    }
}

apply plugin: "kotlin"
apply plugin: "org.junit.platform.gradle.plugin"

junitPlatform {
    filters {
        engines {
            include "spek"
        }
    }
}

repositories {
    mavenCentral()
    maven { url "http://dl.bintray.com/jetbrains/spek" }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"

    testCompile "com.winterbe:expekt:0.5.0"
    testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
    testCompile("org.jetbrains.spek:spek-api:1.1.2") {
        exclude group: "org.jetbrains.kotlin"
    }
    testRuntime("org.jetbrains.spek:spek-junit-platform-engine:1.1.2") {
        exclude group: "org.junit.platform"
        exclude group: "org.jetbrains.kotlin"
    }
}

Pada konfigurasi diatas, terdapat beberapa tambahan yaitu penulis menambahkan library test untuk kotlin yang disupport langsung dari jetbrains yaitu spekframework. Untuk fungsi assert, penulis menggunakan framework expekt.

Silahkan buat sebuah class App di dalam package org.rizki.mufrizal.belajarKotlin, silahkan sesuaikan dengan package anda, lalu masukkan codingan seperti berikut.

package org.rizki.mufrizal.belajarKotlin

import java.math.BigDecimal

/**
 * Created by rizkimufrizal on 6/3/17.
 */
class App {

    fun extensionMoney(extension: String, money: BigDecimal): String {
        return "$extension $money"
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val money = App().extensionMoney(extension = "Rp", money = BigDecimal(5000))
            println("saya mempunyai uang sebesar $money")
        }
    }
}

Berikut adalah beberapa penjelasan dari codingan diatas :

  • fun berfungsi untuk mendeklarasikan sebuah function / method pada kotlin. Jika anda tidak mendeklarasikan sebuah type data maka secara default method tersebut adalah berupa Unit atau void.
  • "$extension $money" adalah contoh dari string interpolation, sehingga yang memiliki tanda $ akan dieksekusi.
  • companion object sebagai static jika anda menggunakan bahasa pemrograman java.
  • @JvmStatic juga sebagai static, akan tetapi berbeda dengan companion object, dimana companion object hanya dapat diakses melalui instance, sedangkan jika menggunakan @JvmStatic maka anda dapat langsung memanggil suatu fungsi dari class nya langsung. Contoh penggunaan ini akan dibahas pada artikel selanjutnya.
  • val berfungsi untuk mendeklarasikan variabel yang bersifat immutable / jika object nya telah dibuat maka isi dari vaeriabel tersebut tidak dapat dirubah, sedangkan var kebalikan dari val yaitu bersifat mutable.
  • money adalah variabel dengan type data string. Mengapa type string ? karena return dari function extensionMoney adalah string. Tanpa mendeklarasikan type data, kotlin dapat dengan mudah menebak type data yang ada di dalam variabel money.

Untuk menjalankan codingan diatas, silahkan klik pada icon kotlin dibagian kiri, lalu pilih menu run seperti berikut.

Screen Shot 2017-06-03 at 11.30.48 PM.png

Oke, langkah selanjutnya kita akan melakukan test, silahkan buat sebuah file AppTest di dalam package test anda. Lalu masukkan codingan seperti berikut.

package org.rizki.mufrizal.belajarKotlin

import com.winterbe.expekt.should
import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.given
import org.jetbrains.spek.api.dsl.it
import org.jetbrains.spek.api.dsl.on
import java.math.BigDecimal

/**
 * Created by rizkimufrizal on 6/3/17.
 */
class AppTest : Spek({
    given("a extension money") {
        val app = App()
        on("set extension and money") {
            val money = BigDecimal(7000)
            val extensionMoney = app.extensionMoney("Rp", money)
            it("should return extension and money") {
                extensionMoney.should.equal("Rp $money")
            }
        }
    }
})

Untuk menjalankan test diatas, silahkan akses project anda dengan terminal lalu jalankan perintah berikut.

gradle test

Jika berhasil maka akan muncul output seperti berikut.

:compileKotlin UP-TO-DATE
:compileJava NO-SOURCE
:copyMainKotlinClasses UP-TO-DATE
:processResources NO-SOURCE
:classes UP-TO-DATE
:compileTestKotlin
Using kotlin incremental compilation
:compileTestJava NO-SOURCE
:copyTestKotlinClasses UP-TO-DATE
:processTestResources NO-SOURCE
:testClasses UP-TO-DATE
:junitPlatformTest

Test run finished after 5066 ms
[         4 containers found      ]
[         0 containers skipped    ]
[         4 containers started    ]
[         0 containers aborted    ]
[         4 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         0 tests failed          ]

:test SKIPPED

BUILD SUCCESSFUL

Total time: 9.272 secs

Sekian artikel mengenai setup project kotlin, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Apa Itu API Gateway ?

API Gateway adalah merupakan gerbang dari beberapa API, bertugas sebagai management API, merge beberapa API, authentication API dan lain - lain.

Berbicara mengenai API gateway maka tidak terlepas dengan pembahasan microservice. Microservice itu sendiri sebenarnya adalah kumpulan dari beberapa service atau kumpulan dari beberapa API. API ini biasanya berbentuk REST API menggunakan protokol http. Microservice sendiri sebenarnya berasal dari monolithic yang telah dipecah. Monolithic adalah aplikasi yang sehari - hari kita kembangkan, dimana semua module terdapat di dalam 1 aplikasi yang sangat besar.

Client biasanya hanya mengakses 1 URL REST API sedangkan microservice memiliki banyak service REST API, untuk mengatasi masalah ini maka kita gunakan API gateway. Terdapat banyak contoh API gateway yang ada di pasaran contohnya kong, tyk, API Umbrella dan lain - lain.

Bagaimana arsitektur yang pernah saya gunakan ?

Biasanya saya akan membuat microservice dengan stack spring framework :). Mengapa menggunakan spring framework ? ya alasan nya karena framework ini merupakan framework yang sudah memiliki banyak support, terutama dalam hal membuat REST API, authentication dengan OAuth2, session dan lain - lain. Berikut adalah arsitektur yang pernah saya gunakan untuk membuat microservice dan API gateway sederhana.

API Gateway.svg

Dari gambar diatas dapat dilihat bahwa aplikasi yang dibuat sangatlah besar meskipun hanya membuat service buku dan review buku. Pada gambar diatas terdapat :

  • database redis : ini kita gunakan untuk menyimpan session
  • database authentication : database untuk menyimpan data user, client credenetial dan lain - lain
  • database book : database untuk menyimpan data - data katalog buku
  • database review : database untuk menyimpan data - data review dari sebuah buku
  • authorization server : adalah authorization server yang berfungsi sebagai management otorisasi setiap service REST API. Authorization yang saya gunakan adalah berbasis OAuth2, dimana antar service wajib melakukan authentication terlebih dahulu sebelum mengakses service yang lain, contohnya adalah ketika service book ingin mengakses service review maka service book diharuskan login terlebih dahulu dengan menggunakan authentication OAuth2 grant type client credentials. Tidak hanya antar service, API gateway yang akan mengakses suatu service juga wajib melakukan authentication terlebih dahulu, akan tetapi setiap service memiliki client id, client secret dan hak akses masing - masing sehingga memiliki keterbatasan dalam mengakses masing - masing service. Bagi anda yang masih bingung dengan OAuth2, silahkan baca artikel Belajar OAuth2.
  • service book : REST API penyedia book
  • service review : REST API penyedia review book
  • API Gateway : yang bertugas untuk management API. Biasanya saya akan melakukan merge beberapa request API jika client membutuhkan banyak API. Contohnya jika kita membutuhkan API yang berisi data book beserta review nya, maka kita harus melakukan request sebanyak 2x yaitu melakukan request ke service book lalu request kembali ke service review. Biasanya model seperti ini akan membuat banyak latency di bagian client, nah untuk mengurangi hal tersebut maka kita cukup melakukan merge request API di bagian API gateway sehingga client hanya perlu melakukan request sekali ke API gateway, biasanya saya akan melakukan merge request API dengan menggunakan RxJava, contohnya adalah seperti berikut.
fun getBookDetails(bookId: Long): Observable<BookDetail> {
    return Observable.zip(
        catalogService.getBook(bookId),
        reviewService.getReviews(bookId),
        this::buildBookDetails
    )
}

private fun buildBookDetails(book: Book, reviews: Iterable<Review>): BookDetail {
    return BookDetail(
            id = book.id,
            title = book.title,
            description = book.description,
            reviews = reviews
    )
}

source code diatas ada disini

Mungkin ada yang bertanya - tanya, mengapa menggunakan redis ?, yups untuk menyimpan sebuah session yang berupa token, terdapat beberapa pendekatan yaitu kita dapat menyimpan token tersebut pada database atau dapat juga disimpan ke dalam redis. Jika menggunakan database maka database nya harus disharing antar service, ini mungkin pekerjaan yang lumayan merepotkan jika kita memiliki banyak service. Alternatif lain adalah menggunakan redis, dimana redis ini akan kita buat di 1 server sendiri.

Bagaimana Menyimpan Session Pada Redis ?

Nah lagi - lagi spring menyediakan kebutuhan session, di spring ada namanya spring-session, dimana kita dapat menyimpan session langsung ke redis. Lalu bagaimana dengan konfigurasi OAuth2 ? apakah token nya dapat disimpan ke redis ? jawaban nya adalah sangatlah mungkin, cara nya adalah dengan menggunakan token store seperti berikut.

@Bean
fun tokenStore(): TokenStore {
    return RedisTokenStore(jedisConnectionFactory)
}

@Throws(Exception::class)
override fun configure(authorizationServerEndpointsConfigurer: AuthorizationServerEndpointsConfigurer?) {
    authorizationServerEndpointsConfigurer
        ?.accessTokenConverter(jwtAccessTokenConverter())
        ?.tokenStore(tokenStore())
        ?.authenticationManager(authenticationManager)
}

source code diatas ada disini

Sehingga apabila anda berhasil melakukan authentikasi, maka token anda akan disimpan ke redis, ingat bahwa token ini memiliki masa nya tersendiri sehingga hanya dapat digunakan untuk waktu tertentu saja.

Bagaimana service lain seperti service book dan service review tau bahwa token yang dikirim oleh client adalah token hasil yang digenerate oleh authorization server ?

Yups lagi - lagi kita menggunakan fungsi spring-session untuk menghandle masalah seperti ini, di bagian service - service kita hanya perlu mendeklarasikan resource id dan juga konfigurasi OAuth2 seperti berikut.

@Bean
fun tokenStore(): TokenStore {
    return RedisTokenStore(jedisConnectionFactory)
}

override fun configure(resourceServerSecurityConfigurer: ResourceServerSecurityConfigurer?) {
    resourceServerSecurityConfigurer
        ?.tokenStore(tokenStore())
        ?.resourceId(RESOURCE_ID)
}

source code diatas ada disini

CATATAN : setiap service wajib memiliki 1 resource id, dimana resource id ini akan didaftarkan ke authorization server. Pencatatan resource id ini berfungsi sebagai memberikan batasan kepada client tertentu, sehingga client tertentu hanya boleh mengakses resource tertentu, contohnya adalah jika client seperti mobile atau web langsung mengakses service book maka tidak diperbolehkan dikarenakan client seperti mobile atau web tidak memiliki resource id dari service book, yang memiliki resource id service book adalah API gateway, sehingga client seperti mobile atau web hanya bisa mengakses melalui API gateway.

Untuk source code diatas dapat anda akses di Simple API Gateway. Sekian artikel mengenai Belajar API Gateway, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Akhir nya kita bertemu lagi di artikel bagian 4 untuk development iOS :). Artikel ini adalah artikel bagian terakhir untuk pembahasan mengenai development iOS. Pada artikel ini, kita hanya akan membahas bagaimana cara memutar music yang berasal dari API spotify, ingat bahwa lagu yang akan diputar adalah hanya preview lagu saja, jadi lagu yang diputar tidak nya lengkap :).

Oke, silahkan buka project anda dengan xcode, nah disini kita perlu terlebih dahulu memisahkan antara view dan controller. Silahkan buat sebuah group dengan nama controllers, silahkan buka group views, disana terdapat file TrackTableView silahkan buka file tersebut, lalu copy isi source code nya lalu hapus file tersebut, silahkan buat file kembali dengan nama TrackTableView di dalam group controller dan paste kan isinya. Oke setelah selesai memindahkan nya, tahap selanjutnya silahkan buat sebuah file swift kembali di dalam group controllers dengan file TrackPreviewViewController.

Langkah selanjutnya kita akan terlebih dahulu membuat view, silahkan buat sebuah story board di dalam group views dengan nama TrackPreview, lalu untuk desainnya silahkan lihat video berikut.

Nah tahap desain udah selesai :D. Selanjutnya silahkan buka kembali file TrackPreviewViewController lalu isikan codingan seperti berikut.

//
//  TrackPreviewViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 5/11/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import AudioPlayerManager
import Kingfisher
import MarqueeLabel

class TrackPreviewViewController: UIViewController {

    @IBOutlet weak var buttonPlayPause: UIButton!
    @IBOutlet weak var labelArtist: MarqueeLabel!
    @IBOutlet weak var labelTrack: MarqueeLabel!
    @IBOutlet weak var imageAlbum: UIImageView!
    @IBOutlet weak var imageBackground: UIImageView!
    
    let track = UserDefaults.standard.object(forKey: "trackPreview") as! String
    let artistsName = UserDefaults.standard.object(forKey: "trackName") as! String
    let trackName = UserDefaults.standard.object(forKey: "artistName") as! String
    let image = UserDefaults.standard.object(forKey: "imageAlbum") as! String
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "Belajar iOS"
        
        self.labelArtist.type = .continuous
        self.labelArtist.speed = .duration(15)
        self.labelArtist.animationCurve = .easeInOut
        
        self.labelTrack.type = .continuous
        self.labelTrack.speed = .duration(15)
        self.labelTrack.animationCurve = .easeInOut
        
        self.labelArtist.text = artistsName
        self.labelTrack.text = trackName
        
        let url = URL(string: image)
        self.imageBackground.kf.setImage(with: url)
        self.imageAlbum.kf.setImage(with: url)
        
        if AudioPlayerManager.shared.isPlaying() == true {
            AudioPlayerManager.shared.stop()
        }
        AudioPlayerManager.shared.play(urlString: track)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func playButtonAction(_ sender: Any) {
        if AudioPlayerManager.shared.isPlaying() == true {
            AudioPlayerManager.shared.pause()
            self.buttonPlayPause.setImage(UIImage(named: "play-icon"), for: .normal)
        } else {
            AudioPlayerManager.shared.play()
            self.buttonPlayPause.setImage(UIImage(named: "pause-icon"), for: .normal)
        }
    }
}

Untuk memutar music, penulis menggunakan library AudioPlayerManager :). Nah untuk mengambil data dari storyboard sebelumnnya, kita menggunakan fungsi UserDefaults.standard, fungsi ini sama seperti fungsi LocalStorage pada web browser. Untuk memutar musik, kita dapat menggunakan fungsi AudioPlayerManager.shared.play, baik itu musik di local mauapun melalui url, pada contoh kali ini kita menggunakan url. Untuk menganti gambar pada button, kita hanya perlu melakuka set image seperti perintah berikut.

self.buttonPlayPause.setImage(UIImage(named: "play-icon"), for: .normal)

Sehingga pada saat musik sedang berjalan, maka posisi button adalah pause, sedangkan pada saat musik berhenti maka posisi button adalah play.

Oke langkah selanjutnya, bagaimana cara untuk berpindah dari 1 storyboard ke storyboard yang lain ?. Ada beberapa cara yaitu bisa menggunakan segue atau menggunakan pushViewController, biasanya saya akan menggunakan pushViewController. Silahkan buka file ViewController lalu pada bagian extension UITableViewDelegate tambahkan kodingan berikut.

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if UserDefaults.standard.object(forKey: "trackPreview") != nil {
            UserDefaults.standard.removeObject(forKey: "trackPreview")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "trackName") != nil {
            UserDefaults.standard.removeObject(forKey: "trackName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "artistName") != nil {
            UserDefaults.standard.removeObject(forKey: "artistName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "imageAlbum") != nil {
            UserDefaults.standard.removeObject(forKey: "imageAlbum")
            UserDefaults.standard.synchronize()
        }

        UserDefaults.standard.set(self.rowData[indexPath.row].previewUrl, forKey: "trackPreview")
        UserDefaults.standard.set(self.rowData[indexPath.row].name, forKey: "trackName")
        UserDefaults.standard.set(ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!), forKey: "artistName")
        UserDefaults.standard.set(self.rowData[indexPath.row].album?.images?[0].url, forKey: "imageAlbum")
        UserDefaults.standard.synchronize()

        let storyboard = UIStoryboard(name: "TrackPreview", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "trackPreviewStoryBoard") as UIViewController
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

fungsi dari function kedua diatas adalah jika user memilih salah satu row table, maka akan dijalankan aksi di dalam nya, di dalam function tersebut kita menyimpan data musik yang akan dikirim ke storyboard berikutnya, untuk berpindah ke storyboard kedua, kita menggunakan perintah berikut.

let storyboard = UIStoryboard(name: "TrackPreview", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "trackPreviewStoryboard") as UIViewController
self.navigationController?.pushViewController(vc, animated: true)

Pertama kita deklarasikan terlebih dahulu variabel storyboard dengan value nama storyboard, tadinya kita membuat storyboard dengan nama TrackPreview, lalu buat sebuah variabel dengan menampung id dari storyboard tersebut, id ini bersifat unik walaupun berbeda storyboard, silahkan lihat video diatas pada saat deklarasikan storyboard id, storyboard id nya yaitu trackPreviewStoryboard. Selanjutnya untuk menampilkan storyboard berikutnya, kita menggunakan perintah pushViewController sehingga storyboard kedua akan berada diatas storyboard pertama sehingga akan saling tumpang tindih, konsep ini sebenarnya sama seperti konsep intent pada android :).

Berikut kodingan lengkap untuk ViewController

//
//  ViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import RxSwift
import Kingfisher
import SwiftOverlays

class ViewController: UIViewController {

    @IBOutlet weak var trackTable: UITableView!
    let searchBar = UISearchBar()

    var rowData: [Item] = []
    var nextPage: String? = nil
    var nextInt = 0
    var limitInt = 20
    var track = "ColdPlay"
    var isSearch = false

    private var disposeBag = DisposeBag()
    private var trackService = TrackService()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Belajar iOS"
        self.getDefaultTrack(track: track, offset: nextInt, limit: limitInt)
        self.trackTable.delegate = self
        self.trackTable.dataSource = self

        self.searchBar.placeholder = "Cari"
        self.searchBar.delegate = self
        self.searchBar.tintColor = UIColor.black
        self.searchBar.sizeToFit()
        self.navigationItem.titleView = searchBar
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getDefaultTrack(track: String, offset: Int, limit: Int) {
        SwiftOverlays.showBlockingWaitOverlay()
        trackService
            .getTrackByQuery(parameters: ConvertString().toPlusString(text: track), offset: offset, limit: limit)
            .subscribe(
                onNext: { trackResponse in
                    if offset == 0 && trackResponse.tracks?.items?.count != 0 {
                        self.rowData.removeAll()
                    }
                    if trackResponse.tracks?.items?.count != 0 {
                        self.rowData.append(contentsOf: (trackResponse.tracks?.items)!)
                        if ((trackResponse.tracks?.next) ?? "").isEmpty == false {
                            self.nextPage = (trackResponse.tracks?.next)!
                        } else {
                            self.nextPage = nil
                        }
                        self.nextInt = (trackResponse.tracks?.offset)! + 20
                        self.trackTable.reloadData()
                    } else {
                        let alert = UIAlertController(title: "Info", message: "Maaf, Data Tidak ditemukan :(", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    SwiftOverlays.removeAllBlockingOverlays()
                },
                onError: { error in
                    SwiftOverlays.removeAllBlockingOverlays()
                }
            )
            .addDisposableTo(disposeBag)
    }

}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if UserDefaults.standard.object(forKey: "trackPreview") != nil {
            UserDefaults.standard.removeObject(forKey: "trackPreview")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "trackName") != nil {
            UserDefaults.standard.removeObject(forKey: "trackName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "artistName") != nil {
            UserDefaults.standard.removeObject(forKey: "artistName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "imageAlbum") != nil {
            UserDefaults.standard.removeObject(forKey: "imageAlbum")
            UserDefaults.standard.synchronize()
        }

        UserDefaults.standard.set(self.rowData[indexPath.row].previewUrl, forKey: "trackPreview")
        UserDefaults.standard.set(self.rowData[indexPath.row].name, forKey: "trackName")
        UserDefaults.standard.set(ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!), forKey: "artistName")
        UserDefaults.standard.set(self.rowData[indexPath.row].album?.images?[0].url, forKey: "imageAlbum")
        UserDefaults.standard.synchronize()

        let storyboard = UIStoryboard(name: "TrackPreview", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "trackPreviewStoryboard") as UIViewController
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.rowData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Bundle.main.loadNibNamed("TrackTableViewCell", owner: self, options: nil)?.first as! TrackTableViewCell

        cell.trackNameLabel.text = self.rowData[indexPath.row].name
        cell.artistNameLabel.text = ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!)
        cell.albumNameLabel.text = self.rowData[indexPath.row].album?.name

        let url = URL(string: (self.rowData[indexPath.row].album?.images?[0].url)!)
        cell.gambarAlbumImage.kf.indicatorType = .activity
        cell.gambarAlbumImage.kf.setImage(with: url)

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 98
    }
}

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.track = self.searchBar.text!
        self.nextInt = 0
        self.getDefaultTrack(track: self.track, offset: self.nextInt, limit: self.limitInt)
        self.searchBar.resignFirstResponder()
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchBar.text = nil
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }
}

Sekarang silahkan jalankan, berikut adalah hasil output nya

Screen Shot 2017-05-11 at 3.53.33 PM.png

Wah tombol back nya kurang bagus nih :(, oke kita akan coba ganti tulisan nya menjadi kembali dengan warna hitam, silahkan buka file TrackPreviewViewController lalu tambahkan kodingan berikut di function viewDidLoad.

self.navigationController?.navigationBar.tintColor = UIColor.black
self.navigationController?.navigationBar.topItem?.title = "Kembali"

dan hasilnya seperti berikut :D.

Akhirnya selesai juga artikel mengenai belajar iOS dari bagian 1 hingga 4 :). Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 4, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Sebelumnya kita telah membuat aplikasi untuk melihat list musik, akan tetapi kurang jika kita tidak menambahkan fitur search :). Oke, pada artikel ini kita akan membahas mengenai bagaimana cara membuat search :D. Mungkin artikel ini adalah artikel yang terpendek dikarenkan untuk membuat sebuah search bar di swift sangatlah gampang :). Oke kita langsung buka project iOS dengan xcode, project yang kita gunakan adalah project sebelum nya yang ada di artikel sebelumnya.

Untuk membuat search bar silahkan buka file ViewController, kemudian deklarasikan sebuah variabel searchBar seperti berikut.

let searchBar = UISearchBar()

Setelah selesai, kita akan membuat search bar tersebut tepat di navigation menu, silahkan masukkan codingan berikut tepat di dalam function viewDidLoad

self.searchBar.placeholder = "Cari"
self.searchBar.delegate = self
self.searchBar.tintColor = UIColor.black
self.searchBar.sizeToFit()
self.navigationItem.titleView = searchBar

Nah karena diatas ada perintah delegate maka kita harus membuat 1 extension untuk menghadle action dari search bar, silahkan masukkan codingan extension berikut di bagian paling bawah.

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.track = self.searchBar.text!
        self.nextInt = 0
        self.getDefaultTrack(track: self.track, offset: self.nextInt, limit: self.limitInt)
        self.searchBar.resignFirstResponder()
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchBar.text = nil
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }
}

fungsi function searchBarSearchButtonClicked berfungsi untuk menghandle action ketika user menekan tombol search, dan pada saat itu, kita akan menjalankan perintah getDefaultTrack untuk mengambil data musik. function kedua berfungsi memunculkan tombol cancel ketika kita melakukan editing pada search bar nya dan pada function terakhir ketika user menekan tombol cancel maka tombol cancel akan kembali seperti biasa.

Berikut adalah hasil dari seluruh kodingannya.

//
//  ViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import RxSwift
import Kingfisher
import SwiftOverlays

class ViewController: UIViewController {

    @IBOutlet weak var trackTable: UITableView!
    let searchBar = UISearchBar()

    var rowData: [Item] = []
    var nextPage: String? = nil
    var nextInt = 0
    var limitInt = 20
    var track = "ColdPlay"
    var isSearch = false

    private var disposeBag = DisposeBag()
    private var trackService = TrackService()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Belajar iOS"
        self.getDefaultTrack(track: track, offset: nextInt, limit: limitInt)
        self.trackTable.delegate = self
        self.trackTable.dataSource = self

        self.searchBar.placeholder = "Cari"
        self.searchBar.delegate = self
        self.searchBar.tintColor = UIColor.black
        self.searchBar.sizeToFit()
        self.navigationItem.titleView = searchBar
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getDefaultTrack(track: String, offset: Int, limit: Int) {
        SwiftOverlays.showBlockingWaitOverlay()
        trackService
            .getTrackByQuery(parameters: ConvertString().toPlusString(text: track), offset: offset, limit: limit)
            .subscribe(
                onNext: { trackResponse in
                    if offset == 0 && trackResponse.tracks?.items?.count != 0 {
                        self.rowData.removeAll()
                    }
                    if trackResponse.tracks?.items?.count != 0 {
                        self.rowData.append(contentsOf: (trackResponse.tracks?.items)!)
                        if ((trackResponse.tracks?.next) ?? "").isEmpty == false {
                            self.nextPage = (trackResponse.tracks?.next)!
                        } else {
                            self.nextPage = nil
                        }
                        self.nextInt = (trackResponse.tracks?.offset)! + 20
                        self.trackTable.reloadData()
                    } else {
                        let alert = UIAlertController(title: "Info", message: "Maaf, Data Tidak ditemukan :(", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    SwiftOverlays.removeAllBlockingOverlays()
                },
                onError: { error in
                    SwiftOverlays.removeAllBlockingOverlays()
                }
            )
            .addDisposableTo(disposeBag)
    }

}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.rowData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Bundle.main.loadNibNamed("TrackTableViewCell", owner: self, options: nil)?.first as! TrackTableViewCell

        cell.trackNameLabel.text = self.rowData[indexPath.row].name
        cell.artistNameLabel.text = ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!)
        cell.albumNameLabel.text = self.rowData[indexPath.row].album?.name

        let url = URL(string: (self.rowData[indexPath.row].album?.images?[0].url)!)
        cell.gambarAlbumImage.kf.indicatorType = .activity
        cell.gambarAlbumImage.kf.setImage(with: url)

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 98
    }
}

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.track = self.searchBar.text!
        self.nextInt = 0
        self.getDefaultTrack(track: self.track, offset: self.nextInt, limit: self.limitInt)
        self.searchBar.resignFirstResponder()
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchBar.text = nil
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }
}

Nah setelah selesai, silahkan jalankan aplikasi kalian, berikut adalah demo aplikasi nya :)

Artikel selanjutnya akan membahas mengenai bagaimana cara untuk memutar lagu :D. Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 3, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Hy jumpa lagi :D, artikel ini merupakan kelanjutan dari artikel sebelumnya. Pada bagian kedua ini kita akan mencoba membuat hingga tahap menampilkan data :). Berikut adalah tahapan development yang akan kita lakukan.

  • Apa Itu Storyboards, xib dan auto layout ?
  • Membuat View Dan Navigation
  • Membuat Service Dengan RxSwift Dan Alamofire
  • Membuat Helper Dan Controller

Apa Itu Storyboards, XIB dan Auto Layout ?

Storyboard merupakan salah satu fitur untuk melakukan desain aplikasi. Pada zaman dulu, developer hanya dapat menggunakan fitur XIB. Storyboard dan XIB sama - sama mempunyai fungsi untuk melakukan desain interface aplikasi, akan tetapi pada zaman sekarang developer apple merekomendasikan untuk menggunakan storyboard. Di dalam story board kita dapat menggunakan beberapa layer aplikasi seperti gambar berikut.

Screen Shot 2017-04-30 at 9.50.18 PM.png

Sehingga anda dapat mengetahui alur dari aplikasi tersebut secara detail. Berbeda dengan XIB, di dalam xib kita hanya dapat menggunakan 1 layer aplikasi saja seperti gambar berikut.

Screen Shot 2017-04-30 at 9.52.35 PM.png

Akan tetapi, ketika dihadapkan dengan suatu project besar maka kedua nya akan tetap bisa kita gunakan karena masing - masing dari mereka berdua mempunyai kelebihan dan kekurangan masing - masing misalnya jika kita menggunakan 1 storyboard dengan banyak layer maka proses membuka storyboard tersebut akan memakan waktu lama disebabkan banyak nya layer di dalam nya.

Bagaimana dengan auto layout ?

Auto Layout biasanya digunakan jika kita akan mendevelop aplikasi dengan berbagai ukuran layar

Biasanya kita tidak hanya terpaku di 1 ukuran layar saja, kita diharuskan untuk membuat untuk beberapa ukuran layar maka kita dapat menggunakan fitur auto layout. Fitur auto layout ini sangat mirip dengan fitur constraint nya android. Berikut adalah percobaan untuk membuat auto layout.

Membuat Contoh Auto Layout

Silahkan buat sebuah project, lalu silahkan drag button dari menu kanan bawah seperti berikut.

Screen Shot 2017-04-30 at 9.59.24 PM.png

Setelah selesai, silahkan klik menu berikut lalu sesuaikan property nya seperti berikut.

Screen Shot 2017-04-30 at 10.03.16 PM.png

dan kemudian pilih add 4 constrains. Maka secara otomatis button kalian akan seperti berikut.

Screen Shot 2017-04-30 at 10.04.59 PM.png

Gambar diatas telah diubah warna backgroud dan warna tulisan nya, untuk mengubahnya, kamu dapat menggunakan menu seperti gambar berikut.

Screen Shot 2017-04-30 at 10.06.40 PM.png

Silahkan dijalankan di beberapa emulator untuk melihat hasil nya :).

Membuat View Dan Navigation

Oke, kembali ke project yang akan kita buat. Pada bagian ini kita akan membuat view dan navigation view. Secara default project anda hanya memiliki 1 storyboard yaitu main.storyboard. Silahkan buka storyboard tersebut, lalu pilih view controller nya, pada menu bagian atas silahkan pilih editor lalu pilih embed in dan terakhir pilih navigation controller maka akan muncul output seperti berikut.

Screen Shot 2017-04-30 at 10.34.18 PM.png

Setelah selesai, silahkan drag 1 table view ke view controller, lalu berikan constraint seperti berikut.

Screen Shot 2017-04-30 at 10.36.25 PM.png

Selanjutnya silahkan buat 1 group yaitu views, di dalam group views kita akan membuat view khusus untuk menampilkan list data lagu. Karena kita menggunakan table view, maka kita membutuhkan table view cell untuk list cell nya. Untuk membuat table view cell, silahkan klik kanan pada group views, lalu pilih new file, lalu pilih cocoa touch class, lalu klik next dan isikan property nya seperti berikut.

Screen Shot 2017-04-30 at 10.40.55 PM.png

Maka akan muncul 2 file yaitu TrackTableViewCell.swift dan TrackTableViewCell.xib. Silahkan buka yang xib lalu silahkan membuat desain seperti berikut.

Screen Shot 2017-04-30 at 10.44.49 PM.png

Setelah selesai, silahkan ikuti tutorial berikut untuk konfigurasi dari view table cell ke controller table cell.

Tahap selanjutnya, silahkan ikuti tutorial berikut untuk main storyboard.

Membuat Service Dengan RxSwift Dan Alamofire

Pada bagian ini, kita akan membuat service. Biasanya service disini saya gunakan untuk melakukan request data ke API. Untuk melakukan request data, kita akan menggunakan Alamofire. Untuk melakukan parsing json ke object swift biasanya saya menggunakan ObjectMapper. Untuk membuat aplikasi suatu event request yang asynchronous maka kita gunakan RxSwift, bagi anda yang ingin memperdalam tentang reactivex silahkan baca di ReactiveX.

Silahkan buat sebuah group dengan nama services lalu buatlah sebuah file swift dengan nama TrackService di dalam group service tersebut. Lalu isikan kodingan seperti berikut.

//
//  TrackService.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import Alamofire
import ObjectMapper
import AlamofireObjectMapper
import RxSwift

class TrackService {
    func getTrackByQuery(parameters: String, offset: Int, limit: Int) -> Observable<TrackResponse> {
        return Observable<TrackResponse>.create { observer -> Disposable in
            let request = Alamofire
                .request("https://api.spotify.com/v1/search?q=\(parameters)&type=track&offset=\(offset)&limit=\(limit)", method: .get)
                .validate()
                .responseObject(completionHandler: { (response: DataResponse<TrackResponse>) in
                    switch response.result {
                    case .success(let trackResponse):
                        observer.onNext(trackResponse)
                        observer.onCompleted()
                    case .failure(let error):
                        observer.onError(error)
                    }
                })
            return Disposables.create(with: {
                request.cancel()
            })
        }
    };
}

Dengan menggunakan RxSwift maka kita dapat menggunakan fitur Observable, fitur ini mirip dengan fitur List di java. Disana terdapat 3 parameter yaitu parameters biasanya ini untuk parameter yang akan kita search, offset sebagai halaman ke berapa karena pada api ini kita menggunakan paging, dan yang terakhir adalah limit yang berfungsi untuk mendeklarasikan berapa per halaman yang akan kita tampilkan.

Membuat Helper Dan Controller

Tahap terakhir kita akan membuat helper dan controller. Helper disini sendiri biasanya kita buat agar helper ini dapat digunakan di banyak tempat. Contohnya adalah, jika kita melakukan request, dimana jika kita menggunakan parameter dan parameter tersebut mempunyai spasi maka kita harus menconvert spasi dengan +, akan tetapi fungsi merubah karakter ini bergantung dari kebutuhan API, ada yang mengharuskan diubah menjadi + atau ada juga menggunakan %20 dan lain sebagainya.

Silahkan buat sebuah group dengan nama helpers lalu buat sebuah file swift dengan nama ConvertString, kemudian masukkan kodingan seperti berikut.

//
//  ConvertString.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 5/1/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import Foundation

class ConvertString {
    func toPlusString(text: String) -> String {
        return text.replacingOccurrences(of: " ", with: "+")
    }

    func fromArrayArtistToString(array: [Artist]) -> String {
        var text = ""
        if array.count == 1 {
            text = array[0].name!
        } else {
            for i in 0..<array.count {
                text = text + array[i].name! + ", "
            }
        }

        if text.substring(from: text.index(text.endIndex, offsetBy: -2)) == ", " {
            text.remove(at: text.index(text.endIndex, offsetBy: -2))
        }

        return text
    }
}

Pada class diatas terdapat 2 fungsi yaitu, fungsi toPlusString berfungsi untuk me replace string kosong dengan karakter +. Sedangkan fungsi fromArrayArtistToString kita gunakan untuk menconvert dari array object Artist menjadi string biasa.

Selanjutnya silahkan buka kembali file TrackTableViewCell lalu ubah codingan nya menjadi seperti berikut.

//
//  TrackTableViewCell.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import MarqueeLabel

class TrackTableViewCell: UITableViewCell {

    @IBOutlet weak var albumNameLabel: MarqueeLabel!
    @IBOutlet weak var artistNameLabel: MarqueeLabel!
    @IBOutlet weak var trackNameLabel: MarqueeLabel!
    @IBOutlet weak var gambarAlbumImage: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        
        self.trackNameLabel.type = .continuous
        self.trackNameLabel.speed = .duration(15)
        self.trackNameLabel.animationCurve = .easeInOut
        
        self.artistNameLabel.type = .continuous
        self.artistNameLabel.speed = .duration(15)
        self.artistNameLabel.animationCurve = .easeInOut
        
        self.albumNameLabel.type = .continuous
        self.albumNameLabel.speed = .duration(15)
        self.albumNameLabel.animationCurve = .easeInOut
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

}

Pada kodingan diatas, kita mendeklarasikan text marquee, tujuan nya adalah jika terdapat text yang panjang melebihi lebar view, maka secara otomatis text tersebut akan berjalan sehingga semua tulisan dapat anda baca. Setelah selesai, silahkan buka file ViewController lalu ubah kodingannya menjadi seperti berikut.

//
//  ViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import RxSwift
import Kingfisher
import SwiftOverlays

class ViewController: UIViewController {

    @IBOutlet weak var trackTable: UITableView!

    var rowData: [Item] = []
    var nextPage: String? = nil
    var nextInt = 0
    var limitInt = 20
    var track = "ColdPlay"
    var isSearch = false

    private var disposeBag = DisposeBag()
    private var trackService = TrackService()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Belajar iOS"
        self.getDefaultTrack(track: track, offset: nextInt, limit: limitInt)
        self.trackTable.delegate = self
        self.trackTable.dataSource = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getDefaultTrack(track: String, offset: Int, limit: Int) {
        SwiftOverlays.showBlockingWaitOverlay()
        trackService
            .getTrackByQuery(parameters: ConvertString().toPlusString(text: track), offset: offset, limit: limit)
            .subscribe(
                onNext: { trackResponse in
                    if offset == 0 && trackResponse.tracks?.items?.count != 0 {
                        self.rowData.removeAll()
                    }
                    if trackResponse.tracks?.items?.count != 0 {
                        self.rowData.append(contentsOf: (trackResponse.tracks?.items)!)
                        if ((trackResponse.tracks?.next) ?? "").isEmpty == false {
                            self.nextPage = (trackResponse.tracks?.next)!
                        } else {
                            self.nextPage = nil
                        }
                        self.nextInt = (trackResponse.tracks?.offset)! + 20
                        self.trackTable.reloadData()
                    } else {
                        let alert = UIAlertController(title: "Info", message: "Maaf, Data Tidak ditemukan :(", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    SwiftOverlays.removeAllBlockingOverlays()
                },
                onError: { error in
                    SwiftOverlays.removeAllBlockingOverlays()
                }
            )
            .addDisposableTo(disposeBag)
    }

}

Karena kita membutuhkan delegate atau action untuk table, maka kita membutuhkan sebuah class extension, dimana class ini berfungsi untuk memisahkan fungsi implementasi class Protocol. Class Protocol ini sama seperti class interface yang ada di java. Silahkan tambahkan codingan extension berikut di akhir baris.

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }
}

Karena dia ingin menampilkan data maka kita juga membutuhkan dataSource, silahkan buat 1 extension lagi untuk dataSource seperti berikut.

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.rowData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Bundle.main.loadNibNamed("TrackTableViewCell", owner: self, options: nil)?.first as! TrackTableViewCell

        cell.trackNameLabel.text = self.rowData[indexPath.row].name
        cell.artistNameLabel.text = ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!)
        cell.albumNameLabel.text = self.rowData[indexPath.row].album?.name

        let url = URL(string: (self.rowData[indexPath.row].album?.images?[0].url)!)
        cell.gambarAlbumImage.kf.indicatorType = .activity
        cell.gambarAlbumImage.kf.setImage(with: url)

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 98
    }
}

Function pertama berfungsi sebagai mendefinisikan jumlah data. Pada function kedua, kita melakukan load file xib, dimana file xib ini adalah table view cell, nah data nya akan kita input melalui parent class nya yaitu ViewController, dan pada function terakhir mendefinisikan tinggi dari xib.

Jika telah selesai, silahkan jalankan project nya, jika berhasil maka anda dapat melihat hasil nya seperti berikut :D.

Screen Shot 2017-05-01 at 1.16.04 AM.png

Artikel selanjutnya akan membahas mengenai bagaimana cara membuat search bar :). Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 2, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)