post Image
【Swift x PHP】iOS端末とPHPサーバでHTTP通信

概要

サーバとの通信を実装する場合はライブラリを用いることが多いですが、今回はライブラリ未使用で実装してみます。Swiftの勉強などにどうでしょうか。
(実際には「タイムアウト処理は?」「オフラインのときは?」などの処理が必要になってきます。)

対象

サーバ上にPHP環境の構築ができることを前提としております。
また、iOS開発に関しても経験があるといいですが、今回作成するアプリをGitHubに上げているので未経験者でも始められると思います。

手順

  1. お好みのサーバにPHP環境を構築
  2. Install xcode
  3. Clone project file from GitHub
  4. Execute

1. PHP Server

Macでローカルサーバを立ち上げる方法(Qiita)
※私はラズパイを使用しました

PHPのコードは以下のようになります。
$_GET[]の部分で受け取ることのできるパラメータとしてtitlenoteを準備しています。その後、printで文字を出力しています。

RaspberryOne.php
<?php 
  $title = $_GET["title"];
  $note = $_GET["note" ];
  print "$title$note";
?>

試しに、http://[IP Address]/RaspberryOne.php?title=あーちゃんへ&note=大好きですとブラウザに入力してアクセスしてみてください。あーちゃんへ : 大好きですと告白されたら成功です。

2. Install Xcode

Swift3とSwift2のコードを準備しているので準備するXcodeもお好きな方をご準備ください。
Androidほど開発環境構築に時間がかかることはないと思います。

3. iOS App Development

※Serverへの接続先が192.168.2.22であるものとして記述しているので、臨機応変に変えてください(GitHubのコードも同様です)

まず、iOS9以降でセキュリティ的に問題のあるHTTP通信が禁止されているので例外で許可させておきましょう。HTTPSで通信できるようにサーバを構築した場合はこの作業は必要ありません。

Info.plist
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>
        <key>192.168.2.22</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <false/>
        </dict>
    </dict>
</dict>

次に、アプリの処理を実際に書いていきます。
clickButtonメソッド内に通信処理を書いています。
ユーザにTextFieldへ送信内容を入力してもらい、その内容を含めたURLでアクセスし、結果を取得・表示するといった流れを記述しています。
UIはお好みで!@IBOutletとなっている部分がStoryboardとリンクさせてる部分です。

Swift2 version

ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var resultLabel: UILabel!
    @IBOutlet weak var inputText1: UITextField!
    @IBOutlet weak var inputText2: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        inputText1.delegate = self
        inputText2.delegate = self
    }

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

    @IBAction func clickButton(sender: AnyObject) {
        if inputText1.text != "" && inputText2.text != "" {
            let title = inputText1.text!
            let note  = inputText2.text!

            // 接続先のURLにパラメータを追加
            let stringUrl = "http://192.168.2.22/RaspberryOne.php?title=\(title)&note=\(note)"
            // 文字列のURLからNSURLに変換
            let URL = NSURL(string: stringUrl.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
            // NSURLを引数にRequestの生成
            let req = NSURLRequest(URL: URL)

            // ここからが実際に通信をする所(非同期通信)
            let task = NSURLSession.sharedSession().dataTaskWithRequest(req, completionHandler: {
                (data, res, err) in
                // サーバから出力はありますか?
                if data != nil {
                    // あるみたいだから文字列に変換しておく
                    let text = NSString(data: data!, encoding: NSUTF8StringEncoding)
                    // 非同期通信内でUIに変更を加えるときのおまじないと思って!
                    dispatch_async(dispatch_get_main_queue(), {
                        self.resultLabel.text = text as String?
                    })
                }else{
                    // サーバから返事がないみたいだからエラー表示
                    dispatch_async(dispatch_get_main_queue(), {
                        self.resultLabel.text = "ERROR"
                    })
                }
            })
            task.resume()
        }
    }

    func textFieldShouldReturn(textField: UITextField) -> Bool{
        //Close keyboard.
        textField.resignFirstResponder()

        return true
    }
}

Swift3 version
Swift3に対応するとともにアラートも追加してみました。
コードの解説はSwift2と変わらないところは省略しています。

ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    //PHP
    @IBOutlet weak var resultLabel: UILabel!
    @IBOutlet weak var inputText1: UITextField!
    @IBOutlet weak var inputText2: UITextField!


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        inputText1.delegate = self
        inputText2.delegate = self
    }

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

    @IBAction func clickButton(_ sender: AnyObject) {
        if inputText1.text != "" && inputText2.text != "" {
            let title = inputText1.text!
            let note  = inputText2.text!

            let stringUrl = "http://192.168.1.21/RaspberryOne.php?title=\(title)&note=\(note)"
            // Swift3からはNSがなくなってURLになりました
            let url = URL(string: stringUrl.addingPercentEscapes(using: String.Encoding.utf8)!)!
            // これもNSがなくなりました
            let req = URLRequest(url: url)

            // ここもNSがなくなりました
            let task = URLSession.shared.dataTask(with: req, completionHandler: {
                (data, res, err) in
                if data != nil {
                    let text = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
                    DispatchQueue.main.async(execute: {
                        self.resultLabel.text = text as String?
                    })
                }else{
                    DispatchQueue.main.async(execute: {
                        self.resultLabel.text = "ERROR"
                    })
                }
            })
            task.resume()
        }else{
            // 未入力なのにサーバにアクセスしようとしたら怒るようにした
            alert("error", messageString: "It is not entered.", buttonString: "OK")
        }
    }

    // 標準のアラートを表示させる
    func alert(_ titleString: String, messageString: String, buttonString: String){
        //Create UIAlertController
        let alert: UIAlertController = UIAlertController(title: titleString, message: messageString, preferredStyle: .alert)
        //Create action
        let action = UIAlertAction(title: buttonString, style: .default) { action in
            NSLog("\(titleString):Push button!")
        }
        //Add action
        alert.addAction(action)
        //Start
        present(alert, animated: true, completion: nil)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool{
        //Close keyboard.
        textField.resignFirstResponder()

        return true
    }
}

4. Execute

上部に送信した内容がフォーマットされて返ってきているのがわかると思います。
PHP側の処理をもっと加えれば、あーちゃんからの返事をもらえるアプリになりますね!

結果 結果

最後に

明日はバレンタインです(記事作成時)!
PHPサーバに向けてiOS端末から告白してみては?

サーバ構築とアプリ開発を別の人が担当して共同開発するのもいいですね。きっと面白くなります!!

以下がiOS側のサンプルです。
GitHub | AkkeyLab/SendMessage


『 Swift 』Article List