見出し画像

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

仕様

動作確認

ファイル構成

スクリーンショット 2021-10-11 23.16.50

コード

AppDelegate

//
//  AppDelegate.swift
//  PokemonAPI_Swift
//
//

import UIKit

@main
class AppDelegateUIResponderUIApplicationDelegate {

   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 SceneDelegateUIResponderUIWindowSceneDelegate {
   
   var window: UIWindow?
   
   var pokemonList = [Models]()
   
   func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
       guard let _ = (scene asUIWindowSceneelse { return }
       
       let window = UIWindow(windowScene: scene asUIWindowScene)
       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 PokemonDecodable {
   var id: Int
   var name: String
   var sprites: Sprites
   var typeList: [TypeList]
   struct SpritesDecodable {
       var frontDefault: String
       private enum CodingKeysStringCodingKey {
           case frontDefault = "front_default"
       }
   }
   struct TypeListDecodable {
       var type: Type
       struct `Type`: Decodable {
           var name = "name"
           private enum CodingKeysStringCodingKey {
               case name = "name"
           }
       }
       private enum CodingKeysStringCodingKey {
           case type = "type"
       }
   }

   private enum CodingKeysStringCodingKey {
       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 TableViewControllerUIViewController {
   
   @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 TableViewControllerUITableViewDelegateUITableViewDataSource {
   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"asTableViewCell
       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 TableViewCellUITableViewCell {

   @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よりも手間が掛かる事が分かりました。
一度に情報を取得し過ぎると、スクロールするのにラグが発生して、一瞬止まる事も分かりました。
作り直すとしたら、取得件数を制限してラグを起こさない様に調整したいと思います。

参考文献


この記事が気に入ったらサポートをしてみませんか?