hayateasdf's blog

Unity,C#, javascript,C++,python,batなど

TypeScript + VSCode

TypeScript + VSCode

過去のCoffeeScriptのファイルをTypeScriptに移行。 そのときに感じたTypeScriptのメリット、デメリット。

メリット

インテリセンスで大体のミスは防げる。

これが一番デカい。これがすべてと言ってもよい。 自分で定義したクラス、ネームスペース、変数、関数、 enumその他すべてにインテリセンスがついてくれるので、 まず入力ミスは防げるし、コンパイルでバグは表示されるので、 そもそもバグでない。

型を定義しなくてもよい。

毎回型を作るのがめんどくさいときは、とりあえずanyを 入れておけばよい。最初にそこまで作り込む気がしない ときは、anyを入れておいて、後々管理する必要がある場合 に型定義していけばよい。

メタプログラミングで生成したtsファイルもコンパイル

bashなどのスクリプトと組み合わせることで、フォルダ内のファイルを データ化し、使用しているファイル削除された場合、データ化した部分でも エラーがでるので、自動化との組み合わせの相性は良いと思う。あとtsファイル を監視(Ctrl + Shift + B: tsc watch)しておけば、ファイルの変更に応じて 勝手にコンパイルしてくれるので楽。

例) imagesフォルダ内のファイルをgame.ASSETS = {};に追加する。

ts_file="./source/auto-generated.ts"

cat << EOT > $ts_file
namespace game {
  export let ASSETS = {
EOT

# images
for file in `\find ./images -type f`; do
  file_name="${file##*/}"
  key="${file_name%.*}"
  echo "    \"${key}\" : \"${file}\"," >> $ts_file
done

cat << EOT >> $ts_file
  };
}
EOT

デメリット

環境構築の必要がある。

そこまで難しくはない。

  • nodejs(nodist)のインストール
  • npm install -g typescript

  • tsconfig.json作成 tconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true
  }
}

main.ts

let v: number = 100;
print(`${v}`);

jsのライブラリの定義ファイル作成がめんどくさい可能性あり。

使いたいライブラリの*.d.tsファイルがない場合にどうすればいいか わからなくなる。超手抜きで作れば、インテリセンスの恩恵はほとんど 受けられないがTypeScriptのコンパイラに怒られなくて済む。

例) 適当に自作したBox2d.d.ts

interface Box2D {
  Common: any;
  Dynamics: any;
  Collision: any;
}

declare var Box2D: Box2D;

感想

CoffeeScriptより読みやすい。ちゃんと型を定義してあると、その構造の 意図が読み取りやすい。型定義がめんどくさくなったらとりあえずanyぶっこめ ばいいので、初心者でも気が楽。

Xcodeを使っていて、Swiftのインテリセンスが遅くてイライラしていたが、 VSCodeは超早いのでぜひ使ってみたほうが良い。

tableView + navigationController

f:id:hayateasdf:20171208102427g:plain

1 Storyboardで画面作成

  • 赤画面のStoryboard ID -> TableCell1
  • 青画面のStoryboard ID -> TableCell2
  • セルのIdentifier -> Cell

f:id:hayateasdf:20171208102453p:plain

2 UIViewControllerをUITableViewCellを作成 -> Classを接続

3 コードを書く

import UIKit

class TableViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    
    let ids = [
        "TableCell1",
        "TableCell2",
    ]
    var cacheControllers: [String: UIViewController] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableInit()
        
        for id in ids {
            cacheControllers[id] = storyboard?.instantiateViewController(withIdentifier: id)
        }
    }

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

extension TableViewController: UITableViewDataSource, UITableViewDelegate {
    
    func tableInit() {
        tableView.dataSource = self
        tableView.delegate = self
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return ids.count * 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
        
        cell.title.text = ids[indexPath.row % ids.count]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
        
        let controller = cacheControllers[cell.title.text!]
        controller?.title = cell.title.text!
        navigationController?.pushViewController(controller!, animated: true)
    }
}
import UIKit

class TableViewCell: UITableViewCell {

    @IBOutlet weak var title: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }

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

}

Swift3 自作デリゲート実装

ショップにアイテムが正常に追加、削除された場合のみ通知されるデリゲートを作る。

  • すでにあるアイテムの追加は無効
  • 存在しないアイテムの削除は無効

ShopNotify.swift

protocol ShopNotify {
    func shopNotify(addItem item: String)
    func shopNotify(removeItem item: String)
}

class Shop {
    var delegate: ShopNotify?
    var items: [String] = []
    
    func addItem(_ item: String) {
        if items.index(of: item) == nil {
            items.append(item)
            delegate?.shopNotify(addItem: item)
        }
    }
    
    func removeItem(_ item: String) {
        guard let index = items.index(of: item) else {
            return
        }
        
        items.remove(at: index)
        delegate?.shopNotify(removeItem: item)
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController, ShopNotify {

    // 通知
    func shopNotify(addItem item: String) {
        print("「\(item)」 が追加されました")
    }
    func shopNotify(removeItem item: String) {
        print("「\(item)」 が削除されました")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let shop = Shop()
        shop.delegate = self
        
        shop.addItem("鉄の斧")
        shop.addItem("世界樹の葉")
        shop.addItem("世界樹の葉")
        
        shop.removeItem("鉄の斧")
        shop.removeItem("紫の炎")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
「鉄の斧」 が追加されました
「世界樹の葉」 が追加されました
「鉄の斧」 が削除されました

JSON読み込みとアニメーション

f:id:hayateasdf:20171130185612g:plain

作り方

必要なUIViewをStoryboardで配置し、コードに接続する。

f:id:hayateasdf:20171130185640p:plain

f:id:hayateasdf:20171130185650p:plain

Podfile

pod 'Alamofire'
pod 'SwiftyJSON'

Util.swift

import Alamofire

class Util {
    class func jsonRequest(
        url: String,
        success: @escaping (_ data: Dictionary<String, Any>) -> Void,
        fail: @escaping (_ error: Error?) -> Void)
    {
        Alamofire.request(url, method: .get).responseJSON { response in
            if response.result.isSuccess {
                success(response.result.value as! Dictionary)
            } else {
                fail(response.result.error)
            }
        }
    }
}

TestJsonViewController.swift

import UIKit
import SwiftyJSON

class TestJsonViewController: UIViewController {

    @IBOutlet weak var indicator: UIActivityIndicatorView!
    @IBOutlet weak var text: UITextView!
    @IBOutlet weak var picker: UIPickerView!
    @IBOutlet weak var loadingView: UIVisualEffectView!
    
    let pickerData = [
        "http://ip.jsontest.com/",
        "http://headers.jsontest.com/",
        "http://date.jsontest.com",
        "http://echo.jsontest.com/insert-key-here/insert-value-here/key/value",
        "http://validate.jsontest.com/?json=[JSON-code-to-validate]",
        "http://cookie.jsontest.com/",
        "http://md5.jsontest.com/?text=[text to MD5]"
    ]
    var pickerSelect: String = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()

        loadingView.isHidden = true
        loadingView.alpha = 0
        indicator.hidesWhenStopped = true
        pickerSelect = pickerData[0]
        self.pickerInit()
    }
    
    @IBAction func onTouched(_ sender: Any) {
        loadingView.isHidden = false
        loadingView.alpha = 0
        
        UIView.animate(withDuration: 0.25,
        animations: {
            self.loadingView.alpha = 1.0
        }, completion: { flag in
            self.jsonResult(url: self.pickerSelect)
        })
    }
    func jsonResult(url: String) {
        indicator.startAnimating()
        Util.jsonRequest(
        url: url,
        success: { dic in
            let json = JSON(dic)
            
            UIView.animate(withDuration: 0.5,
            animations: {
                self.loadingView.alpha = 0.0
            }, completion: { flag in
                self.loadingView.isHidden = true
                self.indicator.stopAnimating()
                self.text.textColor = UIColor.black
                if let raw = json.rawString() {
                    self.text.textColor = UIColor.black
                    self.text.text = raw
                } else {
                    self.text.textColor = UIColor.red
                    self.text.text = "[JSON ERR]: jsonデータないみたいです。"
                }
            })
        }, fail: { error in
            
            UIView.animate(withDuration: 0.3,
            animations: {
                self.loadingView.alpha = 0.0
            }, completion: { flag in
                self.loadingView.isHidden = true
                self.indicator.stopAnimating()
                self.text.textColor = UIColor.red
                self.text.text = "[通信失敗]: " + error!.localizedDescription
            })
        })
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

// picker拡張
extension TestJsonViewController : UIPickerViewDelegate, UIPickerViewDataSource {
    func pickerInit() {
        picker.delegate = self
        picker.dataSource = self
//        picker.selectRow(1, inComponent: 0, animated: true)
    }
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return pickerData.count
    }
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return pickerData[row]
    }
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        pickerSelect = pickerData[row]
    }
}

円を描く

https://stackoverflow.com/questions/26578023/animate-drawing-of-a-circle

https://qiita.com/wilshar10/items/1782bdbf9f0cc665ccf5

f:id:hayateasdf:20171129190617g:plain

import UIKit

class TestCircleViewController: UIViewController {

    @IBOutlet weak var parentView: UIView!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let circleView = CircleView(frame: parentView.bounds)
        parentView.addSubview(circleView)
    }

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

}

struct Segment {
    var color: UIColor
    var value: CGFloat
}
class CircleView: UIView {
    var circleLayers: [CAShapeLayer]!
    let segments = [
        Segment(color: UIColor.green, value: 10),
        Segment(color: UIColor.lightGray, value: 20),
        Segment(color: UIColor.blue, value: 30),
        Segment(color: UIColor.red, value: 40),
    ]
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.gray
        
        let startAngle = -CGFloat(Float.pi * 0.5)
        let endAngle = startAngle + CGFloat(Float.pi * 2.0)
        
        let lineWidth = frame.size.width / 5
        let radius = (frame.size.width - lineWidth) / 2
        let circlePath = UIBezierPath(
            arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height / 2),
            radius: radius,
            startAngle: startAngle,
            endAngle: endAngle,
            clockwise: true)
        
        let valueCount = segments.reduce(0) { $0 + $1.value }
        
        var start: CGFloat = 0.0
        self.circleLayers = segments.map {
            let end = start + ($0.value / valueCount)
            let circle = CAShapeLayer()
            circle.path = circlePath.cgPath
            circle.fillColor = UIColor.clear.cgColor
            circle.strokeColor = $0.color.cgColor
            circle.lineWidth = lineWidth
            circle.strokeStart = start
            circle.strokeEnd = end
            layer.addSublayer(circle)
            start = end
            
            return circle
        }
        
        animationCircle(duration: 0.7)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }
    
    func animationCircle(duration: TimeInterval) {
        for item in self.circleLayers {
            let animation = CABasicAnimation(keyPath: "strokeEnd")
            animation.duration = duration
            animation.fromValue = 0
            animation.toValue = 1
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
            
            item.strokeEnd = 1.0
            item.add(animation, forKey: "animateCircle")
        }
    }
}

XcodeでシミュレータでBlurが反映されなかったとき。

気にしていなかったが以下のような警告が出ている。

Simulator user has requested new graphics quality: 10

Simulator > Debug > Graphics Quality Override > High Quality

ブラーがかかるようになる。

f:id:hayateasdf:20171129190328p:plain

f:id:hayateasdf:20171129190334p:plain

SwiftによるシンプルなRSSリーダーの作り方、を試してみた 2

SwiftによるシンプルなRSSリーダーの作り方

SwiftによるシンプルなRSSリーダーの作り方、を試してみた 1

二日目

サムネが読み込めない

OGP("og:image")がなんか取ってこれなかったので、HTMLReaderを使いmetaタグから 持ってくる方法にシフト。

これを使いするため、クラス名のYqlと関係なくなるので、APIManagerに 名前を変更。

import Alamofire
import HTMLReader

class APIManager {
    class func test_request() {
        APIManager.yqlRequest(
            url: "http://d.hatena.ne.jp/nitoyon/rss",
            query: "select title from rss",
            success: { (data: Dictionary) in print(data) },
            fail: { (error: Error?) in print(error!) }
        )
    }

    // rssデータからjsonへの変換
    // https://qiita.com/tmf16/items/d2f13088dd089b6bb3e4
    // https://qiita.com/mishimay/items/1232dbfe8208e77ed10e
    class func yqlRequest(
        url: String,
        query: String,
        limit: Int = 0,
        success: @escaping (_ data: Dictionary<String, Any>) -> Void,
        fail: @escaping (_ error: Error?) -> Void) {
        
        let host = "https://query.yahooapis.com/v1/public/yql"
        let limit_query = (limit == 0) ? "" : "limit \(limit)"
        
        let param: Parameters = [
            "format": "json",
            "q": "\(query) where url='\(url)' \(limit_query)"
        ]
        
        Alamofire.request(host, method: .get, parameters: param).responseJSON { response in
            if response.result.isSuccess {
                success(response.result.value as! Dictionary)
            } else {
                fail(response.result.error)
            }
        }
    }

    // ["og:image": "http://image.png"]
    class func ogRequest(
        url: String,
        success: @escaping (_ data: Dictionary<String, String>) -> Void,
        fail: @escaping(_ error: Error?) -> Void) {
        
        Alamofire.request(url, method: .get).responseString { response in
            if response.result.isFailure {
                print(response.result.error!)
                return
            
            
            let html = HTMLDocument(string: response.result.value!)
            
            let ogTags:[HTMLElement] = html.nodes(matchingSelector: "meta[property^=\"og:\"]")
                
            var dict = Dictionary<String, String>()
            ogTags.forEach {
                let property = $0.attributes["property"]!
                let content = $0.attributes["content"]!
                dict[property] = content
            }
                
            success(dict)
        }
        
    }
}

一応完成。

感想

  • UITableViewのスクロールで画像がずれるとかいろいろ問題あり。
  • なんかロードが遅い。
  • 使えそうなやつをぶっこんでいったせいで、他の利用法とか調べてない。
  • リロード機能とかまだ実装が足りていない部分が多々。
  • 起動しているが、たまにrssを読み込んでくれない?バグが発生する。

やはり作ってみることで、問題→解決策という手順で学べる事が多い。 rssからのデータ整形、og:image=http...からサムネ取得、 YQLの使い方など今後も役に立つ知識なので、やってよかった。

特にCocoaPodsは、Podfileに記述するだけで利用でき、削除も不要な行を消せば できるので、理解すれば超絶楽だった。

完成品は自分のgithubに保存しておきます。
https://github.com/tikyuu/RssReader-Swift

f:id:hayateasdf:20171024104237p:plain