【Swift】UserNotification(ローカル通知)の使い方

 

完成図 

 ↓destination == "Ken"の時

f:id:nekokichi_yos2:20181009181823p:plain
f:id:nekokichi_yos2:20181009181826p:plain

 

解説

 

 ※注意点

・今回はバックグラウンドでの実装

・”送信する”を押して、すぐにアプリを閉じないと、ローカル通知が発動しない

・発動すれば、画面上部 & 通知センターに表示される

 

ストーリーボード

  

f:id:nekokichi_yos2:20181009181729p:plain

 

ソースコード

 

import UIKit
import UserNotifications

class ViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var honbunTextField: UITextView!
    
    //送り先を受け取る
    var destination = ""
    
    //本文を受け取る
    var message = ""
    
    //identifier用の変数
    let NotificationIdentifier = "ID"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //デリゲートを取得
        nameTextField.delegate = self
        
    }
    
    @IBAction func send(_ sender: Any) {
        //送り先を代入する
        destination = nameTextField.text!
        //本文を代入する
        message = honbunTextField.text!
        //メッセージを送信する
        gopush()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    //ローカル通知の許可等
    func gopush() {
        UNUserNotificationCenter.current().getNotificationSettings { (settings) in
            //もし通知が許可されているなら
            if settings.authorizationStatus == .authorized {
                //通知
                self.push()
                
            //もし通知が許可されてないなら
            } else {
                
                UNUserNotificationCenter.current().requestAuthorization(
                    options: [.sound,.badge,.alert],completionHandler: { (granted, error) in
                        
                    if let error = error {
                        print(error)
                    } else {
                        if (granted) {
                            self.push()
                        }
                    }
                        
                })
                
            }
            
        }
        
    }
    
    //ローカル通知内での処理
    func push() {
        
        //指定した文章と同じかどうか
        if message == "" {
            
            //UserNotificationのインスタンス
            let content = UNMutableNotificationContent()
        
            //送り主で条件分岐
            switch destination {
            case "Amy":
                //メッセージのタイトル
                content.title = "All right!!"
                //メッセージの本文
                content.body = "I will put on my favorite clothes!"
                //通知に貼り付ける画像のURL
                if let url = Bundle.main.url(forResource: "Amy", withExtension: "png"),let attach = try? UNNotificationAttachment(identifier: NotificationIdentifier, url: url, options: nil) {
                    content.attachments.append(attach)
                }
            case "Karen":
                content.title = "OK!"
                content.body = "I'm looking forward to watching a movie with everyone!"
                if let url = Bundle.main.url(forResource: "Karen", withExtension: "png"), let attach = try? UNNotificationAttachment(identifier: NotificationIdentifier, url: url, options: nil) {
                    content.attachments.append(attach)
                }
            case "Ken":
                content.title = "Oh Yes!"
                content.body = "Let's release the stress accumulated in the exam tomorrow at once!"
                if let url = Bundle.main.url(forResource: "Ken", withExtension: "png"), let attach = try? UNNotificationAttachment(identifier: NotificationIdentifier, url: url, options: nil) {
                    content.attachments.append(attach)
                }
            case "Mike":
                content.title = "I want to go early"
                content.body = "Finally I can get out of boring lessons and I can not wait for tomorrow's event"
                if let url = Bundle.main.url(forResource: "Mike", withExtension: "png"), let attach = try? UNNotificationAttachment(identifier: NotificationIdentifier, url: url, options: nil) {
                    content.attachments.append(attach)
                }
            default:
                content.title = "It is a mistake email."
                content.body = "Who are you? It is fine if sales"
            }
        
        //5秒後に通知
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3.0, repeats: false)
        
        //トリガーと内容を内包
        let notificationrequest = UNNotificationRequest(identifier: NotificationIdentifier,
                                                        content: content, trigger: trigger)
        
        //エラー処理
        UNUserNotificationCenter.current().add(notificationrequest) { (error) in
            if let error = error {
                print(error)
            } else {
                print("通知されました")
            }
        }
            
        } else {
            print("もう一度やり直してください。")
        }
        
    }
    
}

【Swift】CollectionViewのページめくりアニメーション

 

完成図

↓1度目のスワイプ            ↓2度目のスワイプ

f:id:nekokichi_yos2:20181009145908p:plain
f:id:nekokichi_yos2:20181009145947p:plain

 

解説

 

※注意点

・ページめくりを実装するには、

  1. Scroll DirectionをHorizontalに指定
  2. Scrolling - Horizontal Indicatorにチェック
  3. MidSpacingの値を0に指定

する必要がある。

f:id:nekokichi_yos2:20181009150631p:plain
f:id:nekokichi_yos2:20181009150626p:plain



・今回はカスタムセルを使ってるが、何もいじってないので、使わなくてもOKかも。

・CollectionViewのsizeItemForAtをview.frame.width/heightにする

・ViewControllerを生成し、ConllecionViewで実装する

 

ストーリーボード

 

f:id:nekokichi_yos2:20181009145448p:plain

 

ソースコード

 

import UIKit

class ViewController: UIViewController,UICollectionViewDelegate,
            UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.delegate = self
        collectionView.dataSource = self
        
    }
    
    @IBOutlet weak var collectionView: UICollectionView! {
        
        //カスタムセルをcollectionViewに登録
        didSet {
            collectionView.register(UINib(nibName: "CollectionViewCell",
                                          bundle: nil), forCellWithReuseIdentifier: "cell")
        }
        
    }
    
    //ページに表示する色
    let dataSource: [UIColor] = [.red, .green, .blue, .cyan, .yellow, .magenta]
    
    // MARK: - UICollectionViewDataSource
    
    func collectionView(_ collectionView: UICollectionView,
                        numberOfItemsInSection section: Int) -> Int {
        
        return dataSource.count
        
    }
    
    func collectionView(_ collectionView: UICollectionView,
                        cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cv = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        
        //ViewControllerを生成
        let viewController = UIViewController()
        //ViewControllerの位置とサイズを設定
        viewController.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
        //ViewControllerの背景色を設定
        viewController.view.backgroundColor = dataSource[indexPath.row]
        //collectionViewにViewControllerをセット
        cv.addSubview(viewController.view)
        
        return cv
        
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        return CGSize(width: view.frame.width, height: view.frame.height)
        
    }
}

【Swift】PickerView&DatePickerの使い方

 

完成図

 

↓”現在時刻”を押した場合         ↓”10分後へ”を押した場合

f:id:nekokichi_yos2:20181009142100p:plain
f:id:nekokichi_yos2:20181009142118p:plain

 

解説

 

「DatePicker」

・日時を扱うなら絶対にこれ

・NSDate()や正確な日時を指定するのに必要な処理を省略できる

・現在時刻をdatePicker.date = Date()だけで取得できる手軽さ

・??分後に移動する場合は、datePicker.date=Date(60(秒)*??(分))、だけでOK

 

「PickerView」

・構造はセルと同じ

・numberOfConponents, numberOfRowsInConponent, titleForRowは、セルが用意するnumberOfSection, numberRowInSection, cellForAt、と似ている

・異なる処理や値を設定したPickerViewの配置は困難。

・1つのPickerViewで値や処理を変えればいい

 

ストーリーボード

 

f:id:nekokichi_yos2:20181009141951p:plain

 

ソースコード

 

import UIKit

class ViewController: UIViewController,UIPickerViewDelegate,UIPickerViewDataSource {
    
    
    @IBOutlet weak var datePicker: UIDatePicker!
    @IBOutlet weak var textPicker: UIPickerView!
    
    let text:[String] = ["1","2","3","4","5","6"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textPicker.delegate = self
        textPicker.dataSource = self
        
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    
    //DatePickerのメソッド
    @IBAction func nowDate(_ sender: Any) {
        datePicker.date = Date()
    }
    
    @IBAction func laterDate(_ sender: Any) {
        datePicker.date = Date(timeInterval: 60*10, since: datePicker.date)
    }
    
    @IBAction func beforeDate(_ sender: Any) {
        datePicker.date = Date(timeInterval: -60*10, since: datePicker.date)
    }
    
    
    //PickerViewのメソッド
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return text.count
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return text[row]
    }

}

【プログラミング】キーボードの入力位置を変えずにdeleteする

f:id:nekokichi_yos2:20181009141205p:plain

 

普通にdeleteキーを押せば、

f:id:nekokichi_yos2:20181009141153p:plain

左に入力位置が移動し、左の文字を消していく。

 

 

しかし、

入力位置を移動せず

右の文字を消していく

が可能だとしたら?

 

方法は、delete + fn、キーの同時押し。

f:id:nekokichi_yos2:20181009141224p:plain

右側の文字を消すのにわざわざ→キーを連打する人はおさらばできるだろう。

【Xcode】UserDefaultsでログイン機能 - 本格版

前回の記事で、UserDefaultsで簡易的なログイン機能を実装する方法を紹介しました。

 

しかし、ユーザー名に関係なく次回以降は無条件でログインできてしまう欠点がありました。

 

今回は、初回時に入力したユーザー名を入力しない限りログインできない仕組みをご紹介します。

 

 

プロセス

 

既に新規登録したか?

↓→してないなら②へ

したなら①へ

 

①既に新規登録した

textFieldの入力値が正しいか?

↓→正しい→ログイン成功

間違い

アラートで再入力を要求

 

②まだしてない

 

textFieldに入力したか?

↓→した→新規登録完了

してない

アラートで再入力を要求

 

ソースコード

import UIKit
import EMAlertController

class ViewController: UIViewController,UITextFieldDelegate {
    
    
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var loginButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.delegate = self
        
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        //もしユーザー名が保存されてるなら
        if let _ = UserDefaults.standard.object(forKey: "userName") {
            //ボタンの文字を"ログイン"に変更
            loginButton.setTitle("ログイン", for: .normal)
        //保存されてないなら
        } else {
            loginButton.setTitle("会員登録", for: .normal)
        }
        
    }

    @IBAction func login(_ sender: Any) {
        
        //既にログインしてるなら
        if let _ = UserDefaults.standard.object(forKey: "userName") {
            
            //もし入力した値が保存したユーザー名と同じなら
            if textField.text ==  UserDefaults.standard.object(forKey: "userName") as? String {
                
                //遷移
                performSegue(withIdentifier: "goTimeLine", sender: nil)
                
            //違うなら
            } else {
                
                //アラート作成
                let alert = EMAlertController(title: "ユーザー名が正しくありません。", message: "")
                //アクション作成
                let action = EMAlertAction(title: "OK", style: .cancel)
                //アラートにアクションを追加する
                alert.addAction(action: action)
                //アラートを表示
                present(alert, animated: true, completion: nil)
                
            }
            
        //新規登録する場合
        } else {
            
            //もしユーザー名が記入されてるなら
            if textField.text != "" {
                
                //UserDefaultsの変数
                let ud = UserDefaults.standard
                //ユーザー名をUDに保存
                ud.set(textField.text, forKey: "userName")
                //画面遷移
                performSegue(withIdentifier: "goTimeLine", sender: nil)
                
            //もしユーザー名が記入されてないなら
            } else {
                
                //アラート
                let alert = EMAlertController(title: "ユーザー名を入力してください", message: "")
                //アクション
                let action = EMAlertAction(title: "OK", style: .cancel)
                //アラートにアクションを追加する
                alert.addAction(action: action)
                //アラートを表示
                present(alert, animated: true, completion: nil)
                
            }
            
        }
        
    }
    
    //画面をタップするとキーボードが閉じる
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        textField.resignFirstResponder()
    }
    
    
}

【Swift】tableViewでセルを表示する

 

完成画面

 

f:id:nekokichi_yos2:20181007224631p:plain

 

必要なプロセス

 

プロセス - StoryBoard編

 

  1. tableView、tableViewCellを配置
  2. tableViewのIdentifierを設定
  3. tableViewにOutletで紐付け
  4. tableViewCellの上にUILabelを配置
  5. UILabelのtagを1に設定

 

1.

f:id:nekokichi_yos2:20181007224626p:plain

 

2.

f:id:nekokichi_yos2:20181007224635p:plain

 

5.

f:id:nekokichi_yos2:20181007224642p:plain

 

プロセス - ViewContoller編

 

  1. UITableViewDelegate,UITableViewDataSourceを記述
  2. viewDidLoad()でdelegate,datasourceを宣言..?
  3. 表示したいテキストを配列で用意(今回はtextという名前の配列)
  4. セルの表示に必要な4つの関数を用意(numberOfSections、numberOfRowsInSection、cellForRowAt、heightForRowAt)

 

1.

f:id:nekokichi_yos2:20181007224621p:plain

 

 

numberOfSections:セルを分類するカテゴリの数

               今回は分類しないので return 1

numberOfRowsInSection:セルの数

               text配列の要素数を指定

cellForRowAt:セルを生成に関わる関数

               StoryBoardで設定したセルのIdentifierを記述

               //で設定したtagでUILabelを生成(今回は1)

heightForRowAt:1つのセルの高さ

               お好みで

 

ソースコード

 

import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    
    let text = ["one","two","three","four","five","six"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return text.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        let textLabel = cell.viewWithTag(1) as! UILabel
        textLabel.text = text[indexPath.row]
        
        return cell
        
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 62
    }

}

 

※補足:セル上にテキストを表示させる方法について

 

セルにテキストを表示る場合、

 

  • 直接代入:cell.textLabel?.text = “文字列”
  • UILabel:cell.viewWithTag() as! UILabel
  • カスタムセル

 

の3通り。

 

今回は、セルの上に配置したUILabelで、セルにテキストを表示させた。

 

注意点としては、セルの配置したUILabelを紐付けすることができないこと。

 

ImageViewはかろうじて配置できるが、それ以外はおそらく不可能。

 

なので、tagを指定し、cell.viewWithTagによって、tagで指定したUI部品を変数で生成する必要がある。

 

 

 

 

 

【teratail】Firebaseに保存されてるデータの受け取り方

自分でFirebaseと連携する機能を実装した際、思い通りに動作しなかったので、復習として書きます。

 

 

質問

 

teratail.com

 

実現したいこと

 

Firebaseに保存した全てのデータをタイムライン形式でセルに表示したい

 

困っていること

 

Firebaseに保存されてるはずなのに、セル(データ)が1つしか表示されない。

 

解決方法

 

原因は、データがまとめて格納されたposts配列をfor文内に書いてしまい、せっかく受け取ったデータを消してしまったことです。

 

なので、posts配列をfor文に入る前に初期化すればいいのです。

 

解決後のコード

 

ref.child("post").observeSingleEvent(of: .value) { (snap,error) in
     
    //Firebaseのデータが入ったsnapを配列に格納
    let snapData = snap.value as? [String:NSDictionary]
            
    if snapData == nil {
         return
    }
            
 //NSObjectクラスの”Post.swift”に表示したいデータを格納する変数を宣言してます
 //1つのセルに表示したいデータ群をposts配列に格納していきます
    self.posts = [Post]()
            
    for (_,post) in snapData! {

	 //解決前は、ここにposts配列の初期化をしてました
	 //self.posts = [Post]()
                
	 //取得したデータを毎回posstが受け取ります
	 //新品のデータを受け取るために毎回初期化します
            self.posst = Post()
                
            if let title = post["title"] as? String, let sentence = post["sentence"] as? String, let pictureURLString = post["pictureURLString"] as? String, let category = post["category"] as? String {
                    
                    self.posst.title = title
                    self.posst.sentence = sentence
                    self.posst.pictureURLString = pictureURLString
                    self.posst.category = category
                   
            }
                
            self.posts.append(self.posst)
                
            print(self.posts)
                
    }
            
    self.tableView.reloadData()
            
}

 

解決前は、

 

1つ目のデータを受け取る

初期化

2つ目のデータを受け取る

初期化

 

というプロセスで、最終的にposts配列には最後に入れたデータしか残りませんでした。

 

しかし、解決後は、

 

初期化

1つ目のデータを受け取る

2つ目のデータを受け取る

3つ目のデータを受け取る

 

というプロセスに変わり、全てのデータをきちんと格納できています。