20200520のSwiftに関する記事は6件です。

Swift:メモ RSSを使ったテーブルアプリ

import UIKit
class ListViewController: UITableViewController,XMLParserDelegate{//uitableviewcontrollerはdatasourceやdelegateも批准している
  var parser:XMLParser!//rssデータを解析するためのXMLParserクラスのインスタンスを格納するためのプロパティ
  var items = [Item]()//複数の記事を格納するための配列
  var item:Item?//Itemクラス型のプロパティ Itemクラスはタイトルと本文のURLの2つのプロパティを持つ
  var currentString = ""
  //セルの個数
  override func tableView(_ tableView:UITableView,numberOfRowsInSection section:Int) -> Int {
    return items.count
  }
  //tableView:cellForRowAtはセルの内容をiPhoneに知らせるメソッド セルの内容を戻り値に設定
  //indexPathにはtableView:cellForRowAtメソッドが設定を行っているセルの行番号が保持されている
  //IndexPathにはsectionとrowのプロパティが含まれている
  override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell{
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    cell.textLabel?.text = items[indexPath.row].title
    //瞬時に表示するために大量のセルを作成しなくて済むようにセルの再利用を設定322
    //dequeueReusableCell(withIdentifier:for)メソッドを使う場合、引数にどのセルを再利用するか指定する→セルを選択した状態でのIdentifier項目の名前Cellが第一引数
    return cell
  }
  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    startDownload()
  }
  //データのダウンロード startDownload()はオリジナルのメソッド
  func startDownload(){
    self.items = []//itemsを空にする、古いデータが残ったままダウンロードすると重複する記事が生まれてしまう
    if let url = URL(
    string: "https://aaaaaa.com/rssfeeder/"){//ニュース記事のあるwebサイトのURLを指定
      if let parser = XMLParser(contentsOf: url)//parserのインスタンスを作成 前の行で指定したURLを引数に 不正なURLが検出された時nilを返すことがあるのでメソッドの戻り値はオプショナル型
        {
        self.parser = parser
        self.parser.delegate = self
        self.parser.parse()
        //parserのデリゲートにselfを指定して最期にparserメソッドを呼び出すことでデータの解析を開始
      }
    }
  }
  func parser(_ parser: XMLParser,didStartElement elementName: String,namespaceURI:String?,qualifiedName qName: String?,attributes attributeDict:[String : String])
    //要素感の開始タグが見つかるごとに呼び出される ダウンロードされた記事から必要なデータだけ取り出す処理を行うメソッド XMLParserDelegateで宣言されているメソッド
  {
    self.currentString=""//要素を一時的に保存する変数currentStringを空に 古い文字列が入らないように
    if elementName == "item"{//要素名がitemの場合のみニュース記事を入れる箱itemを作る
      self.item = Item()
    }
  }
  //タグで囲まれた要素を取り出す 内容が見つかると自動的にこのメソッドが呼び出される メソッドの中に文字列を取り出す処理を書く
  func parser(_ parser: XMLParser, foundCharacters string: String) {
    self.currentString += string//引数stringには見つかった記事の内容が格納されている 引数の中身を変数currentStringに+=演算子を使って追加 この段階ではcurrentStringにどの要素名の内容が入っているかは不明
  }
  //要素名が終わる</items>が見つかると以下のメソッドが自動的に呼び出される
  func parser(_ parser: XMLParser,
        didEndElement elementName:String,//elementNameに要素名を格納
        namespaceURI:String?,
        qualifiedName qName:String?){
    switch elementName{//switch文の条件にelementNameを指定することで要素名ごとに処理が行われる
      //要素名がtitleの時はitem.titleに内容を格納 linkも同じ
    case "title":self.item?.title = currentString
    case "link":self.item?.link = currentString
    case "item":self.items.append(self.item!)//要素がitemの場合はニュース記事の終わりを意味するのでこれまで取得したデータをitemsの中に格納 append=追加する
    default : break
    }
  }
  //テーブルビューの内容を更新することで取得した記事を画面に表示
  func parserDidEndDocument(_ parser:XMLParser){
    self.tableView.reloadData()
  }
  override func prepare(for segue:UIStoryboardSegue, sender:Any?){
    if let indexPath = self.tableView.indexPathForSelectedRow{//ユーザーがタップしたセルのindexPathを取得
      let item = items[indexPath.row]//取得した値を用いてitem配列から該当する記事を取得
      let controller = segue.destination as! DetailViewController//controller定数に遷移先のビューコントローラーを格納
      controller.title = item.title//titleプロパティに記事タイトルを格納
      controller.link = item.link
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『たった2日でマスターするiPhoneアプリ開発集中講座』読書メモ

概要

以下の書籍を読み進めた際の読書メモです。
所要時間は約15時間でした。
超詳細に示された手順を辿りながら、アプリ開発の基本的な流れを分かりやすく学べました。
主要なコンポーネントの使い方からWebAPIを使ったデータ取得まで、サンプルアプリを作りながら学べるので、1冊やるだけでもちょっとしたアプリなら作れるようになると思いました。

書籍概要

  • 書籍名:たった2日でマスターするiPhoneアプリ開発集中講座
  • 著者 :藤治仁・小林加奈子・小林由憲
  • 発刊日:2019/10/23
  • 頁数 :424ページ

読書ノート

開発方法・ビルド

  • 主な開発の流れ

    • Xcode上にパーツを配置し画面レイアウトを作る
    • パーツとコードを接続(connect)し、動作を記述する
    • シミュレータや実機で動作を確認する
  • 実機で動作確認する場合、Xcodeに登録されたApple IDと、実機に設定されているApple IDが同じである必要がある

    • Xcodeには複数のApple IDを登録できるが、その中に実機と同じApple IDがあれば良い
    • ビルド時に選択するTeamは、実機と異なるApple IDのものでも問題ない
  • プロジェクト設定でTeamを選択時に「Failed to register bundle identifier.」エラーが出た場合の対応

image.png

レイアウト

  • AutoLayoutを使うと画面パーツの配置やレイアウトを整えることができる
    • 色々な端末の画面サイズに対応するためには重要な機能

IBOutlet・IBAction

  • IBOutletについて

    • @IBOutlet weak var answerImageView: UIImageView!
      • IBOutlet修飾子Interface Builderにあるパーツとコードを連結することを示すキーワード
      • weak var answerImageView:変数の定義(answerImageViewが変数名)
      • : UIImageView:クラス(型)の指定(変数名:データ型
      • このコードで、UIパーツのImageViewを、コード上でanswerImageViewという名前で参照・設定できるようになる
  • IBActionについて

    • @IBAction func shuffleAction(_ sender: Any) { ... }
      • 紐付いたパーツがユーザによって何かしら操作された際、実行するプログラムを連結するキーワード

変数・定数

  • letで定義した値は一度値を設定したら変更できない(=定数)
    • 変数はvarで定義する
    • 上手く使い分けることでコードの安全性が向上する

アクセス修飾子

  • Swiftのアクセス修飾子
    • public:どこからでもアクセス可能
    • internal:同じモジュール内であればアクセス可能
    • fileprivate:ファイル内でのみアクセス可能
    • private:現在の定義の中でのみアクセス可能

Storyboard・パーツ

  • パーツとコードの関連付けを解除する場合、コードを削除するだけではダメ

    • StoryboardのView Controllerから解除する必要がある
      • 再度関連付けする場合、コード横の白丸を対象パーツにD&Dする
  • Storyboard上にパーツを配置する際、コピペはしないようにする

    • 特に、アクションを設定するボタン等をコピペした場合、コードとの関連付けが上手くいかなくなる場合がある

値の参照渡し

  • メソッド呼び出し時、引数の先頭に&を付けると参照渡しが可能
    • 引数に指定した変数やインスタンスそのもの(同アドレス)にメソッド内で変更を加えたい場合は参照渡しを使う
      • 変数のコピーではなく変数のアドレス情報を渡すので、メソッド内での変更が元の値に影響する
    • 先頭に何も付けなければ値渡しになる
      • コピーを渡すだけなのでコピー先で変更があっても元の値には影響がない

例外処理

  • 例外処理
    • 例外処理はdo-catchブロックで行う
    • メソッド呼び出しの前にtry文を記述する
      • sample = try AVAudioPlayer(contentsOf: cymbalPath, fileTypeHint: nil)

Delegate・DataSource

  • Delegate機能を使うと、特定の操作(文字入力等)が完了したタイミングでプログラムを動作させることができる

    • あるクラスで行いたい処理の一部を他のクラスに任せたり、任せた処理を指定したクラスに通知したりする仕組み
    • Delegateが行われる時に登場するものは大きく3つ
      • 処理を依頼するクラス(例:inputeText.delegate = self
        • 処理結果の通知先を自分自身(ViewController)とし、TextFieldクラスから作ったinputTextにdelegateを設定
      • 依頼する・依頼されるクラスを取り持つプロトコル(例:UITextFieldDelegate
        • return操作が行われたら、設定された通知先に結果を通知するよう、UITextFieldクラスに依頼する
      • 処理を依頼されるクラス(例:UITextField
        • UITextFieldDelegateから通知を受けたら、通知先(ViewControllerのtextFieldShouldReturn)に結果を通知する
        • 通知先はプロトコルが処理してくれるので、このクラスは通知先を知らなくても良い(汎用的に利用できる)
    • ViewControllerの宣言に記述を追加する
      • 例:class ViewController: UIViewController, UITextFieldDelegate { ... }
  • TextFieldのDelegate

    • textFieldShouldReturn() { ... }:Return操作(「検索」ボタンを押す等)が行われたら実行されるメソッド
      • メソッド名はあらかじめ決まっている
  • DataSource

    • UIパーツとコード間でデータのやり取りを行うために定義する

オプショナル・アンラップ・オプショナルチェイニング

  • オプショナル

    • 値がない状態を許すデータ型のこと
    • 変数宣言時のクラス名の後に?または!を指定する(一般的なオプショナル型は?を付ける)
      • これらが無い宣言は、「非オプショナル型」になる(値がない状態を許さない)
  • アンラップ

    • if let ...を使うと、値がある場合のみif文内が実行されるとともに、変数に値が格納される
      • オプショナル型の変数やメソッドを安全に取り扱う手法のこと
      • 例:if let searchKey = textField.text { print(searchKey) }
        • 値があれば、print(searchKey)が実行されるとともに、searchKeyに値が格納される
      • アンラップ処理は入れ子にして使うことも出来る
  • オプショナルチェイニング

    • アンラップの手法の一つで、値がある場合のみ、続くメソッドが実行される
      • 例:_ = navigationController?.popViewController(animated: true)
        • navigationControllerに値が入っている場合のみ、popViewControllerが実行される
  • guard letを使うとオプショナル変数の値を安全に取り出せる

    • if letは「アンラップできたら」だが、guard letは「アンラップできないときは」という条件で分岐する
      • 例:guard let アンラップ変数 = オプショナル変数 else { オプショナル変数に値がない時の処理 }
        • オプショナル変数に値がない時の処理には、returnなどのプログラムが終了するコードのみ記述可能

クロージャ

  • クロージャ
    • 引数などに{}でまとめて処理を渡して実行させる方法のこと
      • 例えばCLGeocoder.geocodeAddressString()などに渡して、位置情報取得後の処理を第2引数に書いたりする
    • 主な記述方法:
{ (引数:引数の型) -> (戻り値の型) in
    何らかの処理
    return 戻り値
}

UserDefaults

  • UserDefaults
    • UserDefaultsを使うと、アプリで利用する値を保存できる
      • 異なる画面間で値をやり取りする時などに使う
      • 値はKVで保存され、アプリを停止・再起動しても保存されている(永続的)
        • 永続化は定期的に実行されているが、即座に永続化するにはsynchronizeを利用する
    • UserDefaultsにアクセスする場合、(必要に応じて)都度UserDefaultsインスタンスを生成してから値を呼び出す

Segue(セグエ)

  • Segueを使って画面遷移する際にprepareメソッドが実行される
    • override func prepare(for segue: UIStoryboardSegue, sender: Any?) { ... }

画像の取り扱い

  • Appleが提供している画像処理フレームワーク「Core Image」を使うと、簡単に画像にフィルタをかけたりできる

音声の取り扱い

  • 音を扱うためには「AVFoundation」というフレームワークを読み込む必要がある

マップの取り扱い

  • CLGeocoderクラスを使うと、緯度経度から住所を検索、またはその逆を行うことができる
    • CLGeocoder.geocodeAddressString():郵便番号や住所等から様々な情報を取得してplacemarksに格納する
      • placemarksCLPlacemarksクラスのインスタンス

ラベル(名前付き引数)

  • メソッドのラベル(名前付き引数)について
    • メソッドのラベルは、メソッドの呼び出し側でより引数を分かりやすくするために使う
      • メソッド名から引数の意味が分かるような場合、引数ラベル省略「_」を利用して呼び出し側でラベルを省略可能
        • ラベルを省略した場合は引数名がラベルとして扱われる
// ラベルを付与する場合

// 合計値を計算
func calcSum(quantity qt: Int, price pc: Int) -> Int {
    // メソッド内ではラベルではなく引数を使う
    return qt * pc
}

// メソッド呼び出し(ラベルを使って引数を指定できる)
calcSum(quantity: 2, price: 300)
// ラベルを省略する場合

// デバッグエリアに文字を表示する
func debugMessage(_ message: String) {
    print("\(message)")
}

// メソッド呼び出し(引数のラベルを省略できる)
debugMessage("デバッグエリアに表示する文字")

JSON

  • Codableプロトコルを利用すると、JSONの項目名とコード中の変数名を同じにした場合に、JSON変換時に一括して変数にデータを格納してくれる

ATS

  • ATSを有効化すると、HTTPでインターネットに接続できなくなる
    • また、HTTPSを使っていても、Appleが推奨するセキュリティ要件を満たしていなければ同様に接続できなくなる
    • 無効化すればHTTPやセキュリティ要件を満たさないHTTPSでも接続できるようになる

タプル

  • タプルを用いると、複数の値を一つの変数として扱うことができる(KVを一つにまとめカンマで区切る)
    • タプルは配列のように値の追加・削除・データの個数変更等はできない
    • 例:(key1:String, key2:String, key3:Int)

その他

  • viewDidLoadメソッドはアプリ起動時に一度だけ動くようにあらかじめ作られている

    • この中に書いたコードもアプリ起動時に一度だけ動作する
  • 引数のデータ型にinoutを指定すると、引数として受け取り、かつ戻り値としてその値を返す

    • return文を書かなくても戻り値を返してくれる
  • Objective-Cで書かれたクラスをSwiftで利用する場合、宣言の先頭に@objcを付ける必要がある

  • 戻り値のあるメソッドを呼び出してかつその戻り値を利用しない場合、「_」に代入する

    • 例:_ = updateTimer()updateTimer()はInt型の値を返す)
  • 文字列の中に定数や変数を埋め込む場合、「\(定数 or 変数)」と記述する

    • 例:label.text = "残り時間:\(remainCount)秒"
  • 既存のクラスを拡張する場合は「Extension」を用いる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ARKit]ARWorldMapを使った空間の保存と復元

概要

Worldmap情報を保存するとAR空間全体の状態を保存できる。

アプリ再起動後にオブジェクトを同じ位置に配置したい場合や複数端末で空間を共有したいときに便利

Worldmapのロード

let worldMap = try! NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data)
configuration.initialWorldMap = worldMap

Worldmapの保存

self.sceneView.session.getCurrentWorldMap(completionHandler: { (worldMap, error) in
    guard let map = worldMap else {
        return
    }
    let archived = try! NSKeyedArchiver.archivedData(withRootObject: map, requiringSecureCoding: true)
    UserDefaults.standard.set(archived, forKey: self.worldMapKey)
})

オブジェクトの位置の復元

オブジェクトの復元はUserDefaultsに座標を保存しておき、rendererメソッドで描画を行う。
(SCNVector3はUserDefaults非対応なので配列として保存する)

ARWorldMap

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

こんなソースコードはイヤだ-mutableな変数を使わなくてもいいのに

プログラムのソースコードのより良い書き方をまとめていこうと思います。

mutableな変数を使わなくてもいいのに

sample.swift
 func sendMessage() {
    var message = ""
    if (user.isMale && user.isAdult) {
      message = "大人の男性"
    } else if (user.isFemale && user.isChild) {
      message = "女の子"
    } else {
      message = "その他の人"
    }

    Logger.info(message)
    self.api.sendMessage(message: message)
  }  

どのように変更できるのか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】iOSアプリ開発入門~ SwiftとUIの接続編2~

前回はボタンタップイベントを受けSwiftで処理をする方法について投稿しました。
SwiftとUIの接続編1:https://qiita.com/euJcIKfcqwnzDui/items/f794ee8b996e6d650027

今回は更にそのイベントを受けUIの表示を変更する方法について説明したいと思います。
とはいっても前回の内容とそれほど大きくな変わりません。

UI自体とSwiftを接続する

UIの表示の変更すを指示するのはSwiftの役割です。
前回はUIからのイベントを接続しましたが今回はUI自体を接続します。

それではいつも通りプロジェクトを開いてください。
前回説明したようにXcodeの画面を分割し、Main.storyboardViewController.swiftをそれぞれ開きます。
スクリーンショット 2020-05-19 23.50.30.png

ボタンを接続してあげてもいいのですが、表示の変更なので今回はラベルを使ってあげましょう。
ラベルは何の変哲もないテキストを表示するためのUIです。
レイアウト編1で説明した方法と同様にしてラベルをstoryboardに追加してください。
スクリーンショット 2020-05-19 23.57.12.png

オートレイアウトで画面中央に配置してあげます。
(オートレイアウトの簡単な説明はレイアウト編2でしていますので参考にしてください)
中央への配置の仕方は下図のように、ラベルに対してHorizontally in ContainerVertically in Containerのそれぞれの制約に0を設定してあげ追加するとできます。
これらの制約は中央から水平・垂直方向からどれくらいの位置に配置するかという制約です。
スクリーンショット 2020-05-20 0.00.02.png
このように制約が追加されていればOKです
スクリーンショット 2020-05-20 0.05.12.png

ラベルを接続します。
ボタンのイベントを接続した方法のようにcontrolを押しながらViewController.swiftにドラッグしてください。
場所は前回追加したtapButtonメソッドの上あたりにしましょう。

ViewController.swift
class ViewController: UIViewController {
    <<<< この辺に追加するスペースがなくやり辛ければ改行してあげても構いません
    @IBAction func tapButton(_ sender: Any) {
        print("Hello World")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}

するとボタンのときと同様にポップアップが表示されます。
スクリーンショット 2020-05-20 0.36.05.png

ポップアップを以下のように変更し、[Connect]ボタンを選択します。

  • Connection:Outlet
  • Name:label
  • Type:UILabel
  • プルダウン:weak

スクリーンショット 2020-05-20 0.50.55.png

ここで重要なのはConnectionにOutletを指定することです。
ボタンイベントのときはActionを指定しましたが、UI自体を接続する場合はOutletの指定となります。
※今回のラベルではそもそもOutletかOutlet Collectionしか選べません。接続のされ方が若干違いますが両方UI自体を接続するものです。ラベルの機能は表示のみなのでActionはありません。

それぞれ簡単に説明します。とは言ってもボタンタップのイベントのときと大差ありません。

  • Connection
    • 接続の種類。Outlet or Outlet Collectionを選択するとUI自体を接続する。
      基本はOutletを指定。Outlet Collectionを指定すると配列として扱える。
  • Object
    • 接続先のオブジェクト。View Controllerにラベルを接続するというイメージ。変更できない。
  • Name
    • プロパティの名前。UIをコードに接続するとプロパティと呼ばれる状態で接続される。
      とりあえずは接続したラベルの名前と認識してもらえれば問題なし。
  • Type:UILabel
    • プロパティの型。接続したラベルはUILabelという種類のUIです、というようなイメージ。
  • プルダウン:weak
    • 参照の強さ。少し難しい概念なのでとりあえずは気にせずOK。weakを選んでおけば間違いない。

以上がそれぞれの項目の内容です。

接続するとコードが自動的に追加されます。

ViewController.swift
class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel! << ここが追加される
    @IBAction func tapButton(_ sender: Any) {
        print("Hello World")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}

追加されたコードの頭には@IBOutletとあります。これはInterface BuilderからOutletとして接続されるプロパティという意味です。

接続するとActionのときと同様に

では実際にコードと@IBOutlet weak var label: UILabel!の左側に●のようなマークが出ています。
ActionでもOutletでも接続されるとこのマークが表示されます。

storyboardを見てみます。
ラベルを選択し右クリックしてください。
[Referencing Outlets]に[label]、[View Controller]という項目が表示されています。
これはViewControllerに「label」という名前で接続されていますよ、という意味です。
スクリーンショット 2020-05-20 1.17.04.png

これでUIとコードは接続は完了です!

表示を変更する

コードに接続したのでラベルの表示を変えてみましょう。

コードを以下のように編集してください。

ViewController.swift
class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func tapButton(_ sender: Any) {
        self.label.text = "Hello World" // << ここを書き換える
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}

シミュレータで実行します。
すると画面真ん中に「Label」と表示されたかと思います。
これが先程追加したラベルです。

ではボタンをタップしてください。
ラベルの文字列が変わり、「Hello World」と表示されます。
スクリーンショット 2020-05-20 1.33.21.png

わかったかも知れませんが、ボタンタップイベントのメソッドに
self.label.text = "Hello World"というラベルの表示する文字列を変更処理を書いたためです。

self.labelは前項で追加したラベルのことです。
labelはUILabelという型でUILabelはtextというプロパティを持っています。
このtextというプロパティが画面に表示する文字列で、ここを「Hello World」という文字列に書換えたため表示が変更されたというわけです。

このように接続したUIに対し様々な処理を行ってあげることでUIを変更することができます。

もう少し変更してみましょう。
UILabelはtextの他にもtextColorというプロパティを持っています。
読んで字のごとく表示される文字の色です。
以下のように編集して赤色に変えてみます。

ViewController.swift
class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func tapButton(_ sender: Any) {
        self.label.text = "Hello World"
        self.label.textColor = UIColor.red // 文字を赤にする
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}

ボタンをタップすると赤文字で「Hellow World」に変更されました。

UILabelはもちろんtexttextColor以外にもbackgroundColorなど数多くのプロパティを持っています。
ここでは全てを紹介しきれませんし私も全て把握できているわけではありません。
これらはApple Developerの公式ドキュメントに情報が載せられており、ここから変更したいプロパティや処理を調べて実装したりします。

また他にもXcodeから参照することもできます。(こっちの方がよくやる)
@IBOutlet weak var label: UILabelのUILabelの部分をcommandキーを押しながらクリックしてください。
ポップアップが表示されるので[Jump to Definition]をクリックしてください。UILabelの定義に飛びます。
スクリーンショット 2020-05-20 2.03.29.png

定義ファイルを見てみると先程使った'text'と'textColor'があります。
スクリーンショット 2020-05-20 2.06.07.png

ここにはUILabelで扱えるプロパティやメソッドが定義されています。
まずはこの定義ファイルを見て、それらしきものがあれば公式ドキュメントやその他のサイトで調べて実装するという流れになります。

最後に

今回はコードにUI自体を変更し表示の変更方法について説明しました。
だんだんとアプリらしくなってきたのではないでしょうか?
イベントではボタン、表示変更ではラベルを取り扱いましたがiOSのUIにはスイッチや少し扱いが難しいかも知れませんがスライダーなど数多くのUIが提供されています。
気になるUIがあれば自分で試してみて扱いに慣れていきましょう。

以上で本投稿は終わりです。
参考になった、わかりにくい、質問がある場合などはコメントを頂けると幸いです。

次回はSwiftで処理制御してもう少しアプリらしい振る舞いにしていきます。
Swift基礎編1:https://qiita.com/euJcIKfcqwnzDui/items/ac50ed99673e10f15b71

本連載ではプログラミング未経験からiOSアプリ開発が行えるようになることを目的としています。
今までの投稿をまとめていますのでこちらもご覧ください。
アジェンダ:https://qiita.com/euJcIKfcqwnzDui/items/0b480e96166e88945684

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーブレットでiPhoneアプリからのリクエストパラメータをうまく取得できなかった話

個人でiPhoneアプリを作成していたところ、getAttribute()とgetParameter()の違いを理解しておらず、
サーブレットでアプリからのリクエストをうまく取得できなかったのでメモとして残しておきます。

今回発生した事象はこんな感じ。

iPhoneアプリからリクエスト送信 
    ↓
サーブレットで受信(サーブレットに到達はするが、リクエスト内のパラメータが取得できない。。)

原因

request.getAttributeで値を取得しようとしていたのがダメでした。
getParameterで取得しないとダメみたいですね。。
(jspはgetAttributeで取得できていたので完全に盲点でした)
2020.05.22追記
↑取得できてませんでした。そもそもsetAttributeとgetAttributeをごちゃごちゃになっていたのでハマっていた様です。。
このサイトで勉強しなおしました。→ getAttribute()メソッド。

詳しい内容はこちらのサイトに記載してありました。
・getAttribute()とgetParameter()の違い

検証

理解を深めるためにサンプルプログラムで検証してみました。
iPhoneのtextFieldに入力された文字列をコンソールに表示するサンプルプログラムです。

・iPhone画面

 input.jpeg  result.png

結果

・コンソール画面

コンソール結果.png
やはりgetAttributeだと取得できていませんでした

ソース

・iPhone画面 (入力画面)

ViewController
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var testField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    //Servletに送信ボタンを押した際に実行
    @IBAction func goServlet(_ sender: Any) {
        self.performSegue(withIdentifier: "goResultView", sender: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        print("prepare動作開始")
        //URLを設定
        guard let req_url = URL(string: "http://localhost:8080/Test/TestServlet")
            else{return}
        print("urlセット完了")

        //リクエストに必要な情報を宣言
        var req = URLRequest(url: req_url)
        print("リクエストの宣言")
        //POSTを指定
        req.httpMethod = "POST"
        //POSTするデータをBODYとして設定
        req.httpBody = "test=\(self.testField.text!)".data(using: .utf8)

        //sessionの作成
        let session = URLSession(configuration: .default,delegate: nil, delegateQueue: OperationQueue.main)
        print("sessionの作成")

                //リクエストをタスクとして登録
                let task = session.dataTask(with: req, completionHandler: {
                    (data, response ,error) in


                })
                //request送信
                task.resume()
            }

}

・iPhone画面 (結果画面)
※iPhoneの結果画面にはswiftコードを定義してません

・Java Servlet

TestServlet
package servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class testServlet
 */
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public TestServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");

        System.out.println("サーブレットのPOSTメソッドに到達");
        System.out.println("getAttributeで受け取った場合:" + "iPhoneアプリから送られた文字列は" + request.getAttribute("test") + "です。");
        System.out.println("getParameterで受け取った場合:" + "iPhoneアプリから送られた文字列は" + request.getParameter("test") + "です。");
    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む