Swift -Pokemon API ポケモン図鑑作ってみました!-
はじめに
以前、JavaScriptでPokemon APIを叩き、情報を取得しました。
今回は、Swiftで叩いてみましたので、iPhoneで情報を参照したい方やポケモンが好きな方は、是非、参考にしてみて下さい。
使用環境
● OS:macOS Big Sur 11.3.1
● Xcode:12.5
● Swift:5.4
● DB Browser for SQLite:3.12.1
仕様
動作確認
ファイル構成
コード
AppDelegate
//
// AppDelegate.swift
// PokemonAPI_Swift
//
//
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
sleep(3)
Util.share.copyDatabase(dbName: "PokemonDB.db")
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
SceneDelegate
//
// SceneDelegate.swift
// PokemonAPI_Swift
//
//
import UIKit
import Alamofire
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var pokemonList = [Models]()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: scene as! UIWindowScene)
self.window = window
window.backgroundColor = .white
PokemonData()
let backGround = UIImageView()
backGround.image = UIImage(named: "Picturebook.jpg")
backGround.frame.size = CGSize(width: window.frame.width, height: window.frame.height/2 - 100)
backGround.center = window.center
window.addSubview(backGround)
let indicator = UIActivityIndicatorView()
indicator.center = window.center
indicator.style = .large
indicator.color = .red
window.addSubview(indicator)
let view = UIView()
view.frame.size = CGSize(width: window.frame.width, height: window.frame.height)
view.backgroundColor = .systemGray
view.alpha = 0.5
window.addSubview(view)
let getInfoLbl = UILabel()
getInfoLbl.frame.size = CGSize(width: window.bounds.width/2 + 150, height: window.bounds.height/2 + 200)
getInfoLbl.bounds.size = CGSize(width: window.bounds.width/2, height: window.bounds.height/2)
getInfoLbl.textAlignment = .center
getInfoLbl.textColor = .black
getInfoLbl.text = "情報を取得中です..."
window.addSubview(getInfoLbl)
indicator.stopAnimating()
indicator.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 13.5) {
view.frame.size = CGSize(width: window.frame.width, height: window.frame.height)
view.backgroundColor = .white
window.addSubview(view)
let Lbl = UILabel()
Lbl.frame.size = CGSize(width: 200, height: 400)
Lbl.bounds.size = CGSize(width: window.bounds.width/2, height: window.bounds.height/2)
Lbl.textAlignment = .center
Lbl.center = window.center
Lbl.textColor = .black
Lbl.text = "情報の取得完了です!"
getInfoLbl.isHidden = true
window.addSubview(Lbl)
indicator.stopAnimating()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 16.0) {
let dest = TableViewController.init(nibName: "TableViewController", bundle: nil)
let nc = UINavigationController(rootViewController: dest)
window.rootViewController = nc
}
window.makeKeyAndVisible()
}
func PokemonData() {
for i in 1 ... 899 {
let path = "\(i)"
let prams: Parameters = [:]
APIRequest.shared.get(path: path, prams: prams, type: Pokemon.self) { response in
//print(response.id)
//print(response.name)
//print(response.sprites.frontDefault)
//print(response.typeList)
let save = ModelManager.getInstance().Save(pokemon: Models(id: response.id, name: response.name, image: response.sprites.frontDefault))
print("save", save)
}
}
pokemonList = ModelManager.getInstance().getOokemonsData()
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
Util
//
// Util.swift
// PokemonAPI_Swift
//
//
import Foundation
import UIKit
class Util {
static let share = Util()
func getPath(dbName: String) -> String {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileUrl = documentDirectory.appendingPathComponent(dbName)
print(fileUrl)
return fileUrl.path
}
func copyDatabase(dbName: String) {
let dbPath = getPath(dbName: "PokemonDB.db")
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: dbPath) {
let bundle = Bundle.main.resourceURL
let file = bundle?.appendingPathComponent(dbName)
do {
try fileManager.copyItem(atPath: file!.path, toPath: dbPath)
}
catch let err {
print(err.localizedDescription)
}
}
}
}
ModelManager
//
// ModelManager.swift
// PokemonAPI_Swift
//
//
import Foundation
import UIKit
import FMDB
var shareInstace = ModelManager()
class ModelManager {
var database: FMDatabase? = nil
static func getInstance() -> ModelManager {
if shareInstace.database == nil {
shareInstace.database = FMDatabase(path: Util.share.getPath(dbName: "PokemonDB.db"))
}
return shareInstace
}
// Save and delete from here
func Save(pokemon: Models) -> Bool {
shareInstace.database?.open()
let isSave = shareInstace.database?.executeUpdate("INSERT INTO pokemon_info(id, name, image) VALUES(?, ?, ?)", withArgumentsIn: [pokemon.id, pokemon.name, pokemon.image])
shareInstace.database?.close()
return isSave!
}
func getOokemonsData() -> [Models] {
shareInstace.database?.open()
var pokemons = [Models]()
do {
let resultset : FMResultSet? = try shareInstace.database?.executeQuery("SELECT * FROM pokemon_info", values: nil)
if resultset != nil{
while resultset!.next() {
let pokemon = Models(id: Int(((resultset?.int(forColumn: "id"))!)), name: (resultset?.string(forColumn: "name"))!, image: (resultset?.string(forColumn: "image"))!)
pokemons.append(pokemon)
}
}
}
catch let err {
print(err.localizedDescription)
}
shareInstace.database?.close()
return pokemons
}
}
APIRequest
//
// APIRequest.swift
// PokemonAPI_Swift
//
//
import Foundation
import Alamofire
class APIRequest {
static var shared = APIRequest()
private let baseUrl = "https://pokeapi.co/api/v2/pokemon/"
func get<T:Decodable>(path: String, prams: Parameters, type: T.Type, completion: @escaping (T) -> Void) {
let path = path
let url = baseUrl + path
let request = AF.request(url, method: .get, parameters: prams)
request.response { response in
let statusCode = response.response!.statusCode
do {
if statusCode <= 300 {
guard let data = response.data else { return }
let decode = JSONDecoder()
let value = try decode.decode(T.self, from: data)
completion(value)
}
} catch {
print("変換に失敗しました:", error)
print(response.debugDescription)
}
switch statusCode {
case 400:
print(response.description)
case 401:
print(response.description)
case 403:
print(response.description)
case 404:
print(response.description)
default:
break
}
}
}
}
Model
//
// Model.swift
// PokemonAPI_Swift
//
//
import Foundation
struct Pokemon: Decodable {
var id: Int
var name: String
var sprites: Sprites
var typeList: [TypeList]
struct Sprites: Decodable {
var frontDefault: String
private enum CodingKeys: String, CodingKey {
case frontDefault = "front_default"
}
}
struct TypeList: Decodable {
var type: Type
struct `Type`: Decodable {
var name = "name"
private enum CodingKeys: String, CodingKey {
case name = "name"
}
}
private enum CodingKeys: String, CodingKey {
case type = "type"
}
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case sprites = "sprites"
case typeList = "types"
}
}
struct Models {
var id: Int
var name: String
var image: String
}
TableViewController
//
// TableViewController.swift
// PokemonAPI_Swift
//
//
import UIKit
import Alamofire
class TableViewController: UIViewController {
@IBOutlet weak var pokemonTableView: UITableView!
var pokemonList = [Models]()
override func viewDidLoad() {
super.viewDidLoad()
//PokemonData()
let nib = UINib(nibName: "TableViewCell", bundle: nil)
self.pokemonTableView.register(nib, forCellReuseIdentifier: "TableViewCell")
pokemonTableView.delegate = self
pokemonTableView.dataSource = self
pokemonList = ModelManager.getInstance().getOokemonsData()
self.pokemonTableView.reloadData()
}
}
extension TableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.pokemonList.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = pokemonTableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.numberLbl.text = "No." + "\(pokemonList[indexPath.row].id)"
cell.pokemonName.text = pokemonList[indexPath.row].name
cell.pokemonImage.image = UIImage(url: pokemonList[indexPath.row].image)
return cell
}
}
extension UIImage {
public convenience init(url: String) {
let url = URL(string: url)
do {
let data = try Data(contentsOf: url!)
self.init(data: data)!
return
} catch let err {
print("Error : \(err.localizedDescription)")
}
self.init()
}
}
TableViewCell
//
// TableViewCell.swift
// PokemonAPI_Swift
//
//
import UIKit
class TableViewCell: UITableViewCell {
@IBOutlet weak var pokemonImage: UIImageView!
@IBOutlet weak var pokemonName: UILabel!
@IBOutlet weak var numberLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
おわりに
今回は、SwiftでPokemon APIを叩いてみました。
セル形式で表示しているので、JavaScriptよりも手間が掛かる事が分かりました。
一度に情報を取得し過ぎると、スクロールするのにラグが発生して、一瞬止まる事も分かりました。
作り直すとしたら、取得件数を制限してラグを起こさない様に調整したいと思います。