独創アプリ開発日記 59日目 テキスト漫画の一発コピー

今日はテキスト漫画のサイトを改良していました。テキスト漫画のガワネイティブアプリを出すにあたり、操作性を向上させるための改良です。

今までテキスト漫画をコピーして使用する場合は、「1回だけ必要なコード」と「テキスト漫画を表示したい箇所に配置するコード」の二つをそれぞれコピーして貼り付ける必要がありました。

これを改善して、1回コピーリンクをクリックするだけでコピー出来るようになりました!

ブラウザによってはクリップボードへのコピーに対応していないものもあります。あなたのブラウザで出来るか試してみましょう!!

以下はコピーリンクをクリック後に貼り付けたテキスト漫画です!簡単!!

ヽ(`д´)ノ うわーん

ヾ(゚д゚*三*゚д゚)ノワァイ

ログイン機能が復活したよ
ヽ(´▽`)ノワーイ

      |ハ,_,ハ
      |´∀`’;/^l
      |u”’^u;’  |
      |∀ `  ミ
      |  ⊂  :, 
      |     ミ
      |    彡
      |    ,:’
      |”~””∪

明日は昨日予定していた、画面のサイズ以外のスマホ判定方法を調べようと思います。

独創アプリ開発日記 58日目 レスポンシブデザイン 第一歩

テキスト漫画のガワアプリを出すにあたって、既存のサイトのデザインをスマホ用に切り替える必要があります。

今回はその第一歩として、スマホの場合はサイトに適用しているCSSのほとんどを取り払うことにしました。

はい、ご覧のように背景の絵や色が消えましたね。ここからスマホ専用にデザインをして行きます。

スマホの判定は、画面の横幅640ピクセル以下の場合としています。コードは以下のようになります。ビューポートの設定をしてから、@media screen and … の部分で判定しています。PCでもウィンドウの幅を狭めると真っ白な画面になります。


<head>
    <meta charset="utf-8">
    <!-- ビューポートの設定 -->
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<title>TextManga</title>
<style>
        @media screen and (min-width: 641px) {
            // ここにPC用のCSS
        }
        @media screen and (max-width: 640px) {
            //ここにスマホ用のCSS
        }
</style>
        // 以下略

・・・

・・

横向きにしたらPC用のデザインに戻っちゃった・・・

画面のサイズ以外にもスマホを判定する方法ってあるのかしら?また明日調べよう。簡単に判定出来ると良いなぁ。

独創アプリ開発日記 57日目 iPhoneX完全全面ガワアプリ

前回まででプログレスバーを含めたWebViewのガワアプリの基本的な部分を完成したつもりだったのですが、よく確認したところ、iPhoneXで画面下にわずかにWebページが表示されない隙間が出来ていました。

原因を調べまくったところ、どうやらcontentInsetという、領域内にスペースを自動で差し込む処理が存在したようです。そこで、その領域を取ることで本当の本当に、iPhoneXの隅から隅まで完全な全面表示になりました。


import UIKit
import WebKit

class ViewController: UIViewController {

    private var _webView : WKWebView!
    private var _hideStatusBar : Bool = false
    private var _iPhoneX_Header : CGFloat = 0
    private var _progressView: UIProgressView!
    
    func iPhoneX() -> Bool {
        guard #available(iOS 11.0, *) else {
            return false
        }
        return UIApplication.shared.windows[0].safeAreaInsets != UIEdgeInsets.zero
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // ステータスバーの高さを取得
        _iPhoneX_Header = iPhoneX() ? UIApplication.shared.statusBarFrame.size.height : 0
        // WKWebViewを位置指定して表示
        let webC = WKWebViewConfiguration()
        _webView = WKWebView(frame:CGRect(x:0, y: -_iPhoneX_Header, width:self.view.bounds.size.width, height:self.view.bounds.size.height + _iPhoneX_Header), configuration: webC)
        //_webView.scrollView.contentInsetAdjustmentBehavior = .always
        // ジェスチャー(フリック)による 進む や 戻る を許可
        _webView.allowsBackForwardNavigationGestures = true
        // WebViewの読込監視
        _webView.addObserver(self, forKeyPath:"estimatedProgress", options:.new, context:nil)
        // ステータスバーの非表示 ※ステータスバーの高さを取得する前に非表示にすると、ステータスバーの高さが取得できない
        _hideStatusBar = true
        self.setNeedsStatusBarAppearanceUpdate()
        // テキスト漫画読み込み
        let url : URL = URL(string: "https://minnano.app/textmanga/")!
        let request : URLRequest = URLRequest(url: url)
        _webView.load(request)
        self.view.addSubview(_webView)
        // ProgressView
        _progressView = UIProgressView(frame: CGRect(x:0, y:0, width:self.view.bounds.size.width * 2, height:4))
        _progressView.layer.position = CGPoint(x:0, y:self.view.bounds.size.height - 1)
        _progressView.transform = CGAffineTransform(scaleX: 1.0, y: 2.0)
        _progressView.progressTintColor = UIColor.green
        _progressView.trackTintColor = UIColor.orange
        self.view.addSubview(_progressView)
        // 回転時の通知
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.orientationChange(notification:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }
    deinit {
        _webView?.removeObserver(self, forKeyPath: "estimatedProgress")
    }
    
    @objc
    private func orientationChange(notification: NSNotification) {
        let portrait : Bool = UIInterfaceOrientationIsPortrait(UIApplication.shared.statusBarOrientation)
        // WebViewの表示調整
        _webView.frame.origin = CGPoint(x:portrait ? 0 : -_iPhoneX_Header, y:portrait ? -_iPhoneX_Header : 0)
        _webView.frame.size = CGSize(width:self.view.bounds.size.width + (portrait ? 0 : _iPhoneX_Header * 2), height:self.view.bounds.size.height + (portrait ? _iPhoneX_Header : 0))
        _webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: portrait ? -_iPhoneX_Header * 4 / 5 : -_iPhoneX_Header / 2, right: 0)
        //_webView.scrollView.contentInset = UIEdgeInsets(top: portrait ? _iPhoneX_Header : 0,
        //                                                left: portrait ? 0 : _iPhoneX_Header,
        //                                                bottom: 0,
        //                                                right: portrait ? 0 : _iPhoneX_Header)
        // プログレスバーの表示調整
        _progressView.frame.origin = CGPoint(x: 0, y: 0)
        _progressView.frame.size = CGSize(width:self.view.bounds.size.width * 2, height:4)
        _progressView.layer.position = CGPoint(x:0, y:self.view.bounds.size.height - 1)
    }
    
    // ステータスバーの非表示
    override var prefersStatusBarHidden: Bool {
        return _hideStatusBar
    }
    
    // WebViewの監視
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        if("estimatedProgress" == keyPath){
            if(_progressView != nil){
                // 更新
                let progress : Float = Float(_webView.estimatedProgress)
                _progressView.setProgress(progress < 1.0 ? progress : 0.0, animated: progress < 1.0)
                _progressView.alpha = progress < 1.0 ? 1.0 : 0.0  // 読み込みが完了したら非表示に
            }
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

下記は上のコードのcontentInset処理部分の抜粋です。 UIEdgeInsetsで余白を調整しています。4 / 5 とか 1 / 2 は、どうしてそうなるのかはわからないのですが、ピッタリ画面端までWebが表示される値です。

誰かどうしてこうなるのか教えてください。


_webView.frame.origin = CGPoint(x:portrait ? 0 : -_iPhoneX_Header, y:portrait ? -_iPhoneX_Header : 0)
        _webView.frame.size = CGSize(width:self.view.bounds.size.width + (portrait ? 0 : _iPhoneX_Header * 2), height:self.view.bounds.size.height + (portrait ? _iPhoneX_Header : 0))
        _webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: portrait ? -_iPhoneX_Header * 4 / 5 : -_iPhoneX_Header / 2, right: 0)

ちょーすっきりした。

独創アプリ開発日記 56日目 プログレスバー

今日は、昨日の続きでプログレスバーの表示対応です。

以下ViewController.swiftのコードです。画面の下端に表示しました。ページ読み込み開始時はオレンジ色で、読み込むと共に緑色になって行きます。


import UIKit
import WebKit

class ViewController: UIViewController {

    private var _webView : WKWebView!
    private var _hideStatusBar : Bool = false
    private var _iPhoneX_Header : CGFloat = 0
    private var _progressView: UIProgressView!
    
    func iPhoneX() -> Bool {
        guard #available(iOS 11.0, *) else {
            return false
        }
        return UIApplication.shared.windows[0].safeAreaInsets != UIEdgeInsets.zero
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // ステータスバーの高さを取得
        _iPhoneX_Header = iPhoneX() ? UIApplication.shared.statusBarFrame.size.height : 0
        // WKWebViewを位置指定して表示
        _webView = WKWebView(frame:CGRect(x:0, y: -_iPhoneX_Header, width:self.view.bounds.size.width, height:self.view.bounds.size.height + _iPhoneX_Header))
        // ジェスチャー(フリック)による 進む や 戻る を許可
        _webView.allowsBackForwardNavigationGestures = true
        // WebViewの読込監視
        _webView.addObserver(self, forKeyPath:"estimatedProgress", options:.new, context:nil)
        // ステータスバーの非表示 ※ステータスバーの高さを取得する前に非表示にすると、ステータスバーの高さが取得できない
        _hideStatusBar = true
        self.setNeedsStatusBarAppearanceUpdate()
        // テキスト漫画読み込み
        let url : URL = URL(string: "https://minnano.app/textmanga/")!
        let request : URLRequest = URLRequest(url: url)
        _webView.load(request)
        self.view.addSubview(_webView)
        // ProgressView
        _progressView = UIProgressView(frame: CGRect(x:0, y:0, width:self.view.bounds.size.width * 2, height:4))
        _progressView.layer.position = CGPoint(x:0, y:self.view.bounds.size.height - 1)
        _progressView.transform = CGAffineTransform(scaleX: 1.0, y: 2.0)
        _progressView.progressTintColor = UIColor.green
        _progressView.trackTintColor = UIColor.orange
        self.view.addSubview(_progressView)
        // 回転時の通知
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.orientationChange(notification:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }
    deinit {
        _webView?.removeObserver(self, forKeyPath: "estimatedProgress")
    }
    
    @objc
    private func orientationChange(notification: NSNotification) {
        let portrait : Bool = UIInterfaceOrientationIsPortrait(UIApplication.shared.statusBarOrientation)
        // WebViewの表示調整
        _webView.frame.origin = CGPoint(x:portrait ? 0 : -_iPhoneX_Header, y:portrait ? -_iPhoneX_Header : 0)
        _webView.frame.size = CGSize(width:self.view.bounds.size.width + (portrait ? 0 : _iPhoneX_Header * 2), height:self.view.bounds.size.height + (portrait ? _iPhoneX_Header : 0))
        // プログレスバーの表示調整
        _progressView.frame.origin = CGPoint(x: 0, y: 0)
        _progressView.frame.size = CGSize(width:self.view.bounds.size.width * 2, height:4)
        _progressView.layer.position = CGPoint(x:0, y:self.view.bounds.size.height - 1)
    }
    
    // ステータスバーの非表示
    override var prefersStatusBarHidden: Bool {
        return _hideStatusBar
    }
    
    // WebViewの監視
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        if("estimatedProgress" == keyPath){
            if(_progressView != nil){
                // 更新
                let progress : Float = Float(_webView.estimatedProgress)
                _progressView.setProgress(progress < 1.0 ? progress : 0.0, animated: true)
                _progressView.alpha = progress < 1.0 ? 1.0 : 0.0  // 読み込みが完了したら非表示に
            }
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

iPhoneXの全面表示や画面の回転にも対応しています。

テキスト漫画はその名の通りテキストばかりで読み込み早いから、

せっかく作ったけどプログレスバーは要らないかなぁ・・・

独創アプリ開発日記 55日目 Web全面表示

今日はプログレスバーを表示する予定だったのですが、その前に下準備を行いました。

まずは将来的に手軽に画面遷移出来るよう、AppDelegate.swiftを編集してナビゲーションコントローラを追加しました。


class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var _navC: UINavigationController?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        // ナビコントローラ作成
        _navC = UINavigationController(rootViewController: ViewController())
        _navC?.isNavigationBarHidden = true
        // windowを生成
        self.window = UIWindow(frame: UIScreen.main.bounds)
        // rootViewControllerにナビコントローラを指定
        self.window?.rootViewController = _navC
        // windowを表示
        self.window?.makeKeyAndVisible()
        return true
    }
・・・

次に、時計やバッテリーを非表示にしてフル全面Web表示にしました。iPhoneXの場合、標準で凹んでいる部分は除外してViewを表示するような作りになっているようです。

iPhoneXの場合は凹んだ部分にもWebを表示するよう、以下のようにViewController.swiftを編集しました。


import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {

    private var _webView : WKWebView!
    private var _hideStatusBar : Bool = false
    
    func iPhoneX() -> Bool {
        guard #available(iOS 11.0, *) else {
            return false
        }
        return UIApplication.shared.windows[0].safeAreaInsets != UIEdgeInsets.zero
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // ステータスバーの高さを取得
        let statusBarHeight = iPhoneX() ? UIApplication.shared.statusBarFrame.size.height : 0
        // WKWebViewを位置指定して表示
        _webView = WKWebView(frame:CGRect(x:0, y: -statusBarHeight, width:self.view.bounds.size.width, height:self.view.bounds.size.height + statusBarHeight))
        // ジェスチャー(フリック)による 進む や 戻る を許可
        _webView.allowsBackForwardNavigationGestures = true
        // テキスト漫画読み込み
        let url : URL = URL(string: "https://minnano.app/textmanga/")!
        let request : URLRequest = URLRequest(url: url)
        _webView.load(request)
        // 表示
        self.view.addSubview(_webView)
        // ステータスバーの非表示 ※ステータスバーの高さを取得する前に非表示にすると、ステータスバーの高さが取得できない
        _hideStatusBar = true
        self.setNeedsStatusBarAppearanceUpdate()
    }
    
    override var prefersStatusBarHidden: Bool {
        return _hideStatusBar
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

以上で、iPhoneXの凹んでいる部分も含めて、完全全面Web表示になりました。

 

独創アプリ開発日記 54日目 WKWebView

今日はガワネイティブアプリの実装の1歩目を行いました。

Xcode(9.2)を起動して「Create a new Xcode project」から「Single View App」を選択します。ガワネイティブアプリで、特にネイティブ部分の処理速度は求められないため、言語はSwiftにしました。

出来上がった雛形の「ViewController.swift」のコードを以下のように書き換えました。これだけで、最低限のガワネイティブアプリの完成です!
ヽ(´▽`)ノわーい


import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {

    var _webView : WKWebView = WKWebView()
    
    override func loadView() {
        self.view = _webView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let url : URL = URL(string: "https://minnano.app/textmanga/")!
        let request : URLRequest = URLRequest(url: url)
        _webView.allowsBackForwardNavigationGestures = true
        _webView.load(request)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

早速シミュレータで実行します。

 

全面表示です。時計やバッテリーが表示されています。スクロールすると表示が被ってしまいますが、シンプルで良い感じです。

これでアプリストアに上げて良いですか?ダメですかそうですか。

次回は、時計やバッテリーを非表示にして、ページ読み込み状況を表すプログレスバーを追加したいと思います。

独創アプリ開発日記 53日目 ガワネイティブ

今日はWebサービスであるテキスト漫画をアプリ化するにあたり、iOSで”ガワネイティブ”アプリの実装方法について調べていました。

ガワネイティブとは、アプリの内部にWebページを表示して、そのWebページを組み込む枠だけがネイティブで、大部分をWeb(HTML,Javascript,CSS)で表示して手軽に内容を更新出来て、かつネイティブ機能の利用も可能になってしまうという、とても欲張りな仕組みです。

もちろんデメリットもあって、ゲームなどであまりに凝ったことをやろうとすると動作が遅いことですね。2Dのゲームならあんまり気にならないかと思いますし、実際にガワネイティブで作られている2Dゲームはいっぱいありますね。

テキスト漫画は文字の表示が切り替わるだけなので、ガワネイティブで十分です。

iOSだとWebViewっていうUIを使えば手軽に実装できそうです。あとはレスポンシブデザインにして、スマホ用に表示を切り替えるだけだな〜〜。

iOSやAndroidに関係なく、共通でプログラムが動くって素敵です。

ビートルズのイマジンが頭の中に流れてきます。

独創アプリ開発日記 52日目 テキスト漫画アプリ

今日は、せっかく復活したテキスト漫画を手軽に利用できるようにスマホアプリ(iOS)を作ろうと思い立ち、そのアプリの概要を考えていました。

以下箇条書きにします。

・Web版はログインしないと自分専用のテキスト漫画を作れませんが、アプリ版は何もしなくても初めから自分専用のテキスト漫画が作れるようにします。これは、iOSがIDFV(Identifier For Vender)でユーザを識別できるため可能になります。

・サーバ側の処理はWeb版となるべく共通にし、Viewだけ変えるようにしたいと思います。

・広告モデルの無料アプリにしたいと思います。

手軽にサクッと作りたいと思います。

独創アプリ開発日記 51日目 タイムゾーンの確認

昨日テキスト漫画のサイトが完全復旧したのですが、一つ気がかりなことがあります。

なぜかphpMyAdmin(DBを管理するツール)で見ると、テキスト漫画のサイトで利用しているDBレコードの更新時間が9時間未来になってしまいます。

ただしDB上でズレているだけで、テキスト漫画のサイト上で表示される時間はちゃんと更新した時間になっています。また、他のアプリ(ねこマタ)で利用しているDBレコードもちゃんとした時間になっています。

タイムゾーン設定が怪しいので確認してみました。

— OS(CentOS) —

$ date
Sun Dec  3 11:07:43 JST 2017

OSは JST(日本標準時) ですね。

— MariaDB —

MariaDB [text_manga]> select NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2017-12-03 11:11:34 |
+---------------------+
1 row in set (0.00 sec)

あれ?ちゃんとした時間になっています。DBも日本時間だ。

MariaDB [text_manga]> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+

system_time_zone は ホストマシンのタイムゾーンを設定するらしく、OSと同じです。time_zone は サーバの現在のタイムゾーン。初期値は’SYSTEM’で、サーバのタイムゾーンがシステムタイムゾーンと同じことを表すみたいです。こちらも日本時間で問題なさそう。

— php —

echo date('Y-m-d H:i:s');
echo date_default_timezone_get();

> 2017-12-03 11:14:39
> Asia/Tokyo

あれ?PHPも問題なし?全部日本時間で揃ってる?
(σ´∀`)

日本時間を指定して保存 > DBには未来の時間で保存(+9時間) > 読込時、保存時間に戻って取得(-9時間)

で、結局書き込んだ正しい時間で読み込み出来るから問題ないけど、気持ち悪いな・・・

mariaDBのドライバが、DBのタイムゾーンみてわざわざ+9時間分の調整してくれてるのかしら?

考えるのめんどいです。

もう時間の存在しない世界に行きたいです。

独創アプリ開発日記 50日目 TextManga完全復活

今日もテキスト漫画のサイトを復旧していました。50回目の今回はアカウント作成ページを復活しました。

これで全ての機能が復活しました。サイト完全復活です。

アカウントを作成してログインすると、自分専用のテキスト漫画が作成出来ます。「自分専用のみ表示」などの機能も使えます。自分専用のAA集を作成するのも良いのではないでしょうか。

自分専用にするには、ログインした状態で、初回保存時に「自分専用」を選択して保存して下さいね。

「だれでも編集」のリンクから編集できるテキスト漫画は、その名の通り誰でも変更できます。他の人の作ったテキスト漫画を改良してあげるのも良いかもしれません。

使い方は様々です。是非ご利用下さい。