Hôm nay mình sẽ hưỡng dẫn các bạn làm App PokemonGo dựa trên 30 dự án mẫu mà mình đã được học.
https://github.com/soapyigu/Swift-30-Projects/tree/master/Project%2007%20-%20PokedexGo
App hiển thị danh sách các Pokemon và hiển thị chi tiết nội dung của Pokemon đó
B1: Khởi tạo xcode và đặt tên cho project
B2: Ở trong Main.storyboard thiết lập ViewController kế thừa NavigationController
Thiết lập hai màn hình, một màn hình để hiển thị danh dách pokemon và màn hình còn lại để hiển thị nội dung chi tiết pokemon mà mình đã chọn
Ở màn hình danh sách pokemon chúng ta kế thừa UITableview và đặt thêm hai thuộc tính đó chính là delegate và dataSource. Custom lại view sao cho hiển thị được ID, Tên và hình ảnh. Thêm một cái UISearchBar để chúng ta có thể tìm kiếm được tên pokemon
Màn hình còn lại chúng ta customView như màn hình danh sách nhưng không kế thừa UITableView và UISearchBar
B3: Tạofile PokemonConstants.swift. Trong file này chúng ta tạo một mảng pokemons chứa các thuộc tính của nó
let pokemons = [
Pokemon(name: "妙蛙种子", id: 1,
detailInfo: "妙蛙种子经常在阳光下酣睡。它背上有个种子,通过吸收阳光渐渐长大。",
type: [PokeType.grass, PokeType.poison],
weak: [PokeType.fire, PokeType.flying, PokeType.ice, PokeType.psychic],
pokeImgUrl: "http://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png"),
B4: Tạo lớp Pokemon kiểu dữ liệu là NSObject, chúng ta tạo một file Pokemon.swift để sao cho dễ quản lý đối tượng hơn
class Pokemon: NSObject {
let name: String
let id: Int
let detailInfo: String
let type: [PokeType]
let weak: [PokeType]
let pokeImgUrl: String
Khởi tạo đối tượng cho lớp Pokemon
init(name: String, id: Int, detailInfo: String, type: [PokeType], weak: [PokeType], pokeImgUrl: String) {
self.name = name
self.id = id
self.detailInfo = detailInfo
self.type = type
self.weak = weak
self.pokeImgUrl = pokeImgUrl
}
Nếu bạn làm việc với scroll direction, bài toán đặt ra là bạn cần phải xác định được chiều scroll.Trong tình huống đó thì enum sẽ là sự lựa chọn hoàn hảo.
enum PokeType {
case normal
case fire
case water
case electric
case grass
case ice
case fighting
case poison
case ground
case flying
case psychic
case bug
case rock
case ghost
case dragon
case dark
case steel
case fairy
}
B5: Ở màn MasterViewController.swift ta thêm hai thư viện RxSwift và RxSwift
import RxSwift
import RxCocoa
Nếu các bạn chưa biết cài thư viện như thế nào thì hãy xem tại đây
RxSwift giúp công việc của bạn trở nên đơn giản hơn. Thay cho notifications, một đối tượng khó để test, ta có thể sử dụng signals. Thay cho delegates, thứ tốn rất nhiều code, ta có thể viết blocks và bỏ đi switches/ifs lồng nhau. Ta còn có thể sử dụng KVO, IBActions, filters, MVVM và nhiều tiện ích khác được hỗ trợ mượt mà trong RxSwift. Bạn có thể tìm hiểu thêm về RxSwift tại đây
RxCocoa là một thư viện được xây dựng trên nền tảng là RxSwift và nó cũng chính là một phần của RxSwift.
RxCocoa thêm các phần mở rộng (extension) vào các thành phần UI của iOS giúp cho chúng ta có thể subscribe để lắng nghe các sự kiện đến từ UI.
Ví dụ: Để lắng nghe sự kiện ON/OFF từ một UISwicth, chúng ta có thể subscribe thông qua phần mở rộng được cung cấp từ RxCocoa đó là .rx.isOn
.
B6: Định nghĩa một đối tượng mới cho PokemonSelectionDelegate kế thừa từ class Pokemon
protocol PokemonSelectionDelegate: class {
func pokemonSelected(_ newPokemon: Pokemon)
}
Ánh xạ UISearchBar để sử dụng
Tạo biến disposeBag
fileprivate let disposeBag = DisposeBag()
fileprivate là 1 access control giới hạn trong 1 file, thường dùng cho extension
Gán delegate và dataSource ở trong table view
weak var delegate: PokemonSelectionDelegate?
// UITableViewDelegate
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 140
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let pokemon = self.filteredPokemons[(indexPath as NSIndexPath).row]
delegate?.pokemonSelected(pokemon)
if let detailViewController = self.delegate as? DetailViewController {
splitViewController?.showDetailViewController(detailViewController.navigationController!, sender: nil)
}
}
// UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredPokemons.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! MasterTableViewCell
let pokemon = filteredPokemons[(indexPath as NSIndexPath).row]
cell.awakeFromNib(pokemon.id, name: pokemon.name, pokeImageUrl: pokemon.pokeImgUrl)
return cell
}
Việc sử dụng Weak, liên quan đến việc quản lý bộ nhớ trong Swift gọi là Automatic Reference Counting (ARC) . Định nghĩa trong Khoa học máy tính thì Reference Counting là kĩ thuật lưu lại số tham chiếu, con trỏ, hoặc sử lý liên quan đến resource như là đối tương, block hoặc bộ nhớ. Nói ngắn gọn, ARC giúp bạn lưu các tham chiếu vào trong bộ nhớ và giúp giải thoát nó đi khi không được dùng nữa.
Khởi tạo fucn setupUI
fileprivate func setupUI() {
self.title = "精灵列表"
definesPresentationContext = true
searchBar
.rx.text
.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(
onNext: { [unowned self] query in
if query?.characters.count == 0 {
self.filteredPokemons = self.pokemons
} else {
self.filteredPokemons = self.pokemons.filter{ $0.name.hasPrefix(query!) }
}
self.tableView.reloadData()
})
.addDisposableTo(disposeBag)
}
Đoạn code này có nghĩa khi bạn tìm kiếm từ khóa của pokemon thì nó sẽ lọc ra những pokemon có từ khóa mà bạn nhập vào nếu mà không có thì nó sẽ k hiển thị
B7: Tạo class MasterTableViewCell để hiển thị view của danh sách pokemon
class MasterTableViewCell: UITableViewCell {
@IBOutlet weak var idLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var pokeImageView: UIImageView!
fileprivate var indicator: UIActivityIndicatorView!
func awakeFromNib(_ id: Int, name: String, pokeImageUrl: String) {
super.awakeFromNib()
setupUI(id, name: name)
setupNotification(pokeImageUrl)
}
deinit {
pokeImageView.removeObserver(self, forKeyPath: "image")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
fileprivate func setupUI(_ id: Int, name: String) {
idLabel.text = NSString(format: "#%03d", id) as String
nameLabel.text = name
pokeImageView.image = UIImage(named: "default_img")
indicator = UIActivityIndicatorView()
indicator.center = CGPoint(x: pokeImageView.bounds.midX, y: pokeImageView.bounds.midY)
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.startAnimating()
pokeImageView.addSubview(indicator)
pokeImageView.addObserver(self, forKeyPath: "image", options: [], context: nil)
}
fileprivate func setupNotification(_ pokeImageUrl: String) {
NotificationCenter.default.post(name: Notification.Name(rawValue: downloadImageNotification), object: self, userInfo: ["pokeImageView":pokeImageView, "pokeImageUrl" : pokeImageUrl])
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "image" {
indicator.stopAnimating()
}
}
B9: Tạo màn hình chi tiết của danh sách pokemon
class DetailViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var nameIDLabel: UILabel!
@IBOutlet weak var pokeImageView: UIImageView!
@IBOutlet weak var pokeInfoLabel: UILabel!
var pokemon: Pokemon! {
didSet (newPokemon) {
self.refreshUI()
}
}
override func viewDidLoad() {
refreshUI()
super.viewDidLoad()
}
func refreshUI() {
nameIDLabel?.text = pokemon.name + (pokemon.id < 10 ? " #00\(pokemon.id)" : pokemon.id < 100 ? " #0\(pokemon.id)" : " #\(pokemon.id)")
pokeImageView?.image = LibraryAPI.sharedInstance.downloadImg(pokemon.pokeImgUrl)
pokeInfoLabel?.text = pokemon.detailInfo
self.title = pokemon.name
}
}
extension DetailViewController: PokemonSelectionDelegate {
func pokemonSelected(_ newPokemon: Pokemon) {
pokemon = newPokemon
}
}
B10: Khởi tạo class LibraryAPI để lắng nghe sự kiện xem nó có trả về hình ảnh ở trên server không
class LibraryAPI: NSObject {
static let sharedInstance = LibraryAPI()
let persistencyManager = PersistencyManager()
deinit {
NotificationCenter.default.removeObserver(self)
}
func getPokemons() -> [Pokemon] {
return pokemons
}
func downloadImg(_ url: String) -> (UIImage) {
let aUrl = URL(string: url)
let data = try? Data(contentsOf: aUrl!)
let image = UIImage(data: data!)
return image!
}
func downloadImage(_ notification: Notification) {
// retrieve info from notification
let userInfo = (notification as NSNotification).userInfo as! [String: AnyObject]
let pokeImageView = userInfo["pokeImageView"] as! UIImageView?
let pokeImageUrl = userInfo["pokeImageUrl"] as! String
if let imageViewUnWrapped = pokeImageView {
imageViewUnWrapped.image = persistencyManager.getImage(URL(string: pokeImageUrl)!.lastPathComponent)
if imageViewUnWrapped.image == nil {
DispatchQueue.global().async {
let downloadedImage = self.downloadImg(pokeImageUrl as String)
DispatchQueue.main.async {
imageViewUnWrapped.image = downloadedImage
self.persistencyManager.saveImage(downloadedImage, filename: URL(string: pokeImageUrl)!.lastPathComponent)
}
}
}
}
}
}
Chạy và hiển thị kết quả
Bình luận