Swift iPad iPhone(別StoryBoard)実装方法

ストーリーボードは当然、iphone,ipad2つ用意する必要があります。
問題は、ソース。
同じ処理を書くことが多い。
それで、同じファイルに記述する方法を記載します。

まずファイル名。これは何でも良いが理解しやすい名前が良い。関係する名前がいいと思います。
この場合は

MsPersonData.swift

でファイル名を作成。

iPhone,iPadともにMsPersonDataUniを継承する。 MsPersonDataUniに共通処理を記載。

import UIKit
import MapKit
import CoreData
/// 訪問先(iPad用ペイン)
class MsPersonDataPad: MsPersonDataUni {

@IBAction ~

ここで画面接続もする。 その際ストーリーボードはそれぞれiPhone,iPad違うファイルに接続可能。

iPhone画面 MsPersonDataUniを継承する。

/// 訪問先(iPhone用画面)
class MsPersonDataPhn: MsPersonDataUni {
    //..........................................................................
    //MARK: Outlets & Properties
    @IBOutlet weak var mkMap: MKMapView!

基本的に、画面接続のところが大多数を占めると思われる。 共通の処理はここに記載

ここでは画面接続もiPhone,iPadともに接続可能。

はじめは驚くかもしれない. 同じuiMailを接続している。

MsPersonDataUniはここで初めてUIViewControllerを継承している。

/// 訪問先本体(iPad & iPhone 共通)
class MsPersonDataUni: UIViewController, MsMapAgentDelegate {
  @IBOutlet weak var uiMail: UITextField!

こちらはiPad

f:id:happy_teeth_ago:20180828135722p:plain:w500

こちらはiPhone

f:id:happy_teeth_ago:20180828135802p:plain:w500

それぞれ接続できている。

これはstoyBoardでそれぞれiPhone,iPadのクラスを指定している為

注意!segueでのprepar for segue はそれぞれiPhone,iPadのクラスに記載する必要があります。

次にAppDelegateの実装

//自作の関数
   func storyboardName()->String{
        if ((UIDevice.current).userInterfaceIdiom) == UIUserInterfaceIdiom.phone {
            //iPhoneのストーリーボードを返す
            return "iPhone"
        }else{
            return "Main"
        }
    }

上記を呼ぶのがここ

//デリゲートメソッド
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
   
        //画面の向きの設定 上記関数を呼んでいる
        let storyboard: UIStoryboard = UIStoryboard(name: storyboardName(), bundle: nil)
        
        self.window = UIWindow(frame: UIScreen.main.bounds)
        
        self.window?.rootViewController = storyboard.instantiateInitialViewController()
        //表示の更新 uiwindowは更新しないといけない
        self.window?.makeKeyAndVisible()

        let path = NSSearchPathForDirectoriesInDomains(
            .applicationSupportDirectory, .userDomainMask,true).first!
        
        return true
    }


    //初回起動時の画面の向き
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        
        if ((UIDevice.current).userInterfaceIdiom) == UIUserInterfaceIdiom.phone {
            //iPhoneのストーリーボードを返す
            return [.portrait , .portraitUpsideDown]
        }else{
            //ipadのストーリーボードを返す
//.landscapeでアクセスできるのは、readonlyのプロパティーだから
            return .landscape
        }
        
    }

デバイスの回転や、画面の向きの制御を行う UIInterfaceOrientationMask 定義

構造体で取得のみです。だから .portrait でアクセスできます。

public struct UIInterfaceOrientationMask : OptionSet {

    public static var portrait: UIInterfaceOrientationMask { get }

    public static var landscapeLeft: UIInterfaceOrientationMask { get }

Swift 起動時間が長いとクラッシュする問題と対策

iOSは読み込み時に、時間がかかりすぎるとクラッシュする。
今回はアプリのCoreDataのCSV読み込み時に時間がかかり、実機だと、起動できない問題が発生。 ちなみにシミュレーターだと、スペックが良いせいか、クラッシュしない。

対策

起動時間をへらす必要がある。しかし、CoreDataへの読み込み時間は変更のしようがない。 それで、StoryBoardで、別ウインドウを作成し、そこへ処理を投げることにした。 f:id:happy_teeth_ago:20180828132316p:plain:h500

  override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear()
 
        if datDics.count == 0 {
            //起動時間が15秒すぎるとアプリが落ちるのでIndicatorViewにて読む込処理をする.
            performSegue(withIdentifier: "toIndicator" , sender: nil)
        }
    }

viewDidAppearで処理をするところがポイント

viewDidLoadはまだ画面が生成されていない。そこでsegueで画面遷移しても、望む結果は得られなかった。

遷移先の画面ではインジケーターを表示して、先程起動時間が長いと言われていた関数を読み込む。

   override func viewDidAppear(_ animated: Bool) {
         //インジケーター表示
        label_indicator.startAnimating()
        //問題の読み込みに時間がかかる関数。約1万件のデータをマイグレーションしている。coredataは関数内でsave しているので問題ない。
        Dic_iphone().setupDatDicFromCsv()
        //終わったらすぐに元画面に戻る。
        performSegue(withIdentifier: "backToTop", sender: nil)
    
    }

viewDidAppearとviewDidLoadは同じキューでは無いようです。 わかる方いたら教えていただけますか.

Rails チュートリアル following followerの関係について

f:id:happy_teeth_ago:20180822124303p:plain

この画面の左側のfollowing followerの数はどのようにして表示されているか?を説明します。

とりあえずアドレスがusers/4なのでUsersControllerのshowが呼ばれているはず

UsersController

  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
  end

userのidを取得

Viewの左画面

   <aside class="col-md-4">
            <section>
                <%= render 'shared/user_info' %>
            </section>
            <section>
                <%= render 'shared/stats' %>
            </section>
            <section>
                <%= render 'shared/micropost_form' %>
            </section>
        </aside>
        <div class="col-md-8">
            <h3>Micropost Feed</h3>
            <%= render 'shared/feed' %>
        </div>

真中部分のここが数字を表示している。

                <%= render 'shared/stats' %>
   

'shared/stats'が呼ばれている
Viewの中のパーシャル(部分テンプレート)を見て見ます。

_shared/stats

<% @user ||= current_user %>
<div class="stats">
    <a href="<%= following_user_path(@user) %>">
    
        <strong id="following" class="stat">
            <%= @user.followed_users.count %>
        </strong>
        following
        </a>
        <a href="<%= followers_user_path(@user) %>">
            <strong id="followers" class="stat">
                <%= @user.followers.count %>
            </strong> 
            followers
        </a>
</div>

<%= @user.followed_users.count %>で数字を表示

followed_usersはどこから来たのか?このブログのポイント

Userモデルに関連を宣言

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :relationships, foreign_key: "follower_id", dependent: :destroy
  has_many :followed_users, through: :relationships, source: :followed

最後の行has_many :followed_users, through: :relationships, source: :followed

この行がポイント Userモデルはfollowed_usersという、プロパティを持つようになる。 それはrelationshipsテーブルを用いて関連付けられる。

そしてfollowedモデルを所有できるようになります。

followedモデルって何?
どこにも書いてないよ?

userテーブルのどこにもその様なカラムはない。

ないのになぜ、アクセスできるのか?

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "name"
    t.boolean "admin", default: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

Relationshipのモデル

followedというテーブルと関連づいているように見える。しかし実態はUser

class Relationship < ApplicationRecord
    belongs_to :follower , class_name: "User"
    belongs_to :followed , class_name: "User"
    validates :follower_id, presence: true
    validates :followed_id, presence: true
end

has_many :followed_users, through: :relationships, source: :followed つまりこれは

relationshipsという中間テーブルを通して、followedを所有することになります。

少し難しいかも?
ポイントは、follower followedも同じuserテーブルを参照していることにあります。
自分自身のテーブルを参照しているのですね。
2つのユーザーが同名の外部キーを持つことはできません。
それで、何らかの方法でkeyの名前を変更しないといけない。

followerとしてフォローするのと
followedとしてフォローされているものは違う2つの値をそれぞれ持つのです。

routesファイルを見てみます。

Rails.application.routes.draw do
   devise_for :users, :controllers => {
    :registrations => "registrations"
  }
    resources :users, only: [:show, :index, :destroy] do
    member do
      get :following, :followers
      end
  end

これによりルーティングにより下記のrouteが定義されます。

f:id:happy_teeth_ago:20180822184635p:plain 画像参照元 Ruby on Rails チュートリアル:実例を使って Rails を学ぼう

なぜこれで行けるかというと、

Userコントローラーfollowingメソッドにより、followed_idカラムを取得しています。

def following?(other_user)
    relationships.find_by(followed_id: other_user.id)
  end
  
  def follow!(other_user)
    relationships.create!(followed_id: other_user.id)
  end

これにより、取得できるのですね。

Relationshipテーブルにはfollowed_idというカラムがあります。

また、余談ですがindexも設定してあります。

  create_table "relationships", force: :cascade do |t|
    t.integer "follower_id"
    t.integer "followed_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["followed_id"], name: "index_relationships_on_followed_id"
    t.index ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true
    t.index ["follower_id"], name: "index_relationships_on_follower_id"
  end

インデックスを簡単に言うと対象のカラムのデータを取り出し、高速に検索できるように手を加えて保存しておいたものです

何度も頻繁に使うカラムだから、高速で検索できるようにしておいたのですね。

マイグレーションファイル生成時に設定しています。

class CreateRelationships < ActiveRecord::Migration[5.1]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true
  end
end

ここで add_index :relationships, [:follower_id, :followed_id], unique: true
とあるので、あるユーザーが同じユーザーを複数回フォローすることを防ぐ事ができます。

流れをまとめると

UsersControllerのshowが呼ばれる

Viewには<%= @user.followed_users.count %>とある

followed_usersはどこから来たのかというと

Userモデルに関連付けがある。

has_many :followed_users, through: :relationships, source: :followed

Userモデルはfollowed_usersにアクセスできる。 それはrelationshipsテーブルを用いて関連付けられる。

そしてfollowedモデルを所有できるようになります。

relationshipsモデル。 実態はUserモデルとなっている。

belongs_to :followed , class_name: "User"

Userコントローラーの関数にて取得できる。

def following?(other_user)
    relationships.find_by(followed_id: other_user.id)
  end

ソースをきちんと読むのは大切ということがわかった。 間違えている点がありましたら、ご指導よろしくおねがいします。

Swift GoogleBook APIで書籍検索

作業フロー

1-エンドポイントの調査

2-API Keyの取得

3-jsonデータの分析

4-コード作成

5-iOSにてView作成

1-エンドポイント、(接続するアドレス)はここに記載されている

https://developers.google.com/books/docs/v1/using

具体的にはここ

https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes&key=yourAPIKey

&keyのkey部分へ自分のキーを配置する

2-keyを取得してエンドポイントへアクセスする。するとこの様なjsonデータが返却される。

これを解析する。 今回はここに時間をかける。

{
 "kind": "books#volumes",
 "totalItems": 100,
 "items": [
  {
   "kind": "books#volume",
   "id": "_oG_iTxP1pIC",
   "etag": "fPjIOwmEaME",
   "selfLink": "https://www.googleapis.com/books/v1/volumes/_oG_iTxP1pIC",
   "volumeInfo": {  **//ここの配下のtitleとdescriptionを取得したい**
    "title": "Flowers for Algernon",
    "authors": [
     "Daniel Keyes"
    ],
    "publisher": "Houghton Mifflin Harcourt",
    "publishedDate": "2007-12-01",
    "description": "The beloved, classic story of a mentally disabled man whose experimental quest for intelligence mirrors that of Algernon, an extraordinary lab mouse.",
    "industryIdentifiers": [
     {
      "type": "ISBN_10",
      "identifier": "0547539630"
     },
     {
   

今回はtitleとdescriptionを取得することを目的とする。

構造体を宣言

欲しいものだけ最低限取得する。

型はエンコード、デコードできる、Codable型

まずitemsの取得、items配下は配列になっているのでItem型の配列とする。 nilになる可能性のないものはオプショナル型でなくても良い。

jsonデータ

"items": [ //ここで、配列ということがわかります。
  {
   "kind": "books#volume",
   "id": "_oG_iTxP1pIC",

Swiftでの記載

struct SearchResut: Codable {
    let kind:String
    let items:[Item] // Item型の配列
}

更にたどっていくと、volumeInfoの下に目的のtitleとdescriptionがある。
ここはjsonオブジェクトなので、配列ではない。
VolumeInfoはVolumeInfo型にする。このようにして、型だけを継承していくイメージ。
あくまでもイメージ。構造体は継承できない。

struct Item: Codable{
    let volumeInfo:VolumeInfo
    
}

VolumeInfoの下に目的のtitleとdescriptionがある。 それは、String型
これ以上の階層は追っかけない。

struct VolumeInfo: Codable{
    let title:String
    let description:String
}

これで、受け取る用意はできた。 あとはjsonの取得。

 //リクエストURL作成
        guard let req_url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(keyword_encode)") else{
            return books

nilになる可能性があるので、guard文にしておく。
httpの取得は、通信エラーなどでnilが返却されることがあるので注意が必要。

本当は非同期処理がいい。 NSURLSessionクラスを利用すれば、非同期ででききる 他にもAlamofireという非同期処理のライブラリがある。これがいいらしい。

cocoapods.org

エラーの可能性があるので, Swiftのエラー処理である、do~catchで囲む nilでも落ちないので便利。

static func searchBooks(keyword:String)->[BookInfo]{
   //返却したい型
   var books:[BookInfo] = []

   do{
           //urlをデータ型へ変換
            let data = try Data(contentsOf: req_url)  //これで非同期処理にならない。
            //jsonをデコード
            let searchResult = try JSONDecoder().decode(SearchResut.self, from: data)
            
            //デコードしたjsonを詰め込むsearchResultがトップなので、itemsはそのプロパティとして持っている
            for item in searchResult.items{
     //item配下のvolumeInfoを詰め込む
                let volumeInfo = item.volumeInfo
                //volumeInfo配下のtitleを詰め込む
                let title = volumeInfo.title
                //volumeInfo配下のdescriptionを詰め込む。同じ階層にある
                let description = volumeInfo.description
                
                //最後に返却したい型を別の箇所で宣言している。bookInfo型 下記に記載
                let bookInfo = BookInfo(title: title, description: description)
                //配列に詰め込んでいく
                    books.append(bookInfo)
                }
            
        }catch{
            print("エラーが出ました")
        }
        //BookInfo型の配列で返却
        return books
    }

bookInfo型

 struct BookInfo{
        var title:String
        var description:String?
    }

これで戻り値でほしい書籍データを取得できた。

必要ないと思うが,TableViewへの表示も記載しておく。

    //add
    var bookList:[BookInfo]?

  override func viewDidLoad() {
        super.viewDidLoad()
        
        //先程の関数 
        bookList = searchBooks(keyword: keyword!)
        
//tableViewのデリゲートメソッドを2つ記載しないとエラーになる。
   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return  bookList!.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "bookCell", for: indexPath)
        cell.textLabel?.text = bookList![indexPath.row].title
        cell.textLabel?.font = UIFont.systemFont(ofSize: 15)
        return cell
    }

Pyton3 cloud9 環境設定 |未完|

cloud9のpythonバージョンが2から3へ切り替わらない ここを参照、しかし問題は未解決。
Cloud9でpython3を動かす時にやっておきたいこと

pipとは

ピップと読むらしい。 パイソンのパッケージ管理ツール RailsのBundlerのようなものか。 またPyPI(パイピーアイ)というライブラリを集めたサイトがある。 Jupyter Notebookをインストールしておくとサンプルコードを追いやすくなる。

http://jupyter.org/install

そのためにAnacondaをインストールする。

https://www.anaconda.com/download/#macos

ec2-user:~/environment $ python --version Python 2.7.14 バージョンが2なので3へ変更 まずメニュー[Cloud9]→[Preferences]
f:id:happy_teeth_ago:20180816123801p:plain

which pythonでするとエイリアスだった。 よってpython3へエイリアスを設定。 python3のフォルダは以下

ec2-user:/usr/bin $ ls -l| grep python
lrwxrwxrwx   1 root root         24 Aug  7 13:11 python -> /etc/alternatives/python
lrwxrwxrwx   1 root root         17 Aug  7 13:11 python2 -> /usr/bin/python27
-rwxr-xr-x   1 root root       5120 May  2 18:32 python27
-rwxr-xr-x   1 root root       5120 May  2 18:32 python2.7
-rwxr-xr-x   1 root root       1846 May  2 18:31 python2.7-config
lrwxrwxrwx   1 root root         25 Aug  7 13:11 python3 -> /etc/alternatives/python3
-rwxr-xr-x   3 root root       6872 Apr 26 00:16 python36
-rwxr-xr-x   3 root root       6872 Apr 26 00:16 python3.6
lrwxrwxrwx   1 root root         17 Aug  7 13:11 python3.6-config -> python3.6m-config
-rwxr-xr-x   3 root root       6872 Apr 26 00:16 python3.6m
-rwxr-xr-x   1 root root        173 Apr 26 00:16 python3.6m-config
-rwxr-xr-x   1 root root       3373 Apr 25 23:57 python3.6m-x86_64

確認して

ec2-user:/usr/bin $ alias python='/etc/alternatives/python3'
ec2-user:/usr/bin $ python --version
Python 3.6.5

と思いきやpipのバージョンが違う

ec2-user:/usr/bin $ readlink -f /usr/bin/python
/usr/bin/python36
ec2-user:/usr/bin $ pip --version
pip 9.0.3 from /usr/lib/python2.7/dist-packages (python 2.7)
ec2-user:/usr/bin $ python3 --version
Python 3.6.5
ec2-user:/usr/bin $ pip3 --version
bash: pip3: command not found

pip3がないと言われているのでインストールしようと思ったが

AWS Cloud9でPython3を使う方法 のサイトによると

書き換え後はsourceコマンドをお忘れなく あとある。もうちょっと書いてくれると嬉しい sourceとは、環境設定フアイルをカスタマイズしたときには、現在のシェル環境に反映させるためのもの シェルファイルは.tcshrcなどrcの文字が最後についていることが多い。 ~/.bashrc のようである。

ec2-user:/usr/bin $ source ~/.bashrc
function

戻っているいみがわからない。

ec2-user:/usr/bin $ python --version
Python 2.7.14

今度はpython36にリンクを貼る

ec2-user:/usr/bin $ alias python='/usr/bin/python36'
ec2-user:/usr/bin $ python --version
Python 3.6.5

sourceコマンドを実行するとバージョンが戻ってしまう。??

ec2-user:/usr/bin $ source ~/.bashrc
function
ec2-user:/usr/bin $ python --version
Python 2.7.14

?? 今度はパスを指定せずにエイリアスを設定。 うまく行った。?? 理由がわかならいので、良くない。
どなたか理由教えていただけますか?

ec2-user:/usr/bin $ alias python=python36
ec2-user:/usr/bin $ python --version
Python 3.6.5

そしてどちらのバージョンを使うかを決めるコマンドを入力。

ec2-user:/usr/bin $ sudo update-alternatives --config python
ec2-user:/usr/bin $ pip --version
pip 9.0.3 from /usr/lib/python3.6/dist-packages (python 3.6)

モデルが詰まった機械学習パッケージをインストール

pip install scikit-learn

しかしエラー

Exception:
Traceback (most recent call last):
  File "/usr/lib/python3.6/dist-packages/pip/basecommand.py", line 215, in main
    status = self.run(options, args)
  File "/usr/lib/python3.6/dist-packages/pip/commands/install.py", line 342, in run

Rails 環境格闘記 !未完成!

Ignoring executable-hooks-1.4.2 because its extensions are not built. Try: gem pristine executable-hooks --version 1.4.2

このエラーが出る。 nokogirを指定の通り入れてもだめ。 この場合pathが通っていないことが多い よってgem の環境を確認.

 $ gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 2.7.6
  - RUBY VERSION: 2.5.1 (2018-03-29 patchlevel 57) [x86_64-linux]
  - INSTALLATION DIRECTORY: /usr/local/rvm/gems/ruby-2.4.1
  - USER INSTALLATION DIRECTORY: /home/ec2-user/.gem/ruby/2.5.0
  - RUBY EXECUTABLE: /home/ec2-user/.rbenv/versions/2.5.1/bin/ruby
  - EXECUTABLE DIRECTORY: /usr/local/rvm/gems/ruby-2.4.1/bin
  - SPEC CACHE DIRECTORY: /home/ec2-user/.gem/specs
  - SYSTEM CONFIGURATION DIRECTORY: /home/ec2-user/.rbenv/versions/2.5.1/etc
  - RUBYGEMS PLATFORMS:
    - ruby
    - x86_64-linux
  - GEM PATHS:
     - /usr/local/rvm/gems/ruby-2.4.1
     - /usr/local/rvm/gems/ruby-2.4.1@global
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :backtrace => false
     - :bulk_threshold => 1000

rbenvで入れたのに実行環境がRVMになっている?? 再度rvmで入れることが可能なのか? このサイトを見ると不可能にちかい。というかエラーになること間違いない。

RVMからrbenvに移行した手順まとめ · GitHub rvmインストールしていないのだから、pathをrbenvに向けてあげればいいだけ。 以下明日に続く

Swift httpsサイトへ接続設定

infoPolistへ以下を追加

f:id:happy_teeth_ago:20180810202014p:plain

App Transport Security Settings

を入力して三角マークが出ている同じ行でプラスボタン押すのがポイント

Allow Arbitrary Loadsを更に入力。

しかしシミュレーターがアマゾンへ接続できない。

他のサイトならできるのに。?? f:id:happy_teeth_ago:20180810204958p:plain

アバストセキュリティーが邪魔してた。

ここから

f:id:happy_teeth_ago:20180810205148p:plain

WEBシールドを無効化する。

f:id:happy_teeth_ago:20180810205210p:plain