文系プログラマの勉強ノート

スマホアプリ開発やデザインなどについて勉強したことをまとめています

自宅用Macを新調した話

最近自宅用のMacを新調しました。

というのも先代のMacはかれこれ6年ちょっと使っていたもので、ここ1〜2年は大分動作が重く、Xcodeなどの開発環境は起動するのも億劫になるほど…。

そのため、今回は開発環境が快適に使えることを基準にカスタマイズしたところ、無事軽快に動くようになりました!これから開発用マシンを購入する方のご参考までに。

  • マシン:(旧)IMac2011年モデル→(新)iMac 2017年モデル
  • メモリ:(旧)4GB→(新)16GB
  • ストレージ:(旧)HDD512GB→(新)SSD 512GB

なかなかの出費ではありましたが、これで技術的に気になっていたことを色々試せるので嬉しいです。技術を磨いてお仕事に繋げたいと思います。業務では未だにObjective-CJavaがメインですが、最近Swift4やKotlinなどが気になっています。

Macからのデータ移行は、TimeMachineを使わずに必要なデータだけ外付けHDDに入れて移動しました。TimeMachineは楽ですが、余計なファイルや設定を引き継いでしまう可能性があるということで。

下記のページを参考に、一旦新しいMacとして初期設定した後、書類、iTunes、写真などを移行しました。

support.apple.com

Mac自体は新しいものとして設定した上で、特定のアプリケーションだけTimeMachineから復元するということもできるようです。

LaunchpadのいらないGoogle系アプリを削除する

f:id:an3714106:20180630223031p:plain

MacChromeをインストールすると、入れた覚えもないのに「YouTube」などのGoogle系アプリがLaunchpadに表示されます。ただでさえごちゃごちゃしやすいLaunchpad、不要なものは置いておきたくないので、以下のサイトを参考に削除しました。

参考サイト

ore-ch.com


削除手順

1. Chromeを開き、左上の「アプリ」をクリック
f:id:an3714106:20180630223257p:plain

2.不要なアプリを右クリック→「Chromeから削除…」をクリック
f:id:an3714106:20180630223404p:plain


f:id:an3714106:20180630223518p:plain
これでLaunchpadからもアプリが削除されます。

【PHP】PHPからSQLを実行する

PHPからPDO(PHP Data Object)を使用してSQLを実行する手順です。
お決まりの手順ですが、たまにやると手順が抜けたり順番を忘れたりするので、まとめておきます。

トランザクションなしの場合

データベースからデータを検索するサンプルです。

// DB接続
try {
    // ①データベースハンドラ作成
    $pdo = new PDO('mysql:host=localhost;dbname=sampledb;charset=utf8', 'username', 'password');
    // ②エラー属性設定
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  // 例外を発生させる
} catch (PDOException $Exception) {
    die('接続エラー :' . $Exception->getMessage());
}

// 処理実行
try {
    // ③SQL作成
    $sql = "select * from member";
    // ④ステートメントハンドラ作成、処理を実行
    $smth = $pdo->query($sql);
    // 結果を表示
    print "登録されているデータは全部で".$smth->rowCount()."件です。<br>";
} catch (PDOException $Exception) {
    print "エラー:" . $Exception->getMessage();
}
①データベースハンドラ作成

DSN(Data Source Name)を指定してデータベースに接続します。
DSNは以下のような構文で、データベースの種類によって指定できる項目が若干変わります。
(サンプルはMySQLの場合です)

<DSN接頭辞>:host=<ホスト名>;dbname=<データベース名>;charset=<文字コード>
②エラー属性設定

エラーが発生した際にtry catchで例外を検出できるように設定します。

SQL作成

実行したいSQLを記述します。

ステートメントハンドラ作成、処理を実行

このサンプルは用意したSQLをそのまま実行するので、$pdo->query($sql)を使いました。
SQL中に変数を含む場合はプリペアドステートメントというものを使用します。
(後述の「トランザクションありの場合」の方をご覧ください)

ステートメントハンドラ($smth、PDOStatementクラス)を使って実行結果を取得できます。

トランザクションありの場合

トランザクション処理を利用することで、処理実行中は他のユーザーのデータベース更新処理がブロックされます。
また、処理に失敗した場合、ロールバック処理で元の状態に戻すことができます。
データの新規登録、更新、削除などではトランザクション処理を利用すべきでしょう。

トランザクション処理を利用する場合は、処理を$pdo->beginTransactionと$pdo->commit()で囲みます。

以下は、データを新規登録するサンプルです。

// DB接続
try {
    // ①データベースハンドラ作成
    $pdo = new PDO('mysql:host=localhost;dbname=sampledb;charset=utf8', 'username', 'password');
    // ②エラー属性設定
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  // 例外を発生させる
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  // プリペアドステートメントを利用
} catch (PDOException $Exception) {
    die('接続エラー :' . $Exception->getMessage());
}

// 処理実行
try {
    // ③トランザクション開始
    $pdo->beginTransaction();
    // ④SQL、ステートメントハンドラ作成
    $sql = "insert into member (username, password, register_date) values(:username, :password, now())";
    $smth = $pdo->prepare($sql);
    // ⑤データをバインド
    $smth->bindValue(':username', 'test@example.com');
    $smth->bindValue(':password', 'test');
    // ⑥処理を実行
    $smth->execute();
    // ⑦変更を確定
    $pdo->commit();
    // 結果を表示
    print "データを".$smth->rowCount()."件挿入しました。<br>";
} catch (PDOException $Exception) {
    // ⑧元の状態に戻す
    $pdo->rollBack();
    print "エラー:" . $Exception->getMessage();
}
①データベースハンドラ作成

同上。

②エラー属性設定

例外発生の設定に加え、プリペアドステートメント利用の設定をします。

トランザクション開始

処理実行の前にトランザクションを開始します。

SQLステートメントハンドラ作成

初期値を指定してデータを新規登録するSQLを記述します。
最初の()内はカラム名、後の()は初期値です。

username, passwordはログインフォームなどでユーザの入力値を使うことを想定しています。
この場合は、名前付きプレースホルダ(:username, :password)を指定し、後で変数と関連付けます。

register_dateは現在時刻を使うことを想定しています。
このようにメソッドで取得できる値や決め打ちの値を使う場合は、SQL中に直接書けます。

⑤データをバインド

④の名前付きプレースホルダに変数の値を結びつけます。

⑥処理を実行

準備ができたので、SQLを実行します。

⑦変更を確定

変更を確定します。

⑧元の状態に戻す

処理の途中でエラーが発生した場合、$pdo->rollBack()を呼んで処理を元に戻します。

【PHP】データを次の画面以降に引き継ぐ方法

画面には表示しない内部データを次の画面に引き継ぎたいとき、いくつかある方法のサンプルコードや特徴、使い分け方について勉強したことをまとめます。
まだざっくりとしか調べていないので、間違っているところがあればご指摘いただければ幸いです。

1. hidden

使い方
// 渡し方
<form name="form1" method="post" action="confirm.php">
    <p>
        <label for="name">タイトル:</label><input type="text" name="title">
    </p>
    <p>
        <label for="name">本文:</label><textarea name="body_text" cols="30" rows="5"></textarea>
    </p>

    <input type="hidden" name="login_id" value="abc123">
    <p>
        <input type="submit" value="確認">
    </p>
</form>

// 受け取り方
<?php
    print "<p>";
    print "タイトル:".$_POST["title"];
    print "</p>";

    print "<p>";
    print "本文:".nl2br($_POST["body_text"]);
    print "</p>";

    print "<p>";
    print "ログインID:".$_POST["login_id"];
    print "</p>";
?>
メリット

・セッションなどの知識がなくても簡単に利用できる

デメリット

・<form>タグ内でしか使えない
ソースコードを表示すると値が見えてしまう
・2画面以上先へ引き継げない

⇒フォームを使用した画面遷移で、値を簡易に引き継ぎたい時などに使える

2.cookie(クッキー)

使い方
// 渡し方
<?php
    // クッキーを送信(有効期限30日)
    setcookie("login_id", "id00001", time() + 60 * 60 * 24 * 30);
?>
// 受け取り方
<?php
    if (isset($_COOKIE["login_id"])) {
        print "<p>";
        print "ログインID:".$_COOKIE["login_id"];
        print "</p>";
    }
?>
// 消し方
<?php
    // 有効期限に現在よりも前の時間を設定
    setcookie("login_id", "", time() - 60);
?>
メリット

・一度セットした値を複数画面で使える

デメリット

・クライアント側に保存されるため、改竄が可能である
・一つのドメインで保存できる数に上限がある(上限数はブラウザによって異なる)

⇒利用者の識別情報、セッションIDなど、クライアント側で保存しておきたい情報に使える

3.session(セッション)

使い方
// 渡し方
<?php
    session_start();

    // 有効期限30日
    session_cache_expire(60 * 24 * 30);
    $_SESSION["login_id"] = "id00001";
?>
// 受け取り方
<?php
    session_start();

    if (isset($_SESSION["login_id"])) {
        print "<p>";
        print "ログインID:".$_SESSION["login_id"];
        print "</p>";
    }
?>
// 消し方
<?php
    session_start();

    // セッション変数をすべて削除
    session_unset();
    // セッションIDおよびデータを破棄
    session_destroy();
?>
メリット

・一度セットした値を複数画面で使える
・サーバ側で保存するため、データ改竄の心配がない

デメリット

・サーバ上にセッションファイルを保存するため、適切な権限の設定が必要=やや知識が必要


⇒ログインにより取得したユーザ情報、ショッピングサイトの買い物かごデータなどに使える
(接続から切断までの一連の通信がセッションの典型例としてよく挙げられていることから、
クッキーよりも短期間、一時的なものという印象を受けました)


大雑把な使い分けとしては、

  • フォームで次の画面まで引き継ぎたい&ユーザに見えても構わない情報→hidden
  • 偽装されてはいけない情報→session
  • 偽装されても問題ない情報、接続時にサーバへ送りたい情報→cookie

という感じでしょうか。

Dockerで PHP+MySQLの環境構築

2018年になりました。今年はスマホアプリだけでなくWeb開発にも挑戦していきたいと思います。

ということで手始めに『PHP+MySQLマスターブック』でPHPを勉強することにしました。
book.mynavi.jp

PHP5.4&MySQL5.5対応と少しバージョンが古いですが、ログイン機能を持つ会員管理システムが作れる、Smarty(テンプレートエンジン)にも触れているということで、一冊終わる頃には実用的な知識が身につきそうです。

同書ではXAMMPの使用を推奨していますが、今回はDockerを使って環境構築しました。以下、その手順です。

Dockerとは

Dockerについては以下のサイトがわかりやすいです。本記事ではDockerのインストールは済んでいることを前提とします。
employment.en-japan.com

構築する環境

  • CentOS 7.7(2018.1時点の最新版)
  • Apache 2.4.3(2018.1時点の最新版)
  • PHP 5.4
  • MySQL 5.5

1.コンテナの準備

CentOS最新版のイメージを取得します。

$ docker pull centos

centosイメージからコンテナを作成・起動します。

$ docker run --privileged -it -d -p 8080:80 -p 3306:3306 -v "/Users/yourname/htdocs/:/var/www/html/" --name MySqlTest centos /sbin/init

色々オプションがついていますが、それぞれの意味は次の通りです。

  • --privileged:特権モードを指定。systemctlコマンドを使用するために付加
  • -it:コンテナ内で操作できる
  • -d :コンテナの実行をバックグラウンドで行う
  • -p 8080:80:ローカルマシンの8080番ポートとコンテナの80番ポートを紐付ける。これによりローカルマシンのWebブラウザから"http://localhost:8080/"でWebページにアクセス可能となる
  • -p 3306:3306:同上。3306番ポートはMySQLのポート番号
  • -v "/Users/yourname/htdocs/:/var/www/html/":ローカルマシンの任意のディレクトリ(htdocs)とコンテナのドキュメントルート(/var/www/html/)を紐付ける
  • --name MySqlTest:コンテナの名前
  • /sbin/init :systemctlコマンドによるOS起動時のサービス自動起動のために付加

以下のコマンドでMySqlTestが表示されれば、正しくコンテナを起動できています。

$ docker ps

作成したコンテナにログインします。

$ docker exec -it MySqlTest /bin/bash

タイムゾーンを日本時間に変更します。

# timedatectl set-timezone Asia/Tokyo
# timedatectl

2.Apacheのインストール

Apache最新版をインストールします。

# yum install -y httpd

httpd.confを編集します。今回は開発用に必要な部分しか設定していません。
念のためオリジナルを複製しておきます。

# cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.bak
# vi /etc/httpd/conf/httpd.conf

<Directory "/var/www/html">
    # ファイル一覧を非表示
    Options -Indexes +FollowSymLinks

    # .htaccessを利用するため
    AllowOverride All

    Require all granted
</Directory>

<IfModule dir_module>
    # index.phpを省略可
    DirectoryIndex index.php index.html
</IfModule>

Apacheを起動します。また、OS再起動時に自動起動するように設定しておきます。

# systemctl start httpd.service
# systemctl enable httpd.service

webブラウザから"http://localhost:8080"にアクセスし、Apacheのデフォルトページが表示されればOKです。

3.PHPのインストール

PHP5.4とその他必要なライブラリをインストールします。
CentOS7のPHPは標準で5.4なので特にバージョンは指定しなくて大丈夫です。

# yum install -y php php-mbstring php-mysql php-mcrypt

php.iniを編集します。

# cp /etc/php.ini /etc/php.ini.bak
# vi /etc/php.ini

default_charset = "UTF-8"
date.timezone = "Asia/Tokyo"

mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
mbstring.http_input = UTF-8
mbstring.http_output = pass
mbstring.encoding_translation = On
mbstring.substitute_character = none;

php.iniを保存したら、変更を反映するためApacheを再起動します。

# systemctl restart httpd.service

以下のコードをphpinfo.phpという名前でドキュメントルート(本記事だと"/Users/yourname/htdocs/")に保存します。

<?php
phpinfo();
?>

webブラウザから"http://localhost:8080/phpinfo.php"にアクセスし、PHPに関する情報が表示されればOKです。

4.MySQLのインストール

CentOS7には標準でMariaDBがインストールされていますので、競合を防止するために削除します。

# yum remove -y mariadb-libs
# rm -rf /var/lib/mysql/

リポジトリを追加します。

# yum install -y http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

インストールしたリポジトリを見るとMySQL5.7がenableになっているので、5.5に変更します。

# yum repolist all | grep mysql

mysql-cluster-7.5-community/x86_64 MySQL Cluster 7.5 Community     disabled
mysql-cluster-7.5-community-source MySQL Cluster 7.5 Community - S disabled
mysql-cluster-7.6-community/x86_64 MySQL Cluster 7.6 Community     disabled
mysql-cluster-7.6-community-source MySQL Cluster 7.6 Community - S disabled
mysql-connectors-community/x86_64  MySQL Connectors Community      enabled:   42
mysql-connectors-community-source  MySQL Connectors Community - So disabled
mysql-tools-community/x86_64       MySQL Tools Community           enabled:   55
mysql-tools-community-source       MySQL Tools Community - Source  disabled
mysql-tools-preview/x86_64         MySQL Tools Preview             disabled
mysql-tools-preview-source         MySQL Tools Preview - Source    disabled
mysql55-community/x86_64           MySQL 5.5 Community Server      disabled
mysql55-community-source           MySQL 5.5 Community Server - So disabled
mysql56-community/x86_64           MySQL 5.6 Community Server      disabled
mysql56-community-source           MySQL 5.6 Community Server - So disabled
mysql57-community/x86_64           MySQL 5.7 Community Server      enabled:  227
mysql57-community-source           MySQL 5.7 Community Server - So disabled
mysql80-community/x86_64           MySQL 8.0 Community Server      disabled
mysql80-community-source           MySQL 8.0 Community Server - So disabled
# yum install yum-utils
# yum-config-manager --disable mysql57-community
# yum-config-manager --enable mysql55-community

これでようやくMySQL5.5をインストールできます。

# yum install -y mysql mysql-devel mysql-server

インストールが終わったら、MySQLのバージョンが5.5になっているのを確認します。

# mysqld --version

MySQLを起動して、自動起動を設定します。

# systemctl start mysqld.service
# systemctl enable mysqld.service

rootのパスワード設定などの初期設定を行います。

# mysql_secure_installation
Enter current password for root (enter for none): [そのままEnter]

// rootのパスワード設定
Set root password? [Y/n] Y
New password: [パスワードを入力]
Re-enter new password:[パスワードを再入力]
Password updated successfully!
Reloading privilege tables..
 ... Success!

// 匿名ユーザー削除
Remove anonymous users? [Y/n] y
 ... Success!

// リモートホストからのrootログイン禁止
Disallow root login remotely? [Y/n] y
 ... Success!

// テスト用データベース削除
Remove test database and access to it? [Y/n] y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

// ユーザー権限が保存されているテーブルをリロード
Reload privilege tables now? [Y/n] y
 ... Success!

All done!  If you've completed all of the above steps, your MySQL
installation should now be secure.

my.cnfを編集します。

# cp /etc/my.cnf /etc/my.cnf.bak
# vi /etc/my.cnf

[mysqld]
# mysqldセクションの末尾に追加
character-set-server = utf8

# clientセクションごとファイルの末尾に追加
[client]
default-character-set = utf8

MySQLを再起動します。

# systemctl restart mysqld.service

これで環境構築完了です!

【Xcode】UILabelの文字サイズを自動調整する

画面サイズに合わせてUILabelが拡大縮小する場合に、文字サイズも自動調整する方法。

何もしないとどうなるか

サンプルとして、5.5inch(ex. iPhone8 Plus)の画面サイズに合わせてレイアウトを作成しました。
青い四角部分がUILabelで、画面サイズに合わせてサイズが変わります。

まず iPhone8 Plusで表示した場合。問題ありません。
f:id:an3714106:20171129235157p:plain

次にiPhone4sで表示した場合。文字が省略されてしまいました。
これを調整していきます。
f:id:an3714106:20171129235212p:plain

1. Autoshrinkを有効にする

UILabelにはAutoshrinkという、UILabelのサイズに合わせて文字サイズを自動調整してくれるそのものずばりな機能があります。

Storyboardから設定する場合。
「Autoshrink」を「Minimum Font Scale」にし、下の欄でScaleを指定します。
Scaleは、自動調整時の文字サイズ最小値を、元々の文字サイズに対する比率で入力します。
f:id:an3714106:20171129235540p:plain

コードから設定する場合。

label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.3

f:id:an3714106:20171130000401p:plain

文字サイズが自動調整されました。
ただ、これだとラベルぎりぎりすぎるので、余白をつけたい場合は次に進みます。

2. drawTextで余白(padding)を付ける

UILabelのカスタムクラスを作成し、下記のようにdrawTextを実装します。

class AutoShrinkLabel: UILabel {
    
    var padding: UIEdgeInsets = UIEdgeInsetsMake(4, 4, 4, 4)

    override func drawText(in rect: CGRect) {
        let newRect = UIEdgeInsetsInsetRect(rect, padding)
        super.drawText(in: newRect)
    }
}

Storyboardからラベルに設定します。
f:id:an3714106:20171203230006p:plain

余白がつきました。
f:id:an3714106:20171203231831p:plain

大体これでいけるのですが、この方法は縦方向に余白を大きく取りたい場合に上手くいかないことがあります。
おそらく「label.adjustsFontSizeToFitWidth」というプロパティ名の通り、幅に合わせて自動調整しているため。

3. viewDidLayoutSubviewsで余白(padding)を付ける

drawTextでは調整しきれない場合は、UIViewControllerでviewDidLayoutSubviewsを実装して自力で計算します。
こうなるとdrawTextはなくてもいいかも。

class AutoShrinkLabel: UILabel {   
    var padding: UIEdgeInsets = UIEdgeInsetsMake(20, 4, 20, 4)
}

class ViewController: UIViewController {

    @IBOutlet weak var label: AutoShrinkLabel!
    
    override func viewDidLayoutSubviews() {
        let fontSize = label.frame.size.height - (label.padding.top + label.padding.bottom)
        label.font = label.font.withSize(fontSize)
    }
}

f:id:an3714106:20171203234559p:plain

以上です!

【Xcode】iOS11からUITableViewのSwipe Actionが新しくなった

f:id:an3714106:20171023224043p:plain:w250
iOS11からUITableViewDelegateにSwipe Actionの新しいメソッドが追加されました。
これによって以下のことができるようになりました。
(iOS11以降限定です)

  • 左から右へのSwipe Actionを実装する
  • Swipe Actionに画像を表示する



使用イメージ

f:id:an3714106:20171023224331p:plain:w250
左から右へのSwipe Action使用例です。
こんな感じで文字の代わりにアイコンを表示できるようになりました!



f:id:an3714106:20171023224318p:plain:w250
こちらは従来通り右から左へのSwipe Action、文字を表示した場合です。
こちらもアイコンに変えることができます。



サンプルコード

上記の使用イメージで使用したソースコードです。
storyboardで適当なUITableViewControllerと紐づけてください。

class ViewController: UITableViewController {
    
    let dataSource = ["One", "Two", "Three"];
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
        cell.textLabel?.text = dataSource[indexPath.row]
        return cell
    }
    
    // iOS11以降
    // 右から左へスワイプ
    @available(iOS 11.0, *)
    override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        let editAction = UIContextualAction(style: .normal,
                                            title: "Edit",
                                            handler: {(action: UIContextualAction, view: UIView, completion: (Bool) -> Void) in
            print("Edit")
            // 処理を実行完了した場合はtrue
            completion(true)
        })
        editAction.backgroundColor = UIColor(red: 101/255.0, green: 198/255.0, blue: 187/255.0, alpha: 1)
        
        let deleteAction = UIContextualAction(style: .destructive,
                                              title: "Delete",
                                              handler: { (action: UIContextualAction, view: UIView, completion: (Bool) -> Void) in
            print("Delete")
            // 処理を実行できなかった場合はfalse
            completion(false)
        })
        deleteAction.backgroundColor = UIColor(red: 214/255.0, green: 69/255.0, blue: 65/255.0, alpha: 1)
        
        return UISwipeActionsConfiguration(actions: [editAction, deleteAction])
    }
    
    // iOS11以降
    // 左から右へスワイプ
    @available(iOS 11.0, *)
    override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        let favoriteAction = UIContextualAction(style: .normal,
                                                title: "Favorite",
                                                handler: { (action: UIContextualAction, view: UIView, completion: (Bool) -> Void) in
            print("Favorite")
            // 処理を実行完了した場合はtrue
            completion(true)
        })
        favoriteAction.backgroundColor = UIColor(red: 210/255.0, green: 82/255.0, blue: 127/255.0, alpha: 1)
        favoriteAction.image = UIImage(named: "ic_favorite")
        
        return UISwipeActionsConfiguration(actions: [favoriteAction])
    }
}