自作アプリを作ってみた -アプリ名:hanagasaki- 共通仕様(全画面対応機能、DB、Splash画面、BaseViewController、Codable仕様のモデル)

はじめに

自作アプリを作りましたので、画面毎の機能とコードを説明したいと思います。
今回、全画面に搭載している機能や、DBの設定、Splash画面、BaseViewController、Codable仕様のモデルのコードを説明が記載されています。

使用環境

● OS:macOS Big Sur 11.3.1
● Xcode:12.5
● Swift:5.4
● DB Browser for SQLite:3.12.1

コード

起動後、初めの画面:SplashViewController

//  SplashViewController.swift
import UIKit
import Lottie

class SplashViewController: UIViewController {

   // 1. Create the AnimationView
   private var animationView: AnimationView?
   private var swipeLockAnimation: AnimationView?
   
   @IBOutlet weak var splashImage: UIImageView! {
       didSet {
           splashImage.frame = CGRect(x: 60, y: 184, width: 200, height: 200)
       }
   }
   
   @IBOutlet weak var swipeView: UIView! {
       didSet {
           swipeView.frame = CGRect(x: 40, y: 444, width: 240, height: 55)
       }
   }
   
   override func viewDidLoad() {

     super.viewDidLoad()
       
     // 2. Start AnimationView with animation name (without extension)
     animationView = .init(name: "55478-hello-bubble")
     
       swipeLockAnimation = .init(name: "697-unlock")
       
     //animationView!.frame = view.bounds
       
       animationView!.frame = CGRect(x: 0, y: 100, width: 150, height: 150)
       swipeLockAnimation!.frame = CGRect(x: 40, y: 444, width: 240, height: 70)
     // 3. Set animation content mode
     
     //animationView!.contentMode = .scaleAspectFit
     
     // 4. Set animation loop mode
     
       animationView!.loopMode = .loop
       
       swipeLockAnimation!.loopMode = .loop
     // 5. Adjust animation speed
     
     animationView!.animationSpeed = 0.5
       swipeLockAnimation?.animationSpeed = 0.5
       //animationView!.frame = CGRect(x: 0, y: 437, width: 240, height: 65)
       
     view.addSubview(animationView!)
       view.bringSubviewToFront(animationView!)
       view.addSubview(swipeLockAnimation!)
         view.sendSubviewToBack(swipeLockAnimation!)
     // 6. Play animation
     
     animationView!.play()
   swipeLockAnimation!.play()
   }
   
   @IBAction func rightSwipe(_ sender: Any) {
       let storyboard = UIStoryboard(name: "TabBarController", bundle: nil)
        let TabBarController = storyboard.instantiateViewController(withIdentifier: "TabBarController")
        //present(SplashViewController, animated: true, completion: nil)
       TabBarController.modalPresentationStyle = .fullScreen
       present(TabBarController, animated: true)
        //ArticleEntryViewController.articleDataDelegate = self
        self.navigationController?.pushViewController(TabBarController, animated: true)
   }
   
}

画面の共通仕様:BaseViewController、TabBarController、HeaderBar、HambugerMenuView、HambugerMenuCell

//  BaseViewController.swift
import UIKit
import FMDB

protocol ArticleDataDelegate {
  func getAllArticleData()
}

class BaseViewController: UIViewController, ArticleDataDelegate {
   
   var hambugerMenuView = HambugerMenuView()
   
   let shadowView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
   
   let xibView = HambugerMenuView(frame: CGRect(x: 0, y: 0, width: 240, height: 568))
   
   let tabBarVC = UITabBarController()
   
   @IBOutlet weak var backGroundImage: UIImageView!
   
   override func viewDidLoad() {
       super.viewDidLoad()
       let tapShadowViewGestureRecognizer = UITapGestureRecognizer(target: self,
                                                                 action: #selector(tapCloseHambugerMenu))
       shadowView.addGestureRecognizer(tapShadowViewGestureRecognizer)
   }

   func addHeaderBar(_ headerTitle: String, _ headerLeftButton: UIBarButtonItem!, _ headerRightButton: UIBarButtonItem!) {
       var headerBar:HeaderBar
       headerBar = Bundle.main.loadNibNamed("Header", owner: self, options: nil)!.first! as! HeaderBar
       headerBar.title.title = headerTitle
       headerBar.title.leftBarButtonItem = headerLeftButton
       headerBar.title.rightBarButtonItem = headerRightButton
       self.view.addSubview(headerBar)
   }
   
   func moveSetHambugerMenu() {
       tabBarController?.tabBar.isHidden = true
       shadowView.backgroundColor = UIColor.black.withAlphaComponent(0.15)
       shadowView.isOpaque = false
       shadowView.isHidden = false
       view.addSubview(shadowView)
       xibView.backgroundColor = .white
       xibView.hambugerMenuTable.backgroundColor = .white
       xibView.isHidden = false
       view.addSubview(xibView)
   }
   
   func closeSetHambugerMenu() {
       shadowView.isHidden = true
       xibView.isHidden = true
       tabBarController?.tabBar.isHidden = false
   }
   
   func getAllArticleData() {
      _ = ModelManager.getInstance().getAllArticle()
   }
   
   @objc func tapped(){
       
       let vc1 = ProfileViewController()
       let vc2 = ArticleViewController()
       let vc3 = VideoViewController()
       let vc4 = QiitaViewController()
       let vc5 = BusinessCardViewController()
       
       tabBarVC.setViewControllers([vc1, vc2, vc3, vc4, vc5], animated: false)
       tabBarVC.modalPresentationStyle = .fullScreen
       present(tabBarVC, animated: true)
   }

   @objc func tapHambugerMenu() {
       moveSetHambugerMenu()
   }
   
   @objc func tapCloseHambugerMenu() {
       closeSetHambugerMenu()
   }
   
   @objc func tapAddArticle() {
      let storyboard = UIStoryboard(name: "ArticleEntry", bundle: nil)
       let ArticleEntryViewController = storyboard.instantiateViewController(withIdentifier: "ArticleEntryViewController")
       present(ArticleEntryViewController, animated: true, completion: nil)
       //ArticleEntryViewController.articleDataDelegate = self
       self.navigationController?.pushViewController(ArticleEntryViewController, animated: true)
   }
   
   @objc func tapAddVideo() {
       let storyboard = UIStoryboard(name: "VideoEntry", bundle: nil)
       let VideoEntryViewController = storyboard.instantiateViewController(withIdentifier: "VideoEntryViewController")
       present(VideoEntryViewController, animated: true, completion: nil)
   }
   
   @objc func tapQiitaIcon() {
       let url = URL(string: "https://qiita.com/")
       if UIApplication.shared.canOpenURL(url!) {
           UIApplication.shared.open(url!)
       }
   }
   
}

extension UIColor {
   /// 16進カラーコードでカラーを生成
   ///
   /// - Parameters:
   ///   - hex: 16進カラーコード
   ///   - alpha: アルファ値
   convenience init(hex: UInt, alpha: CGFloat = 1.0) {
       let red: CGFloat = CGFloat((hex & 0xFF0000) >> 16) / 255.0
       let green: CGFloat = CGFloat((hex & 0x00FF00) >> 8) / 255.0
       let blue: CGFloat = CGFloat(hex & 0x0000FF) / 255.0
       self.init(red: red, green: green, blue: blue, alpha: alpha)
   }
}
//  TabBarController.swift
import UIKit

class TabBarController: UITabBarController {

   override func viewDidLoad() {
       super.viewDidLoad()
       for vc in self.viewControllers! {
                   _ = vc.view
       }
   }
}
//  HeaderBar.swift
import UIKit

class HeaderBar: UINavigationBar {
   @IBOutlet weak var title: UINavigationItem!
   var hambugerMenuBar: UINavigationItem?
   @IBOutlet weak var leftBarButton: UIBarButtonItem!
   @IBOutlet weak var rightBarButton: UIBarButtonItem!
   
   override func draw(_ rect: CGRect) {
           let selfheight:CGFloat = 44
           let selfwidth:CGFloat = 320
           self.frame.size.height = selfheight
           self.frame.size.width = selfwidth

           self.frame.origin.x = 0
           self.frame.origin.y = 24
       
   }
   
}
//  HambugerMenuView.swift
import UIKit

class HambugerMenuView: UIView, UITableViewDelegate, UITableViewDataSource {
   
   @IBOutlet weak var hambugerMenuTable: UITableView! {
       didSet {
           hambugerMenuTable.rowHeight = UITableView.automaticDimension
       }
   }
   
   @IBOutlet weak var menuLabel: UILabel!
   
   @IBOutlet weak var osList: UILabel! {
       didSet {
           osList.textColor = .black
       }
   }
   
   @IBOutlet weak var osLabel: UILabel! {
       didSet {
           let osVerInput = UIDevice.current.systemVersion
           osLabel.text = "ver " + osVerInput
           osLabel.textColor = .black
       }
   }
   
   @IBOutlet weak var appList: UILabel! {
       didSet {
           appList.textColor  = .black
       }
   }
   
   @IBOutlet weak var appLabel: UILabel! {
       didSet {
           let version: String! = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
           appLabel.text =  "ver " + version
           appLabel.textColor = .black
       }
   }
   
   var menuListArray =
           [Menu(list: "Documentation(Swift)", url: "https://swift.org/documentation/#the-swift-programming-language"),
            Menu(list: "Qiita", url: "https://qiita.com/"),
            Menu(list: "Zenn", url: "https://zenn.dev/"),
            Menu(list: "note", url: "note://"),
            Menu(list: "OpenCV", url: "http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_tutorials.html"),
            Menu(list: "DXライブラリ", url: "https://dxlib.xsrv.jp/"),
            Menu(list: "Github", url: "https://github.co.jp/"),
            Menu(list: "GitLab", url: "https://gitlab.com/users/sign_in?__cf_chl_jschl_tk__=pmd_ca11442f89ffb50102868e3e64d13db8ebf03033-1628360577-0-gqNtZGzNAiKjcnBszQiO"),
            Menu(list: "Bitbucket", url: "https://bitbucket.org/"),
            Menu(list: "stackoverflow", url: "https://stackoverflow.com/")]
   
   override init(frame: CGRect) {
           super.init(frame: frame)
           loadNib()
       hambugerMenuTable.delegate = self
       hambugerMenuTable.dataSource = self

       let customCell = UINib(nibName: "HambugerMenuCell", bundle: nil)
       hambugerMenuTable.register(customCell, forCellReuseIdentifier: "HambugerMenuCell")
       }
       
       required init?(coder aDecoder: NSCoder) {
           super.init(coder: aDecoder)
           loadNib()
           //fatalErrorがデフォルトで入っていますが消さないとエラーになってしまうので注意してください!
       }
       func loadNib() {
           //CustomViewの部分は各自作成したXibの名前に書き換えてください
           let view = Bundle.main.loadNibNamed("HambugerMenuView", owner: self, options: nil)?.first as! UIView
           view.frame = self.bounds
           view.backgroundColor = .white
           hambugerMenuTable.backgroundColor = .white
           self.addSubview(view)
       }
   
   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return menuListArray.count
   }
   
   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
       return 50
   }
   
   func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
       let url = URL(string: menuListArray[indexPath.row].url)
       if UIApplication.shared.canOpenURL(url!) {
           UIApplication.shared.open(url!)
       }
   }
   
   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

       let cell = hambugerMenuTable.dequeueReusableCell(withIdentifier: "HambugerMenuCell", for: indexPath) as! HambugerMenuCell
       cell.menuList.text = menuListArray[indexPath.row].list
       return cell
   }
   
}

   
  
//  HambugerMenuCell.swift
import UIKit

class HambugerMenuCell: UITableViewCell {

   @IBOutlet weak var menuList: 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
   }

}

DB:AppDelegate、Util、ModelManager、DbModel

//  AppDelegate.swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
   
   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       // Override point for customization after application launch.
       
       
       Util.share.copyDatabase(dbName: "HanagasakiDB.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.
   }


}
//  Util.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.path)
      return fileUrl.path
  }
  
  func copyDatabase(dbName: String) {
      let dbPath = getPath(dbName: "HanagasakiDB.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.swift
import Foundation
import UIKit

var shareInstance = ModelManager()

class ModelManager {
  
  var database: FMDatabase? = nil
  
  static func getInstance() -> ModelManager {
      if shareInstance.database == nil {
       shareInstance.database = FMDatabase(path: Util.share.getPath(dbName: "HanagasakiDB.db"))
      }
      return shareInstance
  }
  // Save and delete from here
   func Save(article: modelArticle) -> Bool {
          shareInstance.database?.open()
          
       let isSave = shareInstance.database?.executeUpdate("INSERT INTO note(articleTitle, url) VALUES(?,?)", withArgumentsIn: [article.articleTitle!, article.url!])
          
          shareInstance.database?.close()
          return isSave!
   }
   
   func SaveVideo(video: modelVideo) -> Bool {
          shareInstance.database?.open()
          
       let isSave = shareInstance.database?.executeUpdate("INSERT INTO video(videoTitle, image, url) VALUES(?,?,?)", withArgumentsIn: [video.videoTitle!, video.image!, video.url!])
          
          shareInstance.database?.close()
          return isSave!
   }
   
   func getAllArticle() -> [modelArticle] {
       shareInstance.database?.open()
       var articles = [modelArticle]()
       do {
           let resultset : FMResultSet? = try shareInstance.database?.executeQuery("SELECT * FROM note", values: nil)
           if resultset != nil{
               while resultset!.next() {
                   let article = modelArticle(id: (resultset?.string(forColumn: "id")!), articleTitle: (resultset?.string(forColumn: "articleTitle")!), url: resultset?.string(forColumn: "url")!)
                   articles.append(article)
               }
           }
       }
       catch let err {
           print(err.localizedDescription)
       }
       shareInstance.database?.close()
       return articles
   }
   
   func getAllVideo() -> [modelVideo] {
       shareInstance.database?.open()
       var videos = [modelVideo]()
       do {
           let resultset : FMResultSet? = try shareInstance.database?.executeQuery("SELECT * FROM video", values: nil)
           if resultset != nil{
               while resultset!.next() {
                   let video = modelVideo(id: (resultset?.string(forColumn: "id")!), videoTitle: (resultset?.string(forColumn: "videoTitle")!), image: (resultset?.string(forColumn: "image")!), url: resultset?.string(forColumn: "url")!)
                   videos.append(video)
               }
           }
       }
       catch let err {
           print(err.localizedDescription)
       }
       shareInstance.database?.close()
       return videos
   }
}
//  DbModel.swift
import Foundation

struct modelArticle {
   var id: String?
   var articleTitle: String?
   var url: String?
}

struct modelVideo {
   var id: String?
   var videoTitle: String?
   var image: String?
   var url: String?
}

struct Qiita: Codable {
   let title: String
   let created_at: String
   let user: User
   let url: String
   
   enum CodingKeys: String, CodingKey {
       case title = "title"
       case created_at = "created_at"
       case user = "user"
       case url = "url"
   }
}

struct User: Codable {
   let name: String
   let profileImageUrl: String
   
   enum CodingKeys: String, CodingKey {
       case name = "name"
       case profileImageUrl = "profile_image_url"
   }
}

struct Menu {
   var list: String
   var url: String
}

おわりに

最後まで読んで下さりありがとうございます。
次回からは各画面の機能とコードを説明していこうと思います。
最初は、profile画面の記事を投稿したいと思います。
この記事が何かの実装に参考になれば幸いです。

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