2017年8月21日 星期一

iOS Swift Multiple Language Localizable 切換多語系

不是認手機語系, 而是直接在app上切換語系

1.在Main.storyboard 拉三個按鈕與一個Label


2.與ViewController.swift建立連接
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var labMessage: UILabel!
    

    override func viewDidLoad() {
        super.viewDidLoad()
    }

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

    @IBAction func btnEnglish(_ sender: UIButton) {
        UserDefaults.standard.set("Base", forKey: "lang") //儲存語系為基本
        setMessage()
    }
    
    @IBAction func btnJapan(_ sender: UIButton) {
        UserDefaults.standard.set("ja", forKey: "lang") //儲存語系為日文
        setMessage()
    }
    
    @IBAction func btnChinses(_ sender: UIButton) {
        UserDefaults.standard.set("zh-Hant", forKey: "lang") //儲存語系為繁體中文
        setMessage()
    }
    
    func setMessage() {
        labMessage.text = "A Song of Ice and Fire".localized //使用擴展功能 localized
    }

}

extension String{

    var localized: String {
                
        let lang = UserDefaults.standard.string(forKey: "lang")
        
        let bundle = Bundle(path: Bundle.main.path(forResource: lang, ofType: "lproj")!)

        return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
        
    }
}

3.建立Localizable.strings檔案
New File... -> 找到 String File  -> 輸入Localizable.strings -> Create
建立完成後點選檔案->右邊的 ->再點選Localize...

4.回到PROJECTInfo 點選Localizations的 + 會出現所有語言, 這裡選擇Japanese(ja)與Chinses(zh-Hant), 括弧內為設定文字對應的縮寫

勾選建立的Localizable.strings就好

 接著Localizable.strings會出現三個檔案

分別在 Localizable.strings(Base)輸入
"A Song of Ice and Fire" = "A Song of Ice and Fire";

Localizable.strings(Japanese)輸入
"A Song of Ice and Fire" = "氷と炎の歌";

Localizable.strings(Chinese(Traditional))輸入
"A Song of Ice and Fire" = "冰與火之歌"; 





檔案下載:
https://github.com/terryyamg/MultipleLanguageTest
參考連結:
https://stackoverflow.com/questions/25081757/whats-nslocalizedstring-equivalent-in-swift
https://medium.com/lean-localization/ios-localization-tutorial-938231f9f881

2017年8月10日 星期四

iOS Swift Cache Image 簡易的Image Cache

簡單的Cache Image, 讓圖片不用一直下載,造成卡卡的問題

1.建立一個Project, 先在Info.plist ->右鍵 Open AS -> 選Source Code
在dict內加入,
NSAppTransportSecurity
NSAllowsArbitraryLoads 

2.接著建立MyTableViewCell.swift, 後面在Cell建立Image View後拉過來對應
class MyTableViewCell: UITableViewCell {

    @IBOutlet weak var iv: UIImageView!
    
    var ivUrl: NSURL! //圖片來源url
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

3.然後在Main.storyboard拉一個Table View

在cell裡面加個Image View, 對應Class: MyTableViewCell
Table View拉一下dataSource與delegate到ViewController
記得給Cell的Identifier,這裡寫ImageCell

4.建立一個CustomImageCache.swift檔案
import UIKit

class CustomImageCache {
    
    static let sharedCache: NSCache = { () -> NSCache<AnyObject, AnyObject> in
        let cache = NSCache<AnyObject, AnyObject>()
        cache.name = "MyImageCache"
        cache.countLimit = 20 // Max 20 images in memory.
        cache.totalCostLimit = 10*1024*1024 // Max 10MB used.
        return cache
    }()
    
}

extension NSURL {
    
    typealias ImageCacheCompletion = (UIImage) -> Void
    
    
    var cachedImage: UIImage? {
        return CustomImageCache.sharedCache.object(
            forKey: absoluteString as AnyObject) as? UIImage
    }
    
    func fetchImage(completion: @escaping ImageCacheCompletion) {
        // 如果需要客製化取得資料在此做
        let task = URLSession.shared.dataTask(with: self as URL) {
            data, response, error in
            if error == nil {
                if let data = data, let image = UIImage(data: data) {
                    CustomImageCache.sharedCache.setObject(
                        image,
                        forKey: self.absoluteString as AnyObject,
                        cost: data.count)
                    DispatchQueue.main.async() {
                        completion(image)
                    }
                }
            }
        }
        task.resume()
    }
    
}

5.最後在ViewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var url = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 重複50張圖片
        url = Array(repeating: "http://g03.a.alicdn.com/kf/HTB1hvNUIFXXXXbBXFXXq6xXFXXX2/3D-Diamond-Embroidery-Paintings-Rhinestone-Pasted-diy-Diamond-painting-cross-Stitch-font-b-coffe-b-font.jpg", count: 50)
    }

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

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return url.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell: MyTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ImageCell") as! MyTableViewCell
        let ivUrl = NSURL(string: url[indexPath.row])
    
        cell.ivUrl = ivUrl // For recycled cells' late image loads.
        if let image = ivUrl?.cachedImage { //抓過了 -> 直接顯示
            cell.iv.image = image
            cell.iv.alpha = 1
        } else { //沒抓過 ->下載圖片
            cell.iv.alpha = 0
            // 下載圖片
            ivUrl?.fetchImage { image in
                // Check the cell hasn't recycled while loading.
                if cell.ivUrl == ivUrl {
                    cell.iv.image = image
                    UIView.animate(withDuration: 0.3) {
                        cell.iv.alpha = 1
                    }
                }
            }
        }
        
        return cell
    }
}



檔案下載:
https://github.com/terryyamg/CacheImag
參考來源:
http://www.splinter.com.au/2015/09/24/swift-image-cache/

2017年7月1日 星期六

iOS Swift Custom View Xib 客製化 視窗

建立一個上下左右跑進來的視窗,使用xib客製化元件

1.建立一個xib檔案 File -> New -> File ... 選擇View
2.MyView.xib


a.建立 MyView.xib 並拉一個Label到視窗
b.選擇View
c.設定屬性Size:Freeform 其他None

3.建立MyView.Swift
//
//  MyView.swift
//  JumpViewTest
//
//  Created by Terry Yang on 2017/6/30.
//  Copyright © 2017年 terryyamg. All rights reserved.
//

import UIKit

class MyView: UIView {
    
    @IBOutlet weak var labCustom: UILabel!
    
    var view:UIView!
    
    // Label Inspectable
    @IBInspectable
    var mytitleLabelText: String? {
        get {
            return labCustom.text
        }
        set(mytitleLabelText) {
            labCustom.text = mytitleLabelText
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup() {
        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [
            UIViewAutoresizing.flexibleWidth,
            UIViewAutoresizing.flexibleHeight
        ]
        addSubview(view)
    }
    
    func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "MyView", bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
        
        return view
    }
    
}

4.在MyView.xib

a.選擇File;s Owner
b.點擊Identity並選擇Class 對應到 MyView

5.連結Label到MyView.swift
6.於Main.storyboard建立四個按鈕

7.ViewController.swift


//
//  ViewController.swift
//  JumpViewTest
//
//  Created by Terry Yang on 2017/6/30.
//  Copyright © 2017年 terryyamg. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    var indexOfTop : Int    = 0
    var indexOfLeft : Int   = 0
    var indexOfRight : Int  = 0
    var indexOfBottom : Int = 0
    
    var topView : MyView?
    var leftView : MyView?
    var rightView : MyView?
    var bottomView : MyView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    //  Top 按鈕
    @IBAction func btnShowTop(_ sender: UIButton) {
        indexOfTop += 1
        
        if topView == nil {
            //建立客製化View視窗
            topView = MyView(frame: CGRect(x: 0, y: -150, width: UIScreen.main.bounds.size.width, height: 150)) //設定初始位置與大小
            topView?.backgroundColor = .red //背景顏色
            topView?.labCustom.text = "Oh! Top View" //顯示文字
            self.view.addSubview(topView!) //加入此視窗
        }
        
        if indexOfTop % 2 == 1 { //點擊展開視窗
            UIView.animate(withDuration: 0.5, animations: {
                self.topView?.frame.origin.y += 150
            }, completion: nil)
        } else { //點擊收起視窗
            UIView.animate(withDuration: 0.5, animations: {
                self.topView?.frame.origin.y -= 150
            }, completion: nil)
        }
    }
    
    // Left 按鈕
    @IBAction func btnShowLeft(_ sender: UIButton) {
        
        indexOfLeft += 1
        
        if leftView == nil {
            leftView = MyView(frame: CGRect(x: -150, y: 0, width: 150, height: UIScreen.main.bounds.size.height))
            leftView?.backgroundColor = .brown
            leftView?.labCustom.text = "It's Left"
            self.view.addSubview(leftView!)
        }
        
        if indexOfLeft % 2 == 1 {
            UIView.animate(withDuration: 0.5, animations: {
                self.leftView?.frame.origin.x += 150
            }, completion: nil)
        } else {
            UIView.animate(withDuration: 0.5, animations: {
                self.leftView?.frame.origin.x -= 150
            }, completion: nil)
        }
        
    }
    
    // Right 按鈕
    @IBAction func btnShowRight(_ sender: UIButton) {
        
        indexOfRight += 1
        
        if rightView == nil {
            rightView = MyView(frame: CGRect(x: view.frame.maxX, y: 0, width: 150, height: UIScreen.main.bounds.size.height))
            rightView?.backgroundColor = .green
            rightView?.labCustom.text = "It's Right"
            self.view.addSubview(rightView!)
        }
        
        if indexOfRight % 2 == 1 {
            UIView.animate(withDuration: 0.5, animations: {
                self.rightView?.frame.origin.x -= 150
            }, completion: nil)
        } else {
            UIView.animate(withDuration: 0.5, animations: {
                self.rightView?.frame.origin.x += 150
            }, completion: nil)
        }
        
    }
    
    // Bottom 按鈕
    @IBAction func btnShowBootom(_ sender: UIButton) {
        indexOfBottom += 1
        
        if bottomView == nil {
            bottomView = MyView(frame: CGRect(x: 0, y: view.frame.maxY, width: UIScreen.main.bounds.size.width, height: 150))
            bottomView?.backgroundColor = .black
            bottomView?.labCustom.text = "Hi! Bottom View"
            self.view.addSubview(bottomView!)
        }
        
        if indexOfBottom % 2 == 1 {
            UIView.animate(withDuration: 0.5, animations: {
                self.bottomView?.frame.origin.y -= 150
            }, completion: nil)
        } else {
            UIView.animate(withDuration: 0.5, animations: {
                self.bottomView?.frame.origin.y += 150
            }, completion: nil)
        }
    }
    
    
}


 檔案下載:
https://github.com/terryyamg/JumpViewTest
 參考連結:
https://stackoverflow.com/documentation/ios/1362/custom-uiviews-from-xib-files#t=201706280220545656796
https://www.youtube.com/watch?v=EBYdsYwoJVI&t=492s
https://www.youtube.com/watch?v=MMgfnrddOxY

2017年5月23日 星期二

Firebase Cloud Messaging 接收訊息 PHP Service

FCM兩種推播通知方式 - Notification與Data

----------------------------------FCM前置步驟可觀看----------------------------------
Step1:
https://litotom.com/2016/06/24/android-firebase-cloud-messaging-1/
注意:
apply plugin: 'com.google.gms.google-services'
要放在build.gradle(Module: app)這個檔案裡面的最下面

Step2:
https://litotom.com/2016/06/26/firebase-android-send-2/

-----------------------------------------------------------------------------------------------

1.在MyFirebaseMessagingService.java加入推播通知訊息
package com.terryyamg.fcmtest;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMessaging";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.i(TAG, "onMessageReceived:" + remoteMessage.getFrom());
        Intent intent = new Intent();   //點擊啟動到MainActivity頁面
        intent.setClass(this, MainActivity.class);
        showNotification(this, remoteMessage, intent);
    }

    // 顯示通知
    // remoteMessage.getData()         -  無論app在什麼狀態下皆會執行 MyFirebaseMessagingService(需對應Service設定的字串文字)
    // remoteMessage.getNotification() -  只會在app顯示時,執行app的接收訊息 MyFirebaseMessagingService
    private void showNotification(Context context, RemoteMessage remoteMessage, Intent intent) {
        NotificationManagerCompat manager = NotificationManagerCompat.from(this);
        PendingIntent iPending = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle(remoteMessage.getData().get("title")) //需對應Service設定的字串文字
                        .setWhen(System.currentTimeMillis())
                        .setDefaults(Notification.DEFAULT_SOUND)
                        .setContentText(remoteMessage.getData().get("body")) //需對應Service設定的字串文字
                        .setContentIntent(iPending)
                        .setAutoCancel(true);

        Notification notification = mBuilder.build();
        manager.notify(1, notification);

    }
}

2.Service推播 fcm_service.php
    #API access key from Google API's Console
    define( 'API_ACCESS_KEY', 'AAAA6iG_xzk:APA91bHxlAkx5byr66cIFk-CXAr2k3hvs4tGQV9nAzj2nw75kPC8_9eIyyZVaeUPYFYzAk10qV-D0Pwl5BsIGslvv32PI2ECap1KcmW7Iu6lyzh1A3eEE2TJ5jVsnPydWFgKjvh3J5hh' );
    $registrationIds = "dTDs6rF7h04:APA91bECmREiN_qgUJ2IBwagJRzWmVxDMWVpmjEnkgJi1FWMLAtFHGTSYVXA-0VTHMf-42mZH8qLBEdKHFs9Z6oCL1JUf0uJ232z26XJdpEuwMvVPFxunwAZPN9E4-6qKImQEb7b9iHw";
    #prep the bundle
    $msg = array
    (
     'body'  => 'Body  Of Notification',
     'title' => 'Title Of Notification'
     );
    //'notification' - 只會在app顯示時,執行app的接收訊息Service
    //'data'         - 無論app在什麼狀態皆會執行app的接收訊息Service
    $fields = array
    (
     'to'  => $registrationIds,
     'data'     => $msg
     );
    
    
    $headers = array
    (
     'Authorization: key=' . API_ACCESS_KEY,
     'Content-Type: application/json'
     );
    #Send Reponse To FireBase Server
    $ch = curl_init();
    curl_setopt( $ch,CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send' );
    curl_setopt( $ch,CURLOPT_POST, true );
    curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
    curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
    curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
    $result = curl_exec($ch );
    curl_close( $ch );
    #Echo Result Of FireBase Server
    echo $result;




檔案下載:
參考資料: