自作アプリを作ってみた -アプリ名: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画面の記事を投稿したいと思います。
この記事が何かの実装に参考になれば幸いです。