Anything New

developping any time

Swift 丸アイコン内、テキストセンター描画

UIViewのカスタムクラスにて、overrideしたdrawRect内で、
// 丸描画
let roundRect = UIBezierPath(roundedRect: CGRectMake(1, 1, 28, 28), cornerRadius: 10)
UIColor.blueColor().setStroke()
roundRect.lineWidth = 2
roundRect.stroke()

// フォント属性
let fontAttr = [NSFontAttributeName: UIFont.systemFontOfSize(10)]
// テキスト
let str = self.initial_txt! as NSString!
// サイズ取得
let size = str.sizeWithAttributes(fontAttr)

let x_pos = (roundRect.bounds.size.width - size.width) / 2
let y_pos = (roundRect.bounds.size.height - size.height) / 2

// テキスト描画
str.drawAtPoint(CGPointMake(roundRect.bounds.origin.x + x_pos, roundRect.bounds.origin.y + y_pos),
withAttributes: fontAttr)

結果

initial.png

参考


Swiftでテキスト内リンク&テキストタップ検出


指定したテキスト内にリンクを設定したいことがあります。
@you_matz #swiftなどをリンクとして埋め込みたい場合
そしてそのリンクをタップした際に別の画面に遷移させるなど。

実現方法

①UITextViewのプロパティの設定

textView.userInteractionEnabled = true
textView.editable = false

// tapでテキストのポジション検出可能とするため
textView.selectable = true

// 行間レイアウト用
// textView.layoutManager.delegate = self

②テキスト内リンク範囲指定

let text = "Swiftでテキスト内リンク&テキストタップ検出"
let linkText = "リンク"
let nsTex = text as NSString
let link = text.rangeOfString(linkText)
let attributedString = NSMutableAttributedString(string: text)
let start = distance(text.startIndex, link!.startIndex)
let length = distance(link!.startIndex, link!.endIndex)

// リンク位置範囲生成
range = NSMakeRange(start, length)

③textviewに指定した色、カラー、下線を指定後、代入

// テキスト全体文字色
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.whiteColor(), range: NSMakeRange(0, nsTex.length))

// リンクカラー
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blueColor(), range: range)

// リンク下線
attributedString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: range)
// 属性を代入
textView.attributedText = attributedString

④textViewにgesture追加

// gesture追加
let tap = UITapGestureRecognizer(target: self, action: "tapText:")
textView.addGestureRecognizer(tap)

⑤tap時の動作

func tapText(tap: UITapGestureRecognizer) {

// タップされた座標をもとに最寄りの文字列の位置を取得
let location = tap.locationInView(textView)
let textPosition = textView.closestPositionToPoint(location)

// テキストの先頭とタップした文字の距離をNSIntegerで取得
let selectedPosition = firstTextView.offsetFromPosition(firstTextView.beginningOfDocument, toPosition: textPosition!)

// タップした文字がリンク文字のrangeに含まれるか判定
if NSLocationInRange(selectedPosition, range) {

// リンクタップ時の処理
// let webVC = WebViewController()
// self.presentViewController(webVC, animated: true, completion: nil)
}
}

参考

NSAttributedStringを使ってリンク文字を作る(Objective-C
https://gist.github.com/yoshimin/858d14751fc1c00807d2
UITextView でタップ可能なリンクをカスタマイズする
http://kishikawakatsumi.hatenablog.com/entry/20130605/1370370925

S3でIP制限

S3でIP制限
静的なファイルの配置先としてS3を利用している場合、アクセス制限などを設けたい場合があります。どこからでもアクセスできるとセキュリティ的にもコスト的にもあれあので。
今回は特定のIPのみを許可する方法です。

実現方法

S3管理画面、特定のバケットの選択、プロパティを選択、
アクセス許可の欄より、バケットポリシーの編集をクリック
以下にてバケットポリシーを生成します。
http://awspolicygen.s3.amazonaws.com/policygen.html
説明は以下
スクリーンショット 2015-06-14 午後1.12.38.png
  • Select Type of Policyは「S3 Bucket Policy」を選択
  • Effectは「Deny」
  • Principalは「*アスタリスクですべて
  • AWS Serviceは「Amazon S3
  • すべてのアクセスに適用させたいなら「All Actions」にチェック
  • Amazon Resource はバケット以下が対象なら「arn:aws:s3:::yourBucketName/*」のような形で
  • 「Add Conditions (Optional)」をクリックでIP制限を追加
  • Condition 「NotIpAddress」を選択
  • Keyは「aws:SourceIp」を選択
  • Valueは許可したいIPアドレスを入力
  • 「Add Condition」で追加
  • 「Add Statement」で正式に追加
  • 複数IP追加したい場合は再度Add Conditions (Optional)で上記手順
最後に「Generate Policy」にて生成され、
S3のバケットポリシーエディターに貼り付け保存。

生成されたソースの例

{
"Version": "2012-10-17",
"Id": "Policy*************",
"Statement": [
{
"Sid": "Stmt**********",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::yourBucketName/*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": "192.0.2.0/24"
}
}
}
]
}

最後に確認

許可IPからのアクセスの場合

ファイルにアクセス可能

NGの場合、以下のXMLレスポンス

AccessDenied

Amazon Machine Learningについて

Amazon Machine Learningについて
とある案件で利用したのでまとめておきます。
教師ありの機械学習クラウドサービスがお手軽に利用できる、
Amazon Machine Learning。

結局何に使うの?という声が多い機械学習ですが、
データの分析、予測結果の妥当性を高めるために利用するのには適しているかと思います。

  • 大量の顧客情報から効率的にターゲットの顧客に対してキャンペーンを打ちたい
  • 大量のニュースの記事から新たな記事はどのカテゴリに属するか
  • 現状の在庫、売上から、特定の商品はどのくらいの在庫を持つべきか
という問に精度の高い予測、適切な予測を示してくれることでしょう。

アジェンダ

  • 導入にあたっての使用条件
  • どのようなことができるか?
  • 機械学習とは?
  • Amazon MLの導入方法
  • PHPでのAPIサンプル
  • まとめ
  • 参考資料

フリーランスになって三ヶ月後の振り返り


フリーランスとして活動し始めて(2015年2月〜)、三ヶ月が経ちました。
制作実績として記しておきます。
中には秘密保持契約の関係で記載していないものもあります。

筆者背景

1987年生まれ、関西在住。
私立文系大学卒業後、Windowsパッケージソフト開発会社、WEBシステム開発会社を経て、
2015年2月よりフリーランス(個人事業主)、WEB、アプリ開発者として活動
プロフィールサイトはこちら
http://manchan.github.io/index.html

5月

4月

3月

2月

振り返り

振り返ってみると、まぁまぁかなといったところ。
おもしろいことにフリーになったと発信すると、意外や意外、声をかえてくださったり、
仕事をくれたり、飲みに誘ってもらったり、ありがたい限りです。
4月にAppleの日本支社に行けたことがすごく刺激になった。忘れられない思い出。
個人でぼちぼちやってるレベルなので、自分が動けば動く程、良い結果が待っていると考えて、もっと切り詰めてやるべき。
もっと深くクライアントにヒアリングしたり、求めていることを具現化するべき。
継続的にお仕事いただけるところもあれば、単発で終わるところもある。
手広くなんでもやっているので、もう少しiOSアプリ中心にやっていきたい。
直近でサンプルレベルですが初めてAndroidアプリに着手しました。
iOSと違って申請後、公開まで2,3時間なのは、魅力的。
やらず嫌いはよくない。

困ったこと

契約不履行は普通にある。
実際、報酬未払いによる集団訴訟がらみのトラブルを経験したが、自分は少額だったので、勉強代と考える。
なんでもかんでもできる事で、仕事を受ければいいというものではない。
クライアントをよく判断する。

今後の目標

  • 興味、関心があるプロジェクトに参画し、それをお金に変える(利益を求め過ぎない)
  • オープンソースに貢献する(githubのstar100以上)
  • 役に立ってかつ、ひとひねりある、そんなサービスを企画、開発する(アイディア or サンプルはあるがリリースまでもっていけてない、、、)
  • 来年はWWDCに行く
  • 仕事がらみで海外に行く
  • ブログの更新頻度をあげる(最低週一)

おわりに

上記目標を今後一、二年で達成できるようにする。
まだまだこれからです。

lodash(Underscore.js)まとめ, Swift, Objective-C, PHPもあるよ

lodash(Underscore.js)

JavaScriptのユーティリティライブラリ。
2015年5月現在、npmでもっとも多く参照されている。
https://www.npmjs.com/browse/depended
lodashはUnderscoreの派生ライブラリ。
Backbone.jsは、もともとunderscore.jsに依存していますし、
lodashは、JavaScriptトランスパイラのBabel,ブログプラットフォームのGhost,
プロジェクトのベースとなるツールであるYeomanなど,npmの主要なパッケージのいくつかは依存している。
AngularJsでDI(依存性注入)してlodash(underscore.js)をモジュール化して、そのまま使用してもいい。
Reactでももちろん使用可能。

lodash(Underscore.js)を使用する目的

配列やオブジェクトの操作をする際に便利
コードの可読性アップ
つまりコードをシンプルかつ短縮できる
for文、if文を多用せずに済む
つまり保守性が高い(変数の状態を知らなくて済む, 関数は参照透過性が保たれている)

Underscoreとlodashの比較

Lo-Dashの方が機能数が多い
Lo-Dashの方が高速、パフォーマンスがいい
native vs. array.js vs. underscore vs lo-dash
http://jsperf.com/native-vs-array-js-vs-underscore/8

lodash(Underscore)便利メソッド

// latest 3.7.0
var _ = require('lodash');

/**
* lodash(Underscore.js) の便利な関数ライブラリたち
* 「_」というオブジェクトを使用することにより利用可能
* 配列、オブジェクト操作を便利にするもの
*
* よく使うのは以下
* each
* find
* filter
* where
* findWhere
* contains
* pluck
* max
* min
* sortBy
*/

Collection functions

each

/**
* ループ処理 collection each
* => each: 4
* => each: 10
* => each: 16
*/
_.each([2, 5, 8], function(num){
console.log( 'each: ' + num * 2 );
});

for と mapの例

/**
* jsのfor文で配列に要素を追加
* => count_arr:2,4,6,8,10
*/
var count_arr = [];
for(var i = 1; i < 6; i++){
count_arr.push(i * 2); // マッピングのたびに外部の変数の状態が変化する ☓
}
console.log('count_arr:' + count_arr);

/**
* mapで配列を操作
* 返り値は配列
* => map: 2,4,6,8,10
*/
var map = _.map(_.range(1, 6), function(num){
return num * 2; // 変数の状態を考えずに済む ○
});
console.log( 'map: ' + map );

for と reduce × filterの例

/**
* 文字列配列の内、三文字以上のものを選択し、
* 文字数の合計を集計する
* => 18
*/
var cities = ["京都", "サンクトペテルブルク", "ボゴダ", "東京", "ゴア", "バルセロナ"];
var total_str_len = 0;
for(var city in cities){
var str_len = cities[city].length;
if(str_len >= 3){
total_str_len += str_len; // マッピングのたびに外部の変数の状態が変化する ☓
}
}
console.log( 'for total_str_len:' + total_str_len); // => 18


/**
* 上記をreduce × filter でやる
* reduceの返り値は単一の値
* filterにて、条件で絞った配列に対して, reduceで一つずつの要素に対して処理をする。
* memoに結果がストックされる。第三引数0はmemoの初期値
* => 18
*/
var total_len = _.reduce(
_.filter(cities,function(city){return city.length >= 3;}),
function(memo, str){ return memo + str.length;}, 0);
console.log( 'function total_len:' + total_len); // 18

chain

/**
* chain メソッドチェーンで使用
* value()でchain結果を取得
* => 18
*/
var total = _.chain(cities)
.filter(function(city){ return city.length >= 3 })
.reduce(function(memo, str){ return memo + str.length;}, 0)
.value();
console.log( 'function total:' + total); // 18

flatMapを使用したい場合(カスタム)

/**
* flatMapを使用したい場合
*/
var flatMap = _.flatMap = _.concatMap = function (xs, f, self) {
return _.reduce(xs, function (ys, x) {
return ys.concat(f.call(self, x));
}, []);
};
_.each(['flatMap', 'concatMap'], function (name) {
_.prototype[name] = function (f, self) {
return _(flatMap(this.value(), f, self));
};
});


/**
* 以下、
* 整数リストのリスト中から奇数だけを取り出して並べたリストを作成
*/

// 普通にやるとこう filter ✕ map
var filterMap = _.filter(_.range(1, 11), function(x){
return x % 2 === 1;
}).map(function(x){
return x;
});
console.dir( 'filterMap:' + JSON.stringify(filterMap, null, 4));

// filter ✕ reduce
var filterReduce = _.filter(_.range(1, 11), function(x){
return x % 2 === 1;
}).reduce(function(flattened, other){
return flattened.concat(other);
},[]);
console.dir( 'filterReduce:' + JSON.stringify(filterReduce, null, 4));

// flatMap使用
var flatMap = _.flatMap(_.range(1, 11), function (x) {
return x % 2 === 1 ? [x] : [];
});
console.dir( 'flatMap:' + JSON.stringify(flatMap, null, 4));

// 同等
var concatMap = _.concatMap(_.range(1, 11), function (x) {
return x % 2 === 1 ? [x] : [];
});
console.dir( 'concatMap:' + JSON.stringify(concatMap, null, 4));

lodashにはtransformがあった!

/**
* transform
* lodashにはtransformがあった!
* Underscore.jsのreduceの代替
* 結果は上記と同じ
*/
var transform_arr = _.transform(_.range(1, 11), function(result, x, i) {
return x % 2 === 1 ? result.push(x): [];
});
console.dir( 'transform:' + JSON.stringify(transform_arr, null, 4));

find

var a = [1, 3, 6, 9];

/**
* find
* 集合要素に対しての操作
* 一番目の要素を探して返す
* => find: 6
*/
console.log( 'find: ' + _.find(a, function(num){
return num > 5; // 6
}));

where

/**
* where
* 条件に合致するオブジェクトを返す
*/
console.log( 'where:' + JSON.stringify(
_.where(PlayList, {artist : "Pharrell Williams"}), null, 4)
);

findWhere

/**
* findWhere
* 条件に合致する最初のオブジェクトを返す
*/
console.log( 'findWhere:' +
JSON.stringify(
_.findWhere(PlayList, {artist : "Pharrell Williams"}), null, 4
)
);

filter

/**
* filter
* 条件に一致したものを配列で返す
* => filter: 6,9
*/
z = _.filter(a, function(num){
return num > 5;
});
console.log( 'filter: ' + z ); // 6, 9

contains

/**
* contains
* 配列内に指定値があるか => true or false
* => contains: false
*/
console.log( 'contains: ' + _.contains(a, 10));

groupBy

/**
* groupBy
* 結果によるグループ化
* groupBy: [object Object]
*/
var groupResult = _.groupBy([1, 2, 5, 8, 42, 12], function(num){
return num % 3;
});
console.dir( 'groupBy:' + JSON.stringify(groupResult, null, 4));

countBy

/**
* countBy
* グループの要素カウント
* => Object {odd: 2, even: 4}
*/
var countResult = _.countBy([1, 2, 5, 8, 42, 12], function(num){
return num % 2 == 0 ? 'even' : 'odd';
});
// => Object {odd: 2, even: 4}
console.dir( 'countResult:' + JSON.stringify(countResult, null, 4));

sortBy

/**
* sortBy
* => sortBy Math sin: 5,42,12,1,2,8
*/
console.log( 'sortBy Math sin: ' + _.sortBy([1, 2, 5, 8, 42, 12], function(num){
return Math.sin(num);
}) );

/**
* sortBy
* => sortBy length: i,me,and,you,dog,father
*/
console.log( 'sortBy length: ' + _.sortBy(['i', 'me', 'and', 'you', 'father', 'dog'], 'length') );

union

var c = [1, 2, 5];
var d = [2, 4, 6];

/**
* union
* 配列の結合(ユニーク)
* => union: 1,2,5,4,6
*/
console.log( 'union: ' + _.union(c, d) );

intersection

/**
* intersection
* 配列の共通箇所を抽出
* => intersection: 2
*/
console.log( 'intersection: ' + _.intersection(c, d) );

difference

/**
* difference
* 第一引数の配列を元に、比較して違いを配列で返す
* => difference: 1,5
*/
console.log( 'difference: ' + _.difference(c, d) );

uniq

/**
* uniq
* => uniq: 2,5,10
*/
console.log( 'uniq: ' + _.uniq([2,5,10,2, 5]) );

template

/**
* template
* テンプレート機能 テンプレートを引数で置換
* <% %> js evaluate
* <%= %> interpolate
* <%- %> escape output
*/
var js_ = "<% console.log('hello from tmpl'); %>";
var html_tpl = "
  • <%- name %> - (<%- age %>)

  • "
    ;
    console.log(_.template(
    "Using 'with': <%= data.answer %>",
    {variable: 'data'})({answer: 'no'})); // => Using 'with': no

    Object function

    keys

    /**
    * オブジェクト操作
    * @type {{name: string, age: number, url: string}}
    */
    var user = {
    name: 'matsuoka',
    age : 27,
    url : 'http:manchan.github.io'
    };

    // => keys: name,age,url
    console.log( 'keys: ' + _.keys(user) );

    values

    // => values: matsuoka,27,http:manchan.github.io
    console.dir( 'values: ' + _.values(user) );

    invert

    // => Object {27: "age", matsuoka: "name", http:manchan.github.io: "url"}
    console.dir( 'invert:' + JSON.stringify(_.invert(user), null, 4) );

    extend 参照先もコピーされるため,参照元の値を上書きするおそれがある ※ディープクローンしたい場合は、cloneDeep

    /**
    * extend
    * _.extend(destination, *sources)
    * soucesオブジェクトのすべてのプロパティをdestinationオブジェクトにコピーする。
    * sourcesの中に同じ名前のプロパティが含まれていた場合、より後のもので上書きされる。
    */
    // => {name : 'matsuoka', age : 27}
    console.dir( 'extend:' + JSON.stringify(_.extend({name : 'matsuoka'}, {age : 27}), null, 4));

    var firstObj = {firstObj: 1},
    secondObj;
    secondObj = _.extend(firstObj, {secondObj: 2});
    console.log(firstObj); // {firstObj: 1, secondObj: 2}
    console.log(secondObj); // {firstObj: 1, secondObj: 2}
    secondObj.firstObj = 3;
    console.log(firstObj); // {firstObj:3, secondObj:2} !上書きされている
    console.log(secondObj); // {firstObj:3, secondObj:2}

    defaults

    /**
    * defaults
    * _.defaults(object, *defaults)
    * objectがdefaultsオブジェクトのプロパティを持っていない場合、
    * objectにそのdefaultsオブジェクトのプロパティを付与する。
    */
    var iceCream = {flavor : "chocolate"};
    // => {flavor : "chocolate", sprinkles : "lots"}
    console.dir( 'defaults:' + JSON.stringify(
    _.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"}),
    null,
    4));

    clone (shallow copy

    /**
    * clone
    * _.clone(object)
    * objectのシャローコピーを作る。
    * ネストされたオブジェクトや配列は、参照コピーされる
    */
    var clone_obj = _.clone({name : 'matsuoka'});
    // => {name : 'matsuoka'}
    console.dir( 'clone:' + JSON.stringify(clone_obj, null, 4));

    has

    // => has: true
    console.log( 'has: ' + _.has(user, "name"));
    // => false
    if( !_.has(clone_obj, 'foo') ){
    // error
    // return;
    }

    is系(isUndefined, isEmpty, isStringなど)

    /**
    * is系は多い
    * => true or false
    */
    // => isEmpty user.name: false
    console.log( 'isEmpty user.name: ' + _.isEmpty(user.name));
    // => isNull user.age: false
    console.log( 'isNull user.age: ' + _.isNull(user.age));
    // => isString user.age: false
    console.log( 'isString user.age: ' + _.isNull(user.age));
    // => isNumber user.age: true
    console.log( 'isNumber user.age: ' + _.isNumber(user.age));

    shuffle

    /**
    * 関数的シャッフル
    * => shuffle1: 3,8,2,4
    */
    console.log( 'shuffle1: ' + _.shuffle([2,3,8,4]) );
    // OOP
    console.log( 'shuffle2: ' + _([2,8,10,3]).shuffle() );

    range

    // => range 1,10: 1,2,3,4,5,6,7,8,9
    console.log( 'range 1,10: ' + _.range(1, 10) );
    // => range 0,10: 0,1,2,3,4,5,6,7,8,9
    console.log( 'range 0,10: ' + _.range(10) );
    // => range 1,11,2: 1,3,5,7,9
    console.log( 'range 1,11,2: ' + _.range(1, 11, 2) );

    random

    // => random 0, 10: 2
    console.log( 'random 0, 10: ' + _.random(10) );

    escape

    // => escape: 

    Wordpressサイト移行手順(さくらVPSの場合)

    さくらVPSの場合

    手順(ドメイン同じでサーバ移動の場合)
    1. 移行先サーバにてssh,ftp,iptables,apache,php mysqlのインストールと設定、ユーザの追加など
      ※それぞれ設定、バージョンは旧サーバ、新サーバともに合わせる。既存ワードプレスサイトに合わせたバージョンをインストールする。
    2. 移行前Wordpressのプログラムを一式ダウンロード, ftp or scp or rsync
    3. 移行前データベースのエクスポート, phpmyadminで、コンソール上でも
    4. 移行先サイトのドキュメントルートにWordpressのプログラムを一式アップロード
    5. 移行先データベースの新規作成(既存と同名で、照合順序はutf8-general-ci)、3でエクスポートしたSQLファイルをインポート
    6. 移行先URLへアクセス、問題なく表示されることを確認
    7. ネームサーバの編集、ドメインのゾーン編集、AレコードのIPを新サーバのIPに変更(プラスTTL:60(一分)と短めに変更)
    8. 既存ワードプレスを確認したい場合設定変更, 以下詳細
    9. 最終確認

    他のサーバの場合

    レンタルサーバでfutoka,Xserver,hetemlなどの場合、標準でLAMP環境が構築されているので、手順2からでよい

    ドメイン同じでサーバが変更されているかの確認

    手順7の後、一分ほどで新サーバで確認できます。nslookup example.com で確認

    問題がある場合

    移行先URLにアクセスして、TOPページは表示されるが、パーマリンクの設定を/%category%/%postname%/のようなカスタム構造にしていて、他記事のページが404 Not Foundでかつ、リンクが文字化けする場合

    Apachemod_rewriteモジュールの確認

    # grep mod_rewrite /etc/httpd/conf/httpd.conf
    LoadModule rewrite_module modules/mod_rewrite.so
    # find / -name mod_rewrite.so -print
    /usr/lib64/httpd/modules/mod_rewrite.so
    あれば問題なし

    Rewrite設定の確認

    .htaccessが以前と同じ状態であれば問題なし
    文字コードUTF-8で保存されているか。
    パーミッションは604となっているか。

    httpd.confの確認

     "/var/www/html">
    AllowOverride None

    となっていればNoneをAllに変更する
     "/var/www/html">
    AllowOverride All

    これで.htaccessの設定が有効となる

    既存のサイトも閲覧したい場合

    ドメインの向き先を変更した後だと、既存ワードプレス側の設定を変更しないと見れません。
    以下SQLを既存サイト(http://ホスト名/phpmyadmin/)で実行
    #wordpressドメインの変更
    UPDATE wp_options SET option_value =
    REPLACE (option_value, 'old_url', 'new_url')
    WHERE option_name = 'home' OR option_name = 'siteurl';

    #記事のリンクの変更
    UPDATE wp_posts SET guid = REPLACE (guid, 'old_url', 'new_url');

    #記事内のURLの変更
    UPDATE wp_posts SET post_content =
    REPLACE (post_content, 'old_url', 'new_url');

    #画像パスの変更
    UPDATE wp_posts SET post_content =
    REPLACE (post_content, 'src="old_url', 'src="new_url');

    UPDATE wp_posts SET guid =
    REPLACE (guid, 'old_url', 'new_url')
    WHERE post_type = 'attachment';

    #各投稿メタ情報を変更
    UPDATE wp_postmeta SET meta_value =
    REPLACE (meta_value, 'old_url','new_url');
    それぞれold_url, new_urlは書き換えてください。