tag:blogger.com,1999:blog-50404684533114327172023-11-16T19:35:30.927+09:00プロフェッショナルプログラマーシンガポール在住のソフトウェア開発者です。なんだかんだでシンガも4年目突入。仕事もしくは趣味でやっていることをまとめています。 英語のブログは http://atmarkplant.com にありますatmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.comBlogger146125tag:blogger.com,1999:blog-5040468453311432717.post-55962986517341604772017-05-28T22:01:00.001+09:002017-05-28T22:01:06.067+09:00React + Redux 非同期処理を導入前回に引き続き, React + Redux です。
今回は前回のサンプルにajax 非同期処理を導入します。
簡単な例として実装するため今回は, ページロードの際にjsonデータを取ってきてそれをリストで描画する
例にしました。
前回のエントリーはこちら <a href="http://atmarkplant-dj.blogspot.sg/2017/05/react-redux-react-router.html">React + Redux React Router</a>
その前 <a href="http://atmarkplant-dj.blogspot.sg/2017/05/react-redux.html">React + Redux とりあえず簡単な例から</a>
サンプルは, <a href="http://redux.js.org/docs/advanced/ExampleRedditAPI.html">Reddit</a>(Redux公式より) を参考にしています。
このサンプルとの違いはデータ取得をイベントフックにしているかロード時に取得しているかの違いです。
<h4>データの準備</h4>
データはjsonファイルとして準備します。前回のプロジェクトのルートにフォルダを作成し, データを置いておきます。
このデータをwebpack-dev-serverがなんとかしてくれます。
<pre>
first (プロジェクト名)
|- data
|- data.json
</pre>
<br>
<strong>data.json</strong>
<pre>
{
"data" : [
{
"index" : 1,
"name" : "Taro"
},
{
"index" : 2,
"name" : "Jiro"
}
]
}
</pre>
これで, localhost:8080/data/data.json でデータを取得できるようになりました。
<h4>HttpClientはどうする?</h4>
React そのものには, HttpClientは含まれていません。ですので何かしらのライブラリの導入をするか, pure Javascript で頑張るかのどちらかになります。
選択肢としてはいくつかあります。
<ul>
<li>jQuery</li>
<li>Angular</li>
<li><a href="https://visionmedia.github.io/superagent/">SuperAgent</a></li>
<li><a href="https://github.com/mzabriskie/axios">Axios</a></li>
<li><a href="https://github.com/request/request">Request</a></li>
<li><a href="https://github.com/github/fetch">Fetch</a></li>
</ul>
せっかく, Reactを使っているのに jQueryとか, Angularはないよな。と思いつつ。
SuperAgent, Axios などは割と使われているみたいです。
今回は, <a href="https://github.com/github/fetch">Fetch</a>を使ってみます。
<pre class="brush: bash;">
npm insatll whatwg-fetch --save
</pre>
<h4>React-ThunkとMiddleware</h4>
今回のサンプルを実装するに当たって重要なのが React-Thunkと Middlewareです。
<a href="https://github.com/gaearon/redux-thunk">Redux Thunk</a> (Thunk middleware for Redux.)
これを使うことでAction CreatorがAction objectを返す代わりにfunctionを返すことができます。
<pre class="brush: bash;">
npm install redux-thunk --save
</pre>
<h4>非同期データとアクション</h4>
ここで非同期でデータを取ってくるところとアクションの関係を説明します。
データを取ってくる = データを取りに行く + データを受信した or データ取得に失敗した
という形に分離できます。これでアクションは3種類? 実装する必要がありますね。
(結局このあたりの細かいところをどう設計するかが鍵になるのかと思います。最初のうちはさっぱりわからないですよね。)
<h4>サンプル</h4>
構成は前回のエントリーの続きになります。 webpackの設定などの変更はしていないです。
<pre>
first
|-data
| |- data.json
|-.babelrc
|-index.html
|-package.json
|-webpack.config.js
|-src
|-actions
| |- index.jsx
|-components
| |- FormDisplay.jsx
| |- FormInput.jsx
|-containers
| |- AppContainer.jsx
| |- FormApp.jsx
| |- Page2.jsx
| |- Page2Container.jsx
|-reducers
|- fetchReducer.jsx
|- formreducer.jsx
|- index.jsx
</pre>
<br>
<b>index.jsx</b>
<pre class="brush: javascript;">
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { reducers } from './reducers/index.jsx';
import thunkMiddleware from 'redux-thunk';
import AppContainer from './containers/AppContainer.jsx';
import Page2Container from './containers/Page2Container.jsx';
const initialState = {
formReducer: {
value : null
},
fetchReducer: {
items: []
}
};
const store = createStore(reducers,
initialState,
applyMiddleware(thunkMiddleware)); // reducer, initial state
// Rendering
ReactDOM.render(
<Provider store={store}>
<Router>
<Switch>
<Route path="/app" component={AppContainer} />
<Route path="/member" component={Page2Container} />
</Switch>
</Router>
</Provider>,
document.querySelector('.content')
);
</pre>
変更したのは, redux-thunk をmiddlewareの設定にしたことこれで ActionCreatorでfunctionが返せます。
<br>
<br>
<b>reducers/fetchReducer.jsx</b>
<pre class="brush: javascript;">
export function fetchReducer(state = {}, action) {
switch(action.type) {
case 'REQUEST_DATA_SUCCESS':
return Object.assign({}, state, {
items: action.data,
lastUpdated: action.receivedAt
});
case 'REQUEST_DATA_FAIL': {
return Object.assign({}, state, {
items: action.data
});
}
default:
return state;
}
}
</pre>
items というデータのArrayを返します。これはデータのリクエストが成功した時(その時はデータ)
、失敗した時に返します(その時は空[])
<br>
<br>
<b>reducers/index.jsx</b>
<pre class="brush: javascript;">
import { combineReducers } from 'redux';
import { formReducer } from './formreducer.jsx';
import { fetchReducer} from './fetchReducer.jsx';
export const reducers = combineReducers({
formReducer,
fetchReducer
});
</pre>
前回作成した、reducerと共に利用するので2つのReducerをcombineReducersで
繋げます。
<br>
<br>
<b>actions/index.jsx</b>
<pre class="brush: javascript;">
const SEND = 'SEND';
/* Action Creator */
// Return Object
export function send(value) {
// Action
return {
type: SEND,
value,
};
}
/* Action2 */
const REQUEST_DATA = 'REQUEST_DATA';
const REQUEST_DATA_SUCCESS = 'REQUEST_DATA_SUCCESS';
const REQUEST_DATA_FAIL = 'REQUEST_DATA_FAIL';
function invalidateData() {
return {
type: REQUEST_DATA_FAIL,
data: []
}
}
function requestData() {
return {
type: REQUEST_DATA
}
}
function receiveData(json) {
return {
type: REQUEST_DATA_SUCCESS,
data : json,
receivedAt: Date.now()
}
}
export function fetchData() {
return function (dispatch) {
dispatch(requestData());
return fetch('/data/data.json')
.then(response => response.json())
.then(json => dispatch(receiveData(json.data)))
.catch(e =>
dispatch(invalidateData()));
}
}
</pre>
前回作成したActionCreatorに3つのActionを追加しました。
データ取得できなかった場合のAction, リクエスト開始時のAction,
データが取得できた場合のActionです。これらのアクションをまとめた
fetchデータというfunctionをexportしています。
<br>
<br>
<b>containers/Page2.jsx</b>
<pre class="brush: javascript;">
import React from 'react';
import PropTypes from 'prop-types';
import {fetchData} from '../actions/index.jsx';
class Page2 extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { dispatch } = this.props;
// console.log(dispatch);
dispatch(fetchData());
}
render() {
const { items } = this.props;
var list = [];
for(var index in items){
list.push(<li key={index}>{items[index].name}</li>);
}
return (
<div>
<ul>
{list}
</ul>
</div>
);
}
}
Page2.propTypes = {
items: PropTypes.array
};
export { Page2 as default };
</pre>
componentDidMountの所でデータを非同期に取得しています。renderの部分データを取り出し
リストを作成して描画します。
<br>
最後は Containerですね。
<b>containers/Page2Container.jsx</b>
<pre class="brush: javascript;">
// Connect to Redux
function mapStateToProps(state) {
return {
items: state.fetchReducer.items
}
}
const Page2Container = connect(
mapStateToProps
)(Page2)
export { Page2Container as default };
</pre>
この部分でPropertyとComponentを繋ぎます。
<h4>テスト</h4>
動作確認
<pre>
webpack-dev-server
</pre>
localhost:8080/member にアクセス。2つのアイテムのあるリストが見えるはずです。
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-61795954150997836152017-05-28T15:20:00.002+09:002017-05-28T22:01:59.300+09:00React + Redux React Router前回に引き続き, React + Reduxを見ていきます。
今回は 前回のサンプルにReact Routerを導入します。
前回のサンプルはこちらをご覧ください。 (<a href="http://atmarkplant-dj.blogspot.sg/2017/05/react-redux.html">React + Redux とりあえず簡単な例から</a>)
<h4>React Routerを導入</h4>
<pre class="brush: bash;">
npm install --save react-router-dom
</pre>
Versionは, 4.1.1 を使用。このバージョンが曲者っぽい。
4以下の書き方などは違う模様。
また, react-router というパッケージと react-router-dom は違うみたい。
前はreact-routerを使っていた気がするんだけどなぁ。
今回は, react-router-domを使います。
<br>
<br>
<h4>Container の作成</h4>
今回はもう1ページ用にもう一つContainerを作成します。Reduxの流れを作るのが面倒なので
簡単なComponentにしておきます。
<br>
<b>containers/Page2.jsx</b>
<pre class="brush: javascript;">
class Page2 extends React.Component {
render() {
return (
<div>
Hello!
</div>
);
}
}
export { Page2 as default };
</pre>
ただのSimpleなdivです。
<br>
<h4>React-Routerを入れる</h4>
前回作った部分を/appとして今回の部分を/memberと二つにします。
(コードは一部のみ)
<pre class="brush: javascript;">
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import AppContainer from './containers/AppContainer.jsx';
import Page2 from './containers/Page2.jsx';
ReactDOM.render(
<Provider store={store}>
<Router>
<Switch>
<Route path="/app" component={AppContainer} />
<Route path="/member" component={Page2} />
</Switch>
</Router>
</Provider>,
document.querySelector('.content')
);
</pre>
前回と違うところは Prividerのしたのところに, RouterというタグとSwitchというタグ, その中に各ページの定義 Routeが入っています。
これで localhost:8080/app, localhost:8080/member で各ページが確認できます。
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-12352583559627208152017-05-28T15:04:00.002+09:002017-05-28T15:06:19.553+09:00React + Redux とりあえず簡単な例から久々の投稿。ここしばらくは, モバイル, フロントがメインでたまにバックエンドの開発をする日が続いています。
<div>
仕事では, Angular 1 がメインでしたそろそろReact + webpackでいこうと模索しています。</div>
<div>
React をしばらく触って, それからReact + Redux の学習を初めています。</div>
<div>
React は本読んだりしてなんとかしましたが, redux にはかなり苦労しています。</div>
<div>
<br /></div>
<div>
<br /></div>
<h4>参考にしたもの</h4>
<ul>
<li><a href="https://www.blogger.com/blogger.g?blogID=5040468453311432717#editor/target=post;postID=1235258355962720815">Reduxの実装とReactとの連携を超シンプルなサンプルを使って解説</a></li>
<li><a href="http://redux.js.org/docs/basics/ExampleTodoList.html">Example: TODO List Redux</a></li>
<li><a href="http://redux.js.org/docs/advanced/ExampleRedditAPI.html">Example: Reddit API</a></li>
</ul>
一番上の解説はわかりやすかったです。今回のサンプルもここで取り上げていたサンプルをベースに作っています。
下2つはReduxの公式のサンプルです。フォルダの構成とか結構参考になると思います。
<h2>最終的なゴール</h2>
<ul>
<li>(今回)webpackでビルドする</li>
<li>(今回)webpack-dev-server で動作確認</li>
<li>(今回)reduxの導入</li>
<li>(今回)es6でかけるようにする</li>
<li>(今回)action, container, component, reducerの構成を作る</li>
<li>react-routerを使う</li>
<li>ajaxでデータを取ってくる</li>
</ul>
react-routerと, ajaxに関しては次回以降取り上げたいと思います。
<br>
<h3>バージョンなど</h3>
作成日 : 2017/05/28
webpack : 2.5.1
<br>
<h5>プロジェクトの作成, react, webapack, redux の導入</h5>
<pre class="brush: bash;">
mkdir first
cd first
npm init -y
npm install --save webpack webpack-dev-server
npm install --save-dev babel babel-core babel-loader babel-preset-es2015 babel-preset-react
npm install --save react react-dom react-redux redux
npm install --save prop-types
</pre>
firstはプロジェクト名です。なんでも良いです。
今回は, webpack, webpack-dev-server, react, react-redux, reactのプロパティを使うとWarningが出るので
prop-typesを使います。
<br>
<br>
<h5>フォルダ構成</h5>
<pre>
first
|- dist
|- src
| |- actions
| |- components
| |- containers
| |- reducers
| |- index.jsx
|- .babelrc
|- index.html
|- package.json
|- wbpack.config.js
</pre>
<br>
<br>
<h4>.babelrc</h4>
ここで, es6を使うための設定をします。
<pre>
{
"presets": [
"es2015", "react"
]
}
</pre>
<br>
<br>
<h4>webpack.config.js</h4>
webpack の設定です。今回は jsx を動作させるための設定だけを記載しました。
<pre class="brush: javascript;">
const path = require('path');
module.exports = {
entry: path.join(__dirname, 'src/index.jsx'),
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
devServer: {
contentBase: '.',
port: 8080,
inline: true,
historyApiFallback: true
},
module: {
loaders: [
{
test: /\.jsx$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
};
</pre>
webpackでビルドした結果を distフォルダへ, babel-loaderを使って, jsxを処理します。
webpack-dev-server はポート8080で動作させます。
<br>
<br>
<h3>Redux</h3>
Redux に関しては他のブログなどのエントリーを参考にしてさらっとまとめます。
Redux : <a href="http://redux.js.org/docs/introduction/Motivation.html">Redux</a> 細かい話は公式などを確認した方が良さそう。
状態管理をアプリ全体で行う, 状態管理するStoreはアプリ全体で1つ。
ユーザのActionがコールされると, Recuderを使って, Storeの状態を更新する。
ここで登場人物が3つ出てきました。(Action, Reducer, Store)
Reduxではデータフローが重要で, データフローのルールに従います。
以下各要素の説明とプログラム上での登場人物の整理です。
<br>
<br>
<h3>Action</h3>
typeをキーとしてStateを更新するための定義を書く。
状態更新はdispatch関数を実行して行う
(ajaxの通信はここで行う)
ActionはAction名(Reducerで処理を判別するため)と状態の値を持った単なるオブジェクト
Storeのdispatch() メソッドの引数にAction Creatorを渡すことでActionがReducerに送られる
<br>
<br>
<h3>ActionCreator</h3>
最終的には, Actionと同じファイルに書いている
functionで Action = object を返す
Actionで書いていない更新したい状態の定義を書く。
Stateの更新自体は, Reducerがするので更新したい部分を引数などから生成する
<br>
<br>
<h3>Reducer</h3>
Reducerは、現在の状態(state)と受け取ったActionを引数に取り、新しい状態を返す関数
typeをキーにして処理を分ける, Switch 文中に Action で定義した State の type で分岐しロジックを書く
stateの更新には, ES2015 Object.assign()を使おう!!
Object.assign() はmergeオブジェクトのマージを行います。
※Reducerは複数作ることができます。(ページごとに作るイメージかなぁ)
(storeは一つしか引数に取れないので combineReducersで combine)
<br>
<br>
<h3>Store</h3>
状態管理のオブジェクト
初期データと, ReducerからcreateStoreメソッドで作成します。これをProvider渡します。
Reduxのmiddlewareを渡す場合はそれも引数に加えます。
<br>
<br>
<h3>Container Components</h3>
親コンポーネント。ここでReactとReduxのつなぎ込みを行います。
Container ComponentsはPresentational Componentを持ちます。
<br>
<br>
<h3>Presentational Component</h3>
子コンポーネント。最小単位のコンポーネント
親からプロパティを受け取り描画します。イベントなども親から受け取ります。
<br>
<br>
<h4>サンプル</h4>
今回のサンプルは参考の一番上のエントリーを参考にreact + reduxをwebpackで動作させる, フォルダ構成をReactの公式サンプルを
元に作り変えたものです。
<b>index.jsx</b>
エントリー部分。
<pre class="brush: javascript;">
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { reducers } from './reducers/index.jsx';
import AppContainer from './containers/AppContainer.jsx';
const initialState = {
formReducer: {
value : null
}
};
const store = createStore(reducers,
initialState); // reducer, initial state
// Rendering
ReactDOM.render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.querySelector('.content')
);
</pre>
<br>
<br>
とりあえず必要なComponentなどを全部importしています。 ここに直接書くのは, 初期状態の設定と(この部分もファイル分離できる)
store の作成と, レンダリングの部分ですね。
Reactに連携させるReduxのStoreを渡しています。Providerという部分でReactとReduxのStoreの連携をしていますね。
<br>
<br>
<b>index.html</b>
元になるView,ただし動作関係は全部上のindex.jsxに任せる
<pre class="brush: html;">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Redux Sample</title>
</head>
<body>
<div id="root" class="content"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
</pre>
ビルドした後のjsを取り込んでいるだけです。この部分はもう基本的には変更しません。
<br>
<br>
<b>actions/index.jsx</b>
アクションの実装です。Actionと,ActionCreatorの実装をしています。
<pre class="brush: javascript;">
const SEND = 'SEND';
/* Action Creator */
// Return Object
export function send(value) {
// Action
return {
type: SEND,
value,
};
}
</pre>
Actionは, objectを返します。
ここでは, Actionタイプ(アクションの名前みたいなもの)とアクションを発行した部分から
valueという値を受け取ってそれを返しています。
<br>
<br>
<b>recucers/formreducer.jsx</b>
Reducerの実装です。
<pre class="brush: javascript;">
export function formReducer(state = {}, action) {
switch(action.type) {
case 'SEND':
return Object.assign({}, state, {
value: action.value,
});
default:
return state;
}
}
</pre>
<br>
<br>
<b>containers/FormApp.jsx</b>
<pre class="brush: javascript;">
import React from 'react';
import PropTypes from 'prop-types';
import FormInput from '../components/FormInput.jsx';
import FormDisplay from '../components/FormDisplay.jsx';
class FormApp extends React.Component {
render() {
return (
<div>
<FormInput handleClick={this.props.onClick} />
<FormDisplay data={this.props.value} />
</div>
);
}
}
FormApp.propTypes = {
onClick: PropTypes.func.isRequired,
value: PropTypes.string
};
export { FormApp as default };
</pre>
<br>
<br>
<b>containers/AppContainer.jsx</b>
<pre class="brush: javascript;">
import { connect } from 'react-redux';
import { send } from '../actions/index.jsx';
import FormApp from './FormApp.jsx';
// Connect to Redux
function mapStateToProps(state) {
return {
//value: state.value
value: state.formReducer.value //http://qiita.com/usagi-f/items/ae568fb64c2eac882d05
}
}
function mapDispatchToProps(dispatch) {
return {
onClick(value) {
dispatch(send(value));
}
};
}
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(FormApp)
export { AppContainer as default };
</pre>
ここで Redux のプロパティとComponentを繋ぐ作業をしています。
storeからデータをひっぱてくるところ, メソッドを渡すとこですね。
<br>
<br>
<b>components/FormDisplay.jsx</b>
<pre class="brush: javascript;">
class FormDisplay extends React.Component {
render() {
return (
<div>{this.props.data}</div>
);
}
};
FormDisplay.propTypes = {
data: PropTypes.string
};
export { FormDisplay as default };
</pre>
<br>
<br>
<b>components/FormInput.jsx</b>
<pre class="brush: javascript;">
class FormInput extends React.Component {
send(e) {
e.preventDefault();
this.props.handleClick(this.myInput.value.trim());
this.myInput.value = '';
return;
}
render() {
return(
<form>
<input type="text" ref={(ref) => (this.myInput = ref)} defaultValue="" />
<button onClick={(event) => this.send(event)}>Send</button>
</form>
);
}
};
FormInput.propTypes = {
handleClick: PropTypes.func.isRequired
};
export { FormInput as default };
</pre>
<br>
<br>
<h4>動作確認</h4>
webpackでビルド, webpack-dev-serverで簡易サーバーを動かします。
ビルド
<pre class="brush: bash;">
webpack
</pre>
サーバーを動かす
<pre class="brush: bash;">
webpack-dev-server
</pre>
localhost:8080/index.html へアクセス
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-75460532557714506122015-01-17T10:30:00.001+09:002015-01-17T10:30:56.047+09:00Swift UICollectionViewをコードでつくる<h4>UICollectionViewをコードでつくります</h4>
<strong>UICollectionViewDelegate</strong>, <strong>UICollectionViewDataSource</strong>
<strong>UICollectionViewDelegateFlowLayout</strong> をUIViewController 内で実装します
UIは, xibや, storyboardを使わずすべてコードで実装します
セルやフッターなどのUIコードは省略します.
<pre class="brush: cpp;">
class ViewController : UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let barSize : CGFloat = 44.0
private let kCellReuse : String = "PackCell"
private let kCellheaderReuse : String = "PackHeader"
private var collectionView : UICollectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout()) // Initialization
override func viewDidLoad() {
super.viewDidLoad()
// Collection
self.collectionView.delegate = self // delegate : UICollectionViewDelegate
self.collectionView.dataSource = self // datasource : UICollectionViewDataSource
self.collectionView.backgroundColor = UIColor.clearColor()
// Register parts(header and cell
self.collectionView.registerClass(PackViewCell.self, forCellWithReuseIdentifier: kCellReuse) // UICollectionViewCell
self.collectionView.registerClass(PackCollectionSectionView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: kCellheaderReuse) // UICollectionReusableView
self.view.addSubview(self.collectionView)
}
override func viewWillLayoutSubviews() {
let frame = self.view.frame
self.collectionView.frame = CGRectMake(frame.origin.x, frame.origin.y + barSize, frame.size.width, frame.size.height - barSize)
}
// MARK: UICollectionViewDelegate, UICollectionViewDataSource
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell : PackViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(kCellReuse, forIndexPath: indexPath) as PackViewCell
return cell // Create UICollectionViewCell
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1 // Number of section
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
var res = 0
switch(section) {
case 0:
res = 4 // Number of cell per section(section 0)
break
default:
res = 0
break
}
return res
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
collectionView.deselectItemAtIndexPath(indexPath, animated: false)
// Select operation
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
var reusableView : UICollectionReusableView? = nil
// Create header
if (kind == UICollectionElementKindSectionHeader) {
// Create Header
var headerView : PackCollectionSectionView = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: kCellheaderReuse, forIndexPath: indexPath) as PackCollectionSectionView
reusableView = headerView
}
return reusableView!
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 90, height: 90) // The size of one cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSizeMake(self.view.frame.width, 90) // Header size
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let frame : CGRect = self.view.frame
let margin = (frame.width - 90 * 3) / 6.0
return UIEdgeInsetsMake(10, margin, 10, margin) // margin between cells
}
}
</pre>
<strong>UICollectionViewDelegateFlowLayout</strong> の実装でセルのサイズや, ヘッダーのサイズ, セル間のマージンなどを決めています
UICollectionViewDelegate, 実装していれば読み込まれるはずです
コードそのものは, UITableViewの実装によく似ています. 行やセクションのナンバーから, セルを引っ張りだすことなど
違いは , レイアウトが柔軟なこと, サイズを返すところで行ごとにサイズを変更できます
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-5140269592771041942014-12-20T10:56:00.000+09:002014-12-20T10:56:03.525+09:00WKWebView アラート(alert)を表示させる省メモリや高機能が売りの, WKWebViewですが, いろいろ問題があるようです<br>
alertの表示もその一つです<br>
Webページ上で, JavaScriptでalert(); などのメソッドでダイアログを表示させようとすると, WKWebViewでは無反応になります<br>
表示させる場合,タイプに応じて自前でコントロールを実装してあげる必要があります<br>
カスタマイズができるので, 「いい」とも思われますが, 面倒ですよね<br>
オリジナルはこちら(English), <a href="http://atmarkplant.com/wkwebview-prompt/">WkWebView Prompt</a>
英語版の方には, 検索で見つけたテスト用のページのリンクを貼っておきました
<h4>WKUIDelegateを実装する</h4>
個人的な推奨ですが, これらのメソッドをUIViewControllerに実装して, WKWebViewを利用するUIViewControllerに<br>
継承させれば, 楽かなと思っています<br>
<br>
UIViewController.h<br>
<pre class="brush: cpp;">
@interface ViewController : UIViewController<WKUIDelegate>
@end
</pre>
<br>
</br>
UIViewController.m<br>
<pre class="brush: cpp;">
@implementation ViewController
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)())completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:webView.URL.host message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
completionHandler();
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
// TODO We have to think message to confirm "YES"
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:webView.URL.host message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
completionHandler(YES);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
completionHandler(NO);
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:webView.URL.host preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.text = defaultText;
}];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
completionHandler(input);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
completionHandler(nil);
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
@end
</pre>
この3つのメソッドの実装で, アラート, 選択式アラート, 入力ボックス付きアラートはカバーできます<br>
ローカライズ言語部分は省略しています.<br>
適宜置き換えてください<br>
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-76724286268700566922014-12-20T10:12:00.003+09:002014-12-20T10:12:36.926+09:00WKWebView スクリーンショットUIWebViewで簡単に取得できたスクリーンショットも, WKWebViewでは同じ方法ではできません.<br />
<br />
オリジナルはこちら (English),<a href="http://atmarkplant.com/wkwebview-screenshots/">WKWebView Screenshots</a><br />
<br />
<h4>
UIViewのみを取得したい場合</h4>
<b>snapshotViewAfterScreenUpdates</b> を利用します。これだけで, UIViewまではO.K.です
Objective-C
<br />
<pre class="brush: cpp;">UIView *capturedView = [self snapshotViewAfterScreenUpdates:NO];
</pre>
swift
<br />
<pre class="brush: cpp;">var capturedView : UIView? = self.snapshotViewAfterScreenUpdates(false)
</pre>
このUIViewは, 通常の方法でUIImageに変換できません。
<b>drawViewHierarchyInRect</b>を使うと, 常に NO, false が返ってきます
UIViewを, Contextに描画できないようです
シミュレータを使うと, drawViewHierarchyInRectでも描画できますが, 実機だと上記のようなことが起こります(iPhone5s, iPhone4s)
<br />
<h4>
UIImageにしたい場合</h4>
上の方法だと実機で, UIView, UIImageのコンバートに失敗します
ScrollViewの内容をそのまま描画してImageにしてしまえば, 実機でもうまくいきます
<br />
<pre class="brush: cpp;">- (UIImage *)screenCapture {
CGSize size = self.scrollView.contentSize;
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[self drawViewHierarchyInRect:CGRectMake(self.bounds.origin.x, self.bounds.origin.y, size.width, size.height) afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)screenCapture:(CGSize)size {
UIGraphicsBeginImageContext(size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat scale = size.width / self.layer.bounds.size.width;
CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
CGContextConcatCTM(ctx, transform);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
</pre>
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-66564833415770929902014-11-22T16:41:00.004+09:002014-11-22T16:41:46.210+09:00iOS WebView(UIWebView, WKWebView)をバージョンで分けて利用する<h4>WebView</h4>
iOSでWebページを表示させる場合, UIWebViewを利用します. しかし, iOS8から, WKWebViewが登場しました.<br>
WKWebViewは, Safariに搭載されている機能と同様の機能を含んでおり, Safariに似た,機能を利用できます.<br>
またパフォーマンス面でも, WKWebViewは, UIWebViewを上回っています.<br>
ですが, この機能は, WebKit というFrameworkを利用しており, これは, iOS8より利用できるので, iOS7以下ではりようできません
そこで, OSのバージョンによりUIWebView, WKWebViewを分ければ, iOS8によりよい機能を提供できるのでは.<br>
<br>
<h4>サンプル</h4>
UIWebView, WKWebViewをハンドルクラスでラップして, iOS7,8で使い分けられるようにする<br>
UIWebViewの機能をベースにする<br>
Delegateは, 外で実装, delegateのセットは内部で行う(WKWebViewは, Navigationのみ)<br>
storyboardを使わずコードのみで実装<br>
<br>
<br>
サンプルの構成<br>
<br>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI9HI1wVCbKa9isQMkFW9A3ZyykfCUaS_47ZJDSe3WIUcmQNyYYHXxnu-fv0i6pW6VyYJAgmH3vB3A8J2Yc8GoO7Ov6Bk7P0Qh-uWssu6RA0-9KpkiiX4KWgLQHoixtqrG_cfOJRPurag/s1600/Screen+Shot+2014-11-22+at+2.46.30+PM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI9HI1wVCbKa9isQMkFW9A3ZyykfCUaS_47ZJDSe3WIUcmQNyYYHXxnu-fv0i6pW6VyYJAgmH3vB3A8J2Yc8GoO7Ov6Bk7P0Qh-uWssu6RA0-9KpkiiX4KWgLQHoixtqrG_cfOJRPurag/s320/Screen+Shot+2014-11-22+at+2.46.30+PM.png" /></a>
<br>
<br>
WebHandler : iOS7, iOS8で, UIWebView, WKWebViewを分ける, 機能もそれぞれ分ける<br>
そのほかのView<br>
カテゴリ : UIWebView+Info.m : UIWebViewで, タイトルや, URLを取得するための拡張機能
コードは, 一部省略しています(AppDelegate.mなど)<br>
<h4>UIWebView+Info.h</h4>
<pre class="brush: cpp;">
#import <UIKit/UIKit.h>
@interface UIWebView (Info)
- (NSString *)title;
- (NSString *)URLString;
- (NSURL *)URL;
@end
</pre>
<br>
<h4>UIWebView+Info.m</h4>
<pre class="brush: cpp;">
#import "UIWebView+Info.h"
@implementation UIWebView (Info)
- (NSString *)title {
return [self stringByEvaluatingJavaScriptFromString:@"document.title"];
}
- (NSString *)URLString {
return [self stringByEvaluatingJavaScriptFromString:@"document.URL"];
}
- (NSURL *)URL {
return [NSURL URLWithString:self.URLString];
}
@end
</pre>
<br>
<br>
<h4>WebHandler.h</h4>
<pre class="brush: cpp;">
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef void (^CompletionBlock)(id, NSError*);
@interface WebHandler : NSObject
- (id)webView;
- (void)layout:(CGRect)frame;
- (void)setDelegate:(id)delegate;
- (void)clean;
// Load
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadURL:(NSString *)url;
- (void)stopLoading;
// Web history
- (void)goBack;
- (void)goForward;
- (BOOL)canGoBack;
- (BOOL)canGoForward;
- (BOOL)isLoading;
// JavaScript
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)javascript completion:(CompletionBlock)completion;
// Info
- (NSString *)title;
- (NSURL *)URL;
@end
</pre>
<br>
<br>
<h4>WebHandler.m</h4>
<pre class="brush: cpp;">
#import "WebHandler.h"
#import <WebKit/WebKit.h>
#import "UIWebView+Info.h"
@interface WebHandler()
@property (nonatomic) UIWebView *uiwebView;
@property (nonatomic) WKWebView *wkwebView;
@property (nonatomic) BOOL ios8;
@end
@implementation WebHandler
- (id)init {
if (self = [super init]) {
self.ios8 = [WKWebView class] != nil;
if (self.ios8) {
self.wkwebView = [[WKWebView alloc] init];
}
else {
self.uiwebView = [[UIWebView alloc] init];
}
}
return self;
}
- (id)webView {
if (self.ios8) {
return self.wkwebView;
}
return self.uiwebView;
}
- (void)layout:(CGRect)frame {
if (self.ios8) {
self.wkwebView.frame = frame;
}
else {
self.uiwebView.frame = frame;
}
}
- (void)setDelegate:(id)delegate { // This is for Navigation Delegate
if (self.ios8) {
self.wkwebView.navigationDelegate = delegate;
}
else {
self.uiwebView.delegate = delegate;
}
}
- (void)setUIDelegate:(id)delegate { // This is for only ios8
if (self.ios8) {
self.wkwebView.UIDelegate = delegate;
}
}
- (void)clean {
if (self.ios8) {
self.wkwebView.navigationDelegate = nil;
}
else {
self.uiwebView.delegate = nil;
}
if ([self isLoading]) {
[self stopLoading];
}
}
#pragma mark - Common Features
/*
* Load
*/
- (void)loadRequest:(NSURLRequest *)request {
if (self.ios8) {
[self.wkwebView loadRequest:request];
}
else {
[self.uiwebView loadRequest:request];
}
}
- (void)loadURL:(NSString *)url {
[self loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}
- (void)stopLoading {
if (self.ios8) {
[self.wkwebView stopLoading];
}
else {
[self.uiwebView stopLoading];
}
}
/*
* History
*/
- (void)goBack {
if (self.ios8) {
[self.wkwebView goBack];
}
else {
[self.uiwebView goBack];
}
}
- (void)goForward {
if (self.ios8) {
[self.wkwebView goForward];
}
else {
[self.uiwebView goForward];
}
}
- (BOOL)canGoBack {
if (self.ios8) {
return [self.wkwebView canGoBack];
}
else {
return [self.uiwebView canGoBack];
}
}
- (BOOL)canGoForward {
if (self.ios8) {
return [self.wkwebView canGoForward];
}
else {
return [self.uiwebView canGoForward];
}
}
- (BOOL)isLoading {
if (self.ios8) {
return [self.wkwebView isLoading];
}
else {
return [self.uiwebView isLoading];
}
}
// goToBackForwardListItem is only for iOS8 WKWebView
#pragma mark - JavaScript
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)javascript completion:(CompletionBlock)completion {
if (self.ios8) {
[self.wkwebView evaluateJavaScript:javascript completionHandler:completion];
}
else {
[self.uiwebView stringByEvaluatingJavaScriptFromString:javascript];
}
}
#pragma mark - Info
/*
* Info
*/
- (NSString *)title {
if (self.ios8) {
return [self.wkwebView title];
}
else {
return [self.webView title];
}
}
- (NSURL *)URL {
if (self.ios8) {
return [self.wkwebView URL];
}
else {
return [self.webView URL];
}
}
@end
</pre>
<br>
<br>
<h4>UIHeaderView.h</h4>
<pre class="brush: cpp;">
#import <UIKit/UIKit.h>
@interface UIHeaderView : UIView
@property (nonatomic) UILabel *titleLabel;
@end
</pre>
<br>
<br>
<h4>UIHeaderView.m</h4>
<pre class="brush: cpp;">
#import "UIHeaderView.h"
@implementation UIHeaderView
- (id)init {
if(self = [super init]) {
[self setBackgroundColor:[UIColor grayColor]];
self.titleLabel = [[UILabel alloc] init];
[self.titleLabel setTextColor:[UIColor whiteColor]];
[self.titleLabel setTextAlignment:NSTextAlignmentCenter];
[self addSubview:self.titleLabel];
}
return self;
}
- (void)layoutSubviews {
self.titleLabel.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
@end
</pre>
<br>
<br>
<h4>UIFooterView.h</h4>
<pre class="brush: cpp;">
import <UIKit/UIKit.h>
@interface UIFooterView : UIView
@property (nonatomic) UIButton *backButton;
@property (nonatomic) UIButton *forwardButton;
@end
</pre>
<br>
<br>
<h4>UIFooterView.m</h4>
<pre class="brush: cpp;">
#import "UIFooterView.h"
#define kBUTTONSIZE 80
@implementation UIFooterView
- (id)init {
if(self = [super init]) {
[self setBackgroundColor:[UIColor grayColor]];
self.backButton = [[UIButton alloc] init];
[self.backButton setTitle:@"Back" forState:UIControlStateNormal];
[self.backButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
self.forwardButton = [[UIButton alloc] init];
[self.forwardButton setTitle:@"Forward" forState:UIControlStateNormal];
[self.forwardButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[self addSubview:self.backButton];
[self addSubview:self.forwardButton];
}
return self;
}
- (void)layoutSubviews {
CGRect viewRect = self.frame;
self.backButton.frame = CGRectMake(0, 0, kBUTTONSIZE, viewRect.size.height);
self.forwardButton.frame = CGRectMake(0 + kBUTTONSIZE + 10, 0, kBUTTONSIZE, viewRect.size.height);
}
@end
</pre>
<br>
<br>
<h4>ViewController.h</h4>
<pre class="brush: cpp;">
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
@interface ViewController : UIViewController<UIWebViewDelegate, WKNavigationDelegate>
@end
</pre>
<br>
<h4>ViewController.m</h4>
<pre class="brush: cpp;">
#import "ViewController.h"
#import "WebHandler.h"
#import "UIHeaderView.h"
#import "UIFooterView.h"
#define kHEADERHEIGHT 44
#define kFOOTERHEIGHT 44
@interface ViewController ()
@property (nonatomic) WebHandler *webhandler;
@property (nonatomic) UIHeaderView *header;
@property (nonatomic) UIFooterView *footer;
@property (nonatomic) int webCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Web
self.webhandler = [[WebHandler alloc] init];
[self.webhandler setDelegate:self];
self.webCount = 0;
// Header
self.header = [[UIHeaderView alloc] init];
// Footer
self.footer = [[UIFooterView alloc] init];
[self.footer.backButton addTarget:self action:@selector(backClick:) forControlEvents:UIControlEventTouchUpInside];
[self.footer.forwardButton addTarget:self action:@selector(forwardClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.header];
[self.view addSubview:self.webhandler.webView];
[self.view addSubview:self.footer];
[self.webhandler loadURL:@"http://google.com.sg"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (self.isViewLoaded && [self.view window] == nil) {
[self dispose];
self.view = nil;
}
}
- (void)dealloc {
[self dispose];
}
- (void)dispose {
if (self.webhandler != nil) {
[self.webhandler clean];
}
}
- (void) viewWillLayoutSubviews {
// Calculate layout
CGRect viewRect = self.view.frame;
self.header.frame = CGRectMake(viewRect.origin.x,
viewRect.origin.y,
viewRect.size.width,
kHEADERHEIGHT);
[self.webhandler layout:CGRectMake(viewRect.origin.x,
viewRect.origin.y + kHEADERHEIGHT,
viewRect.size.width,
viewRect.size.height - kHEADERHEIGHT - kFOOTERHEIGHT)];
self.footer.frame = CGRectMake(viewRect.origin.x,
viewRect.origin.y + viewRect.size.height - kFOOTERHEIGHT,
viewRect.size.width,
kFOOTERHEIGHT);
}
#pragma mark - UIWebViewDelegate(iOS7)
- (void)webViewDidStartLoad:(UIWebView *)webView {
// Start Loading
NSLog(@"Start iOS7");
self.webCount++;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.webCount--;
if (self.webCount == 0) {
NSLog(@"Finish iOS7");
[self.header.titleLabel setText:[self.webhandler title]];
}
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(@"Erorr iOS7");
}
#pragma mark - WKWebViewDelegate(iOS8)
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"Start iOS8");
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[self.header.titleLabel setText:[self.webhandler title]];
NSLog(@"title %@", [self.webhandler title]);
NSLog(@"Finish iOS8");
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"Error iOS8");
}
#pragma mark - Button Click
- (void)backClick:(id)sender {
if ([self.webhandler canGoBack]) {
[self.webhandler goBack];
}
}
- (void)forwardClick:(id)sender {
if ([self.webhandler canGoForward]) {
[self.webhandler goForward];
}
}
@end
</pre>
<br>
<br>
<h4>実行結果</h4>
iOS7, iOS8 ともに, 同じ結果が得られます<br>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnEiP5xoO1xSK4L5YyesTG1Q0kHLZGW2phyphenhyphenMBuflS0gCfTq6Qyh3RsWnR1_Xcf6OKlvaEb0quq1NzTo5LJT0SwBEh6s8famdyKm1YfaCUkwItitCDk-9z8HOzTKMjz6wDPHjystHO5Vo0/s1600/iOS+Simulator+Screen+Shot+Nov+22,+2014,+3.25.54+PM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnEiP5xoO1xSK4L5YyesTG1Q0kHLZGW2phyphenhyphenMBuflS0gCfTq6Qyh3RsWnR1_Xcf6OKlvaEb0quq1NzTo5LJT0SwBEh6s8famdyKm1YfaCUkwItitCDk-9z8HOzTKMjz6wDPHjystHO5Vo0/s320/iOS+Simulator+Screen+Shot+Nov+22,+2014,+3.25.54+PM.png" /></a>
<br>
<br>
<h4>リファレンス</h4>
<a href="http://nshipster.com/wkwebkit/">NS(WKWebView)</a><br>
<a href="http://atmarkplant.com/ios-wkwebview/">Professional Programmer</a>atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com2tag:blogger.com,1999:blog-5040468453311432717.post-37013220669030938282014-10-18T11:24:00.000+09:002014-10-18T11:24:03.461+09:00iOS8 ステータスバー対策どうやらiOS8 SDKから, landscape時にステータスバーなくなりました. WWDCでのデモどおり, そして, beta, GMとそのままでXCode6.0.1と最終的にステータスバーはやっぱりなくなるっぽいです.<br />
SDKを8に変更してコンパイルすると, iphoneのlandscape時にステータスバーが自動で消えます, ipadはそのまま残ります XCode5のままだとステータスバーは残ります<br />
landscape時に, より画面のスペースを確保するのが狙いらしいですが, アプリの作りによっては大問題です. <br />
<br />
<br />
<h3>
アプリの作り方における問題</h3>
UIを作る方法は,
<br />
<ul>
<li>storyboard</li>
<li>xib</li>
<li>コード</li>
</ul>
この場合, 問題になるのは, コードですべて開発している場合です.
storyboardや, xibの場合はよしなにやってくれるはずです.
<br />
<h3>
対策</h3>
ステータスバーの高さを動的に計算します.
UIViewControllerの<b>viewWillLayoutSubviews</b>, UIViewの<b>layoutSubviews</b>メソッド内で,
レイアウト(frame)の計算をおこないます. これらのメソッドは回転時にも呼ばれるので, そこで修正が可能です.
ステータスバーの計算は以下で行います
<br />
<pre class="brush: cpp;">
CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
CGFloat statusBarHeight = statusBarSize.height
</pre>
<h4>
サンプル</h4>
headerとその下にborderを入れたものです. もちろんstoryboardは使用していません.
<br />
<pre class="brush: cpp;">
#import "ViewController.h"
#define kHEADERHEIGHT 44
@interface ViewController ()
@property (nonatomic) UIView *headerView;
@property (nonatomic) UIView *borderView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor whiteColor]];
// Add Header
self.headerView = [[UIView alloc] init];
[self.headerView setBackgroundColor:[UIColor cyanColor]];
self.borderView = [[UIView alloc] init];
[self.borderView setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:self.headerView];
[self.view addSubview:self.borderView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillLayoutSubviews {
CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
self.headerView.frame = CGRectMake(0, statusBarHeight, self.view.frame.size.width, kHEADERHEIGHT);
self.borderView.frame = CGRectMake(0, statusBarHeight + kHEADERHEIGHT, self.view.frame.size.width, 0.5);
}
@end
</pre>
<br />
<h4>
Swift</h4>
<pre class="brush: cpp;">import UIKit
let kHEADERHEIGHT : CGFloat = 44.0
class ViewController: UIViewController {
var headerView : UIView
var borderView : UIView
override init() {
self.headerView = UIView()
self.borderView = UIView()
super.init()
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
self.headerView = UIView()
self.borderView = UIView()
super.init(nibName: nibName, bundle: nibBundle)
}
required init(coder aDecoder: NSCoder) {
self.headerView = UIView()
self.borderView = UIView()
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
self.headerView.backgroundColor = UIColor.cyanColor()
self.borderView.backgroundColor = UIColor.grayColor()
self.view.addSubview(self.headerView)
self.view.addSubview(self.borderView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillLayoutSubviews() {
let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.height
self.headerView.frame = CGRectMake(0, statusBarHeight, self.view.frame.size.width, kHEADERHEIGHT)
self.borderView.frame = CGRectMake(0, statusBarHeight + kHEADERHEIGHT, self.view.frame.size.width, 0.5)
}
}
</pre>
<h4>
結果</h4>
iOS Simulator iOS8.0 iPhone4sでの結果です<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0qCXl9DCI_qXJ1NVIXbwuxvZw4F0LW2ctKYGP5Uwrn2Yn-NVHWmawyLXFgie1Seduozcjk-1vkK5yPrFzMUug3kUfG-pYGkggWUFpelM1ljxhATrZd5ODqWnq_-SLYiP3b9R_vuxgY7g/s1600/iOS+Simulator+Screen+Shot+Oct+18,+2014,+9.37.02+AM.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0qCXl9DCI_qXJ1NVIXbwuxvZw4F0LW2ctKYGP5Uwrn2Yn-NVHWmawyLXFgie1Seduozcjk-1vkK5yPrFzMUug3kUfG-pYGkggWUFpelM1ljxhATrZd5ODqWnq_-SLYiP3b9R_vuxgY7g/s320/iOS+Simulator+Screen+Shot+Oct+18,+2014,+9.37.02+AM.png" /></a>
<br />
portrait時です<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-ataCm3Z5yCNlTG215rGm2XFFm6sOHWgk9OXRB_cgpxs5dpiy1R3CBJiOynAXboj3__YXQBAyFtpbLj9LS8Zpp2ah9mKDMPaYWpndToUJb0Mg_M4ad7e8RHpX5J8LziUv58G6vOSNpho/s1600/iOS+Simulator+Screen+Shot+Oct+18,+2014,+9.37.10+AM.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-ataCm3Z5yCNlTG215rGm2XFFm6sOHWgk9OXRB_cgpxs5dpiy1R3CBJiOynAXboj3__YXQBAyFtpbLj9LS8Zpp2ah9mKDMPaYWpndToUJb0Mg_M4ad7e8RHpX5J8LziUv58G6vOSNpho/s320/iOS+Simulator+Screen+Shot+Oct+18,+2014,+9.37.10+AM.png" /></a><br />
landscape時です<br />
ステータスバーがなくなりました.<br />
<br />
これをiOS7.1のsimulatorでやると, ステータスバーは残ったままになります
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-6342169988747797412014-08-09T19:37:00.003+09:002014-08-09T19:40:06.608+09:00UIIVewに穴をあけるひさびさの投稿になりました
最近は, iOSとサーバーサイドの仕事にほぼ集中するような状態です
今回はUIViewに穴をつくり, 下のViewを表示するというViewをつくります
Original English version is <a href="http://atmarkplant.com/create-hole-in-uiview/">here</a>.
<h4>早速ですが例です</h4>
結果から先に表示すると,<br>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzpEaqGwomqOue5-UInU_CXCNektWpLKyQT8Er1if6VNSnr5KNpakDyEoNjYlF838H8jcsPHUkTKifooaI8iklxh5qaVHM7mfYkB60uXL9dQa9_3_yBpQlojtLPsvT_Ug-lC5NN1CWyAg/s1600/iOS+Simulator+Screen+Shot+Jul+29,+2014,+11.25.08+PM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzpEaqGwomqOue5-UInU_CXCNektWpLKyQT8Er1if6VNSnr5KNpakDyEoNjYlF838H8jcsPHUkTKifooaI8iklxh5qaVHM7mfYkB60uXL9dQa9_3_yBpQlojtLPsvT_Ug-lC5NN1CWyAg/s320/iOS+Simulator+Screen+Shot+Jul+29,+2014,+11.25.08+PM.png" /></a>
このような形になります.
"Touch me!"というのは, UIButtonで, UIViewControllerの上に乗っています
その上に, 青い薄い色のUIViewを重ねています.
通常ですと, この薄い色がボタンに重なるはずですが, ホールを作っているので下がくっきり見えています.
<h4>このUIの実現方法</h4>
上に重ねるViewに対して, UIRectFillを使って描画します.
色は, 透明を使います.
<h5>HoleView.h</h5>
<pre class="brush: cpp;">
#import <UIKit/UIKit.h>
@interface HoleInViewController : UIViewController
@end
</pre>
<h5>HoleView.m</h5>
<pre class="brush: cpp;">
#import "HoleView.h"
@implementation HoleView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGRect hole = CGRectMake(100, 100, 100, 40);
[[UIColor clearColor] setFill];
UIRectFill(hole);
}
</pre>
<h4>このViewの使い方</h4>
ヘッダーは省略します
<pre class="brush: cpp;">
#import "HoleInViewController.h"
#import "HoleView.h"
@interface HoleInViewController ()
@property (nonatomic)UIButton *button;
@property (nonatomic)HoleView *holeView;
@end
@implementation HoleInViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.button = [UIButton buttonWithType:UIButtonTypeCustom];
[self.button setTitle:@"Touch me!" forState:UIControlStateNormal];
[self.button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
self.view.backgroundColor = [UIColor whiteColor];
self.holeView = [[HoleView alloc] init];
self.holeView.backgroundColor = [UIColor colorWithRed:0.0 green:0.1 blue:1.0 alpha:0.3];
[self.view addSubview:self.button];
[self.view addSubview:self.holeView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)viewWillLayoutSubviews {
self.button.frame = CGRectMake(100, 100, 100, 40);
self.holeView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
@end
</pre>
これで先ほど見せた例は完成です. さて次回は円のホールの作り方です
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-49974360440540592032014-05-17T11:31:00.002+09:002014-05-17T11:31:43.378+09:00Magical Record DAO前回に続いて, Magical Recordに関する話題です.<br />
前回は, セットアップの方法と, 利用するための初期化のコードまで書きました<br />
今回は実際の操作(DAO)をつくります<br />
こちらが前回の分<br />
<a href="http://atmarkplant-dj.blogspot.sg/2014/05/magical-record.html">Magical Record 最初のステップ</a><br />
<h3>データ構造</h3>
今回使用するデータはこんな感じです<br />
<b>Country</b><br />
<ul>
<li>code Integer32</li>
<li>key String</li>
</ul>
Countryという名前のNSManagedObjectで, Integerと, Stringの2つのデータを持つ場合です<br />
uniqueなキーの用なものが必要であれば, もう少し工夫が必要です<br />
さて次はこれらのCRUD処理を書いていきます<br>
<h3>Create 作成</h3>
<pre class="brush: cpp;">
-(Country *)createCountry:(int)code key:(NSString *)key {
Country *country = [Country MR_createEntity];
country.code = [NSNumber numberWithInt:code];
country.key = key;
return country;
}
</pre>
利用方法<br>
<pre class="brush: cpp;">
CountryDAO *cdao = [[CountryDAO alloc] init];
[cdao createCountry:1 key:@"goodtimebiz.us"];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
</pre>
最後にcommitにあたるコードをいれます<br>
<h3>Read データをとってくる</h3>
Find All と, 属性での検索の例をのせます<br>
<pre class="brush: cpp;">
-(NSArray *)findAll {
return [Country MR_findAll];
}
</pre>
<br>
<pre class="brush: cpp;">
-(Country *)findByCode:(int)code {
return [Country MR_findFirstByAttribute:@"code" withValue:[NSNumber numberWithInt:code]];
}
</pre>
codeで検索します
<br>
<h3>Delete</h3>
データが多い場合は, 一気に消すために truncateを使いましょう<br>
1件ずつ消す場合は, <b>MR_deleteAllMatchingPredicate</b>を使います
<pre class="brush: cpp;">
-(void)deleteAll {
[Country MR_truncateAll];
}
</pre>
<h3>Update</h3>
Updateは特に解説するまでもないです<br>
findなどでデータをとってきて, データを直接更新最後に<br>
<pre class="brush: cpp;">
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
</pre>
を忘れずに
<h3>NSPredicate</h3>
少し, 条件を複雑にしたい場合, SQLのWHERE部分に相当する部分は, CoreData同様, Magical Recordでも<b>NSPredicate</b>を利用します<br>
少し例をかえます. Itemという新たなデータを作りました
<b>Item</b><br>
<ul>
<li>from Date</li>
<li>name String</li>
<li>to Date</li>
<li>uid String</li>
</ul>
少し複雑になりました<br>
<b>Deleteの例</b><br>
<pre class="brush: cpp;">
-(void)deleteByUid:(NSString *)uid {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uid == %@", uid];
[Item MR_deleteAllMatchingPredicate:predicate];
}
</pre>
<br>NSPredicateを使って, uidという値で検索して, deleteします.検索にかかったものをdeleteします
<br>
さらに複雑な例ですfromという時間を利用します<br>
<pre class="brush: cpp;">
-(NSArray *)findBetweenFromTo:(NSDate *)from to:(NSDate *)to {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(from >= %@) AND (to <= %@)", from, to];
return [Item MR_findAllSortedBy:@"from" ascending:YES withPredicate:predicate];
}
</pre>
<br>
<h3>ユニークなキー</h3>
CoreDataは自動的にprimary key的なものが追加されますが, 開発者側ではアクセスできません<br />
自分自身で primary key 的なものを管理したい場合新たに, ユニークなキーを要素として追加する必要があります<br />
個人的にはこんな感じに作っています
<pre class="brush: cpp;">
Item *item = [Item MR_createEntity];
NSString *uid = [[[item objectID] URIRepresentation] lastPathComponent];
</pre>
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-55128341562914988062014-05-11T12:06:00.001+09:002014-05-17T11:32:24.589+09:00Magical Record 最初のステップiOSでSQLiteを使用していればおそらくCore Dataを使っているかと思います。<br />
Core Dataの登場で, DBの操作はずいぶん楽になりました. ORMapperがあると楽ですよね.<br />
Core Dataを普通に使う場合, コネクションの部分や, Contextの処理は自身で書きます<br />
これが意外に面倒だったりします. <br />
Core Dataを普通に利用する場合はこちらでどうぞ.(English)<br /><br />
<h3>Core Data</h3>
<ul>
<li><a href="http://atmarkplant.com/core-data-prepare/">Core Data Prepare</a></li>
<li><a href="http://atmarkplant.com/coredata-file/">CoreData(File xcdatamodeld)</a></li>
<li><a href="http://atmarkplant.com/core-data-dao/">Core Data(Data Access Object)</a></li>
</ul>
<br />
<h3>Magical Record</h3>
このCoreDataをさらにwrapするライブラリ, Magical Recordがあります.<br />
Rubyの, Active Recordの操作感で実装できるwrapperです.<br />
上で書いたような処理を内部でやってくれるのでデータ処理の部分のみに集中できるという利点があります<br />
今回は導入部分のみで, 次回データ操作(DAO)の部分を扱います<br />
DAOは<a href="http://atmarkplant-dj.blogspot.sg/2014/05/magical-record-dao.html">こちら</a>
<br />
<h3>ステップ</h3>
導入におけるステップです<br />
<ul>
<li>CoreData.frameworkをリンク</li>
<li>Magical Recordのソースをgithubから持ってきて, プロジェクトにそのまま入れる</li>
<li>xcdatamodeldファイルを準備(ここは普通のCoreDataと同じでよい)</li>
<li>ModelファイルをXCodeから作成</li>
<li>AppDelegateにコードを書く</li>
</ul>
<br />
1, 3はCoreDataと同じです. Core Data関連は筆者の別ブログ(English)上で確認してください<br />
<br />
<h3>AppDelegate</h3>
AppDelegateにコードを書き足します. ピュアCoreDataと比べるとずいぶんシンプルです
<pre class="brush: cpp;">
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[MagicalRecord setupCoreDataStackWithStoreNamed:@"Model"];
return YES;
}
</pre> <br />
一行です. Modelのところはファイル名です. 適宜かえてください.
さてこれで使用する準備は整いました. 次回は実際の操作ですatmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-74193268459277798572014-05-11T10:09:00.000+09:002014-05-11T10:09:53.521+09:00Core Data テーブルの追加iOSでCore Data利用時に, テーブル(スキーマ)を追加する方法です。<br />
サーバーサイドでは, スキーマの変更などはよくあります. テーブルの追加,カラムの追加削除, などなどです.<br />
スキーマの変更コードをさらっと書いておしまい. というわけですが...
iOSでももちろんそういったケースは考えられます.<br />
Core Dataではどのようにするのでしょうか? <a href="http://atmarkplant.com/core-dataadd-new-table/">English</a>
<br />
<br />
<h3>今回取り扱うケース</h3>
<ul>
<li>すでにリリース済み xcdatamodelがすでに存在する</li>
<li>新しいデータを追加(テーブルの追加にあたる)</li>
</ul>
<h3>How to?</h3>
<h4>新しいバージョンの .xcdatamodelを追加</h4>
まず, xcdatamodelはバージョンで管理していることを押さえましょう.<br />
XCodeでは, rootのxcdatamodelが表示されています. 追加すると, モデル一覧が出てきます<br />
<br />
さて, バージョンを追加しましょう<br />
rootのxcdatamodelをポイントします<br />
次に, "New File" -> "Core Data" -> "Data Model" と選択します<br />
新しい xcdatamodelを追加できます. ここで, 名前を決めて終了です.<br />
<br />
名前ですが, モデルの番号かもしくはソフトウェアのバージョンをつけておくとわかりやすいです<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoe8jB3dG4Gbq-yVJxExmOpzvmP6j6nJEHtyC7QLb9StqFvgkVKDjrfnkvjogbChTD9OyUjZjVgN7k3trXHxF6Gkx7cU9yp0BI7dJGEWWPCXYjvTHQg69h_qN2afyDgQQ7ubmb9NoXik0/s1600/Screen+Shot+2014-05-10+at+11.15.32+AM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoe8jB3dG4Gbq-yVJxExmOpzvmP6j6nJEHtyC7QLb9StqFvgkVKDjrfnkvjogbChTD9OyUjZjVgN7k3trXHxF6Gkx7cU9yp0BI7dJGEWWPCXYjvTHQg69h_qN2afyDgQQ7ubmb9NoXik0/s320/Screen+Shot+2014-05-10+at+11.15.32+AM.png" /></a><br />
<br />
<h4>新しいバージョンにスイッチ</h4>
次に先ほど作成したバージョンにファイルを切り替える操作をします.<br />
xcdatamodelは選択できます. つまり戻したりできます.<br />
もういちど, rootのxcdatamodelをポイントします. File Inspector を観ましょう.<br />
下の方に, "Model Version" というのがあります.<br />
ここのドロップダウンで選択できます<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXe-eEmvCelAY_D2owI4Y53PhVGvOQos5usTGvguvagIQNwtI5_ngRNzfem2pgt7M0T-jRSN3VYUiCXqPRYEC1y29abTjynScwie_WfHXLvgQ4JH0C_B7siaZ8eFcs_ihZVfKwPb57xvQ/s1600/Screen+Shot+2014-05-10+at+9.02.33+AM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXe-eEmvCelAY_D2owI4Y53PhVGvOQos5usTGvguvagIQNwtI5_ngRNzfem2pgt7M0T-jRSN3VYUiCXqPRYEC1y29abTjynScwie_WfHXLvgQ4JH0C_B7siaZ8eFcs_ihZVfKwPb57xvQ/s320/Screen+Shot+2014-05-10+at+9.02.33+AM.png" /></a><br />
<h4>新しいバージョンのファイルにデータを追加</h4>
先ほどの新しいファイルにデータを追加します
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVmeuTw8Ceml85DVXaIAA3Ban9lFiwE2mdcdYXEXQdtXKQI7G2GehP_Dbgwj8d4M2eBFJYH9rF-joN8_1XRpSBE4SKMZ2Kyusv_vMIw3xbuZ6gofMtot8guxJlx60GEU8nq1HK8nWLG7c/s1600/Screen+Shot+2014-05-10+at+9.02.43+AM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVmeuTw8Ceml85DVXaIAA3Ban9lFiwE2mdcdYXEXQdtXKQI7G2GehP_Dbgwj8d4M2eBFJYH9rF-joN8_1XRpSBE4SKMZ2Kyusv_vMIw3xbuZ6gofMtot8guxJlx60GEU8nq1HK8nWLG7c/s320/Screen+Shot+2014-05-10+at+9.02.43+AM.png" /></a><br />
CustomHolidayというのを追加しました<br />
基本的に, スキーマ変更のためのコードは,自動的にやってくれます. カラムの追加時, データ構造の変更時であれば, overrideして操作コードを書かないといけませんが
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-59635462513408505842014-04-07T21:08:00.003+09:002014-04-07T21:09:22.194+09:00UIDatePickerをしまっておくUIをつくったこんにちは, 今回は, UIDatePickerに関する話題です。<br>
UIDatePickerは, 日時を選択するためのUIで配置するだけでりようできるので便利ですが, サイズが大きいので場所をとりますよね.<br>
なので, 見た目しまっておけるようなUIを作ってみました<br>
<br>
<a href="https://github.com/DJ110/TextDatePicker">TextDatePicker</a>という名前でGithubで公開しています.<br>
UITextFieldのようなところにボタンをつけました. このボタンを押すと, UIDatePickerが登場します.<br>
もう一度タップすると, UIDatePickerをしまうことができます<br>
UIPickerで変更した結果はその段階で文字列の時間に反映されます<br>
<br>
使い方はGithubに書いてある通りです<br>
ソースおよびリソースをコピーして, 数行書くだけで利用できます<br>
<br>
このままでは使いづらいところもあるでしょうから, コードなどをいじってみてもよいかもしれません<br>
UIViewを重ねたViewになっています.<br>
一部iOS7のアニメーションを使っています<br>
<br>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoaVlBOIIVje4-Payy8Da_sObyrAYYgbBdWWefqf8dMchlVgkb5PtRHhkYFqOHkizaDZGgwaGbGNw2ACPaIbtum8RjHd6blv73JeEgIKXZz2kVA-2vAoB5cyjifpY2a33ofUXIsO-Lu1w/s1600/textdatepicker1.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoaVlBOIIVje4-Payy8Da_sObyrAYYgbBdWWefqf8dMchlVgkb5PtRHhkYFqOHkizaDZGgwaGbGNw2ACPaIbtum8RjHd6blv73JeEgIKXZz2kVA-2vAoB5cyjifpY2a33ofUXIsO-Lu1w/s320/textdatepicker1.png" /></a></div><br>
<br>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDSpLhUakJQFpqu-3M0dX-m75zQqdbJw1ZmALrhr2xZ3Xwp3KYeFiMCWIr0VL7pv9_BPKs9AvvNLo2hVkHjELzdINO-cUf7ti2b4QKGXMvzSiN0UjMAyLOGFOw9G19__DVOTmCvWW0cLM/s1600/textdatepicker2.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDSpLhUakJQFpqu-3M0dX-m75zQqdbJw1ZmALrhr2xZ3Xwp3KYeFiMCWIr0VL7pv9_BPKs9AvvNLo2hVkHjELzdINO-cUf7ti2b4QKGXMvzSiN0UjMAyLOGFOw9G19__DVOTmCvWW0cLM/s320/textdatepicker2.png" /></a></div><br>atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-82231146130891010522014-03-29T10:18:00.002+09:002014-03-29T10:19:07.729+09:00Java(Android)時間操作ライブラリiOS に続いて, Java(Android)用に, 時間まわりのラリブラリを書きました
GitHubで公開しています
<a href="https://github.com/DJ110/JavaDatekit">JavaDatekit</a>
<b>build.xml</b>を準備しておきましたので, ant をお持ちの方は,
<pre>
ant
</pre>
で, dst/datekit.jar が作成されますので
PureJavaでコーディングしていますので, Androidでも動きます
<h3>概要</h3>
時間操作に関わる操作を, Builderでまとめてみました.
基本的な操作はCalendarクラスを使って, Date, Calendarの相互の変換などを入れました
Builderになっている部分で操作を行い, getのメソッドでデータを操作もしくは取得, format系のメソッドでString形式のデータを取得します
<h3>使い方</h3>
ソースコードをまるごとコピーする
もしくは, antを使って, datekit.jar を作成してそれをライブラリとしてプロジェクトにコピーします(BuildPathを通します)
<h3>メソッド</h3>
<b>コンストラクタ</b>
<ul>
<li>public DateBuilder()</li>初期化です現在時刻で初期化します
<li>public DateBuilder( TimeZone zone )</li>TimeZoneを使って初期化します
<li>public DateBuilder( Date date )</li>Dateを使って初期化します
</ul>
<b>builder系</b>
<ul>
<li>public DateBuilder on( Date date )</li> Dateインスタンスをセットします
<li>public DateBuilder addDay( int day )</li> day分だけ日にちを足します
<li>public DateBuilder subDay( int day )</li> day分だけ日にちを引きます
<li>public DateBuilder begin()</li> 日にちの最初00:00:00を取得します
<li>public DateBuilder end()</li> 日にちの最後23:59:59を取得します
<li>public DateBuilder firstDateOfMonth()</li> 現在月の最初の日を取得します
<li>public DateBuilder lastDayOfMonth()</li> 現在月の最後の日を取得します
<li>public DateBuilder nextMonthFirst()</li> 次の月の最初の日を取得します
<li>public DateBuilder getFromYMD( int year, int month, int day )</li> 年, 月, 日をセットします
<li>public DateBuilder getDayofDay( int day )</li> 曜日をセットします 0 日曜日, 6 土曜日
<li>public DateBuilder getGMT()</li> GMTのタイムゾーンをセットします
</ul>
<b>get系</b>
<ul>
<li>public int getYear()</li> 年を取得します
<li>public int getMonth()</li> 月を取得します
<li>public int getHour()</li> 時間を取得します(24h)
<li>public int getSecond()</li> 秒を取得します
<li>public Date getTime()</li> Dateインスタンスを取得します
<li>public Calendar getCal()</li> Calendarインスタンスを取得します
<li>public boolean isWeekDay()</li> WeekDay(ビジネスデイ)の判定をします
<li>public boolean isWeekEnd()</li> 週末かどうかを判定します
<li>public boolean before ( Calendar calendar )</li> 引数calendarとBuilderに保持している日時を比較して, 保持しているものが前ならtrueを返します
<li>public boolean before ( Date date )</li> 引数dateとBuilderに保持している日時を比較して, 保持しているものが前ならtrueを返します
<li>public boolean after ( Calendar calendar )</li> 引数calendarとBuilderに保持している日時を比較して, 保持しているものが後ならtrueを返します
<li>public boolean after ( Date date )</li> 引数dateとBuilderに保持している日時を比較して, 保持しているものが後ならtrueを返します
<li>public DateBuilder copy()</li> DateBuilderをコピーして新しいDateBuilderを作成します
</ul>
<b>format系</b>
Builderで持っている情報をStringで返します
<ul>
<li>public String formatYear()</li> yyyy形式で返します
<li>public String formatMonth()</li> MM形式で返します
<li>public String formatDay()</li> dd形式で返します
<li>public String formatA()</li> yyyy-MM-dd形式で返します
<li>public String formatB()</li> yyyy/MM/dd形式で返します
<li>public String formatFull()</li> yyyy/MM/dd HH:mm:ss形式で返します
</ul>
<h3>サンプル</h3>
<b>テストより</b>
年, 月, 日をセットします
<pre class="brush: java;">
DateBuilder builder = new DateBuilder();
builder.getFromYMD(2014, 3, 11);
System.out.println(builder.formatA()); // 2014/03/11
</pre>
2014/03/11の00:00:00をCalendarインスタンスで取得します
<pre class="brush: java;">
Calendar cal = builder.getFromYMD(2014, 3, 11).begin().getCal();
</pre>
GMT
<pre class="brush: java;">
DateBuilder builder = new DateBuilder(TimeZone.getTimeZone("GMT"));
</pre>
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-5484536535285029872014-03-01T12:31:00.001+09:002014-03-01T12:31:46.189+09:00iOS時間操作ライブラリ(DateKit)ちょっとしたアプリを開発しようかと思い立って, つらつらとコードを書いていたところ,さくっと機能的なところがまとまったので
公開できそうな部分を切り離して, GitHubで公開いたしました, かなり久々の公開です
iOSで時間などを操作するためのライブラリです, "DateKit"と名付けました URLは<a href="https://github.com/DJ110/DateKit">こちら</a>
中身はきわめてシンプルな, NSDateとNSStringのCategoryとして実装してありますので, 特に何か依存するライブラリなどはなく
<b>NSDateと, NSString</b>に機能が追加されたなあという程度のものです
タイムゾーン的なところは意識していないです,(このあたりもやりたいなあ)
GitHubでは英語の解説が中心ですので, 日本語の解説をこちらでエントリーしておきました
<h3>使い方</h3>
プロジェクトそのものは, staticライブラリになっているので, ビルドしてヘッダーをコピーしていただければ外部ライブラリとして活用できるはずです.
その辺りが面倒であれば,
<h3>UnitTest</h3>
UnitTestを準備しておきました. <b>DateKitTests.m</b>というソースがありましてその中にざっくりとしたUnitテストが入っています
実行のさせ方は, 通常のテストと同じで, XCodeでスキームを切り替えてTestで実行させればできます
詳しくは, <a href="http://atmarkplant.com/ios-unittestxctest/">iOS UnitTest(XCTest) English</a>
でUnitTestの走らせ方は解説しています
<br>
<br>
<h3>メソッド</h3>
現在公開しているメソッドは以下のとおりです(後でかわるかも)
<h5>NSDate</h5>
<ul>
<li>-(NSDate *)getAdd:(int)days</li>
days分だけ日数をプラスします
<li>-(NSDate *)getSub:(int)days</li>
days分だけ日数を引きます
<li>-(NSDate *)getFirstDateOfMonth</li>
月のはじめの日をとってきます
<li>-(NSDate *)getLastMonthLast</li>
次の最後の日をとってきます
<li>-(NSDate *)getNextMonthFirst</li>
次の月のはじめの日をとってきます
<li>-(int)getWeekDay</li>
何曜日かをとってきます1:日曜日, 7:土曜日
<li>-(BOOL)isWeekEnd</li>
NSDateが週末かどうかを判定します
<li>-(BOOL)isWeekDay</li>
NSDateがビジネスデイかどうかを判定します
<li>-(int)getDay</li>
NSDateから, 日にち情報を取得します
<li>-(int)getMonth</li>
NSDateから, 月情報を取得します
<li>-(int)getYear</li>
NSDateから, 年情報を取得します
<li>-(int)getHour</li>
NSDateから, 時間情報を取得します
<li>-(int)getMinutes</li>
NSDateから, 分情報を取得します
<li>-(int)getSeconds</li>
NSDateから, 秒情報を取得します
<li>-(NSDate *)begin</li>
その日のはじめ00:00:00を取得します
<li>-(NSDate *)end</li>
その日の終了23:59:59を取得します
<li>+(NSDate *)getFromYMD:(int)year month:(int)month day:(int)day</li>
年, 月, 日にちより, NSDateをつくります
<li>-(NSDate *)getFromTime:(int)hour minute:(int)minute second:(int)second</li>
NSDateに時間情報をセットします
<li>-(NSString *)formatYear</li>
NSDateより, yyyyフォーマットのNSStringを取得します
<li>-(NSString *)formatMonth</li>
NSDateより, MMフォーマットのNSStringを取得します
<li>-(NSString *)formatDay</li>
NSDateより, ddフォーマットのNSStringを取得します
<li>-(NSString *)formatA</li>
NSDateより, yyyy-MM-ddフォーマットのNSStringを取得します
<li>-(NSString *)formatB</li>
NSDateより, yyyy/MM/ddフォーマットのNSStringを取得します
<li>-(NSString *)formatFull</li>
NSDateより, yyyy/MM/dd HH:mm:ssフォーマットのNSStringを取得します
<li>-(BOOL)before:(NSDate *)target</li>
NSDateがターゲットのNSDateより前であるどうかの判定をします
<li>-(BOOL)after:(NSDate *)target</li>
NSDateがターゲットのNSDateより後であるどうかの判定をします
</ul>
<h5>NSString</h5>
<ul>
<li>-(NSDate *)parseYYYYMMddD</li>
YYYY-MM-dd の形のNSStringをパースして, NSDateで返します, 失敗するとnilが返ります
<li>-(NSDate *)parseYYYYMMddS</li>
YYYY/MM/dd の形のNSStringをパースして, NSDateで返します, 失敗するとnilが返ります
<li>-(NSDate *)parseH</li>
YYYY-MM-dd HH:mm の形のNSStringをパースして, NSDateで返します, 失敗するとnilが返ります
<li>-(NSDate *)parseFull</li>
YYYY-MM-dd HH:mm:ss の形のNSStringをパースして, NSDateで返します, 失敗するとnilが返ります
</ul>
<h3>例をいくつか</h3>
1日先のNSDateをとってくる
<pre class='brush: cpp'>
NSDate *today = [NSDate date];
NSDate *tomorrow = [today getAdd:1];
</pre>
1日前のNSDateをとってくる
<pre class='brush: cpp'>
NSDate *today = [NSDate date];
NSDate *yesterday = [today getSub:1];
</pre>
YYYY/MM/ddのフォーマットのNSStringをパースする(失敗するとnilがかえります)
<pre class='brush: cpp'>
NSString *str = @"2014/03/01";
NSDate *format = [str parseYYYYMMddS];
</pre>
例を増やしていければなと思います(編集中)atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-68448441397673513602014-02-15T09:34:00.001+09:002014-02-15T09:35:31.956+09:00gitでorigin/origin/masterをつくってしまったのでなんとかするこんにちは, 今回はgitでミスってしまったときの話です.
先日, jenkinsの設定をミスりまして, うっかりremoteに, origin/origin/masterを作ってしまいました
origin/masterにプッシュするつもりが, jenkinsのタグうちの設定をミスったようです
これをなんとかしたかった訳ですが, 微妙に面倒な事になったのでメモ
<h4>いつも通りcheckout</h4>
このブランチを別へへマージしたかったのでチェックアウト
<pre>
git checkout origin/master
</pre>
すると, derived from origin/master となり, masterがチェックアウトされる
こうなったら
<pre>
git checkout origin/origin/master
</pre>
derived from origin/origin/master となりますが, 一時チェック的になり操作が面倒
おいおい!
<h4>名前を指定してcheckout</h4>
きちんと名前を指定すればちゃんと来ます
<pre>
git checkout remote/origin/origin/master origin/master
</pre>
これで, 思い通りの, origin/origin/master が origin/master という名前で, ローカルにチェックアウトされました
<h4>リモートを消去する</h4>
さて, ここが怖いところです
<pre>
git push --delete origin origin/master
</pre>
これで消去できましたatmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-46386658968252459082014-02-13T22:44:00.000+09:002014-02-13T22:44:03.401+09:00iOS7 レビューサイトへ飛ばすEnglish is <a href="http://atmarkplant.com/ios7-review-app/">here</a>
アプリをリリースしたら, やはりレビューをかいてもらいたいのでなんとかして書いてもらえるように, レビューサイトへ誘導したい
iOS6までは,
<br />
<h3>
iOS7は?</h3>
iOSでレビューサイトへ行くというのは, App Storeのレビューの部分へ行くということです
どうやらiOS7から, レビューの部分に誘導するURIがなくなったようです
ですので明確な回答を得られていません.
<br />
<h3>
それでもなんとかしたい</h3>
明確な回答ではありませんが, アプリのページまでは誘導できるようです. ここからユーザにレビューまで行っていただかなければなりません
App StoreアプリをHackできればよいのですが...
<h3>ライブラリを利用</h3>
<b>Appirater</b>というライブラリがあります. <a href="https://github.com/arashpayan/appirater">GitHub</a>
このライブラリを組み込めば簡単にレビューサイトへの誘導を実装できます
<h3>どのように実現するか?</h3>
アイディアとしては以下のとおりです
<ul>
<li>SKStoreProductViewControllerでアプリ内にApp Store的な機能を入れる</li>
<li>UIApplication openURLでApp Storeアプリを起動する</li>
</ul>
SKStoreProductViewControllerを使うやり方には罠があります
これは, アプリ内で, App Storeめいたことをするわけですが, レビュー機能がOffになっています
オプションで変更できると思いきやできません
<h3>Appiraterの使い方</h3>
SDKをダウンロードしてきて解凍します. 中にある, <b>Appirater.h, Appirater.m, AppiraterDelegate.h</b>と,必要な言語ファイル xxx.lproj
をプロジェクトにコピーします
コードはGitHubを見た方が簡単です. デバッグモードの設定などパラメータに気をつける必要があります
<pre class="brush: cpp;">
[Appirater setAppId:@"YOUR_ITUNES_APP_ID"];
[Appirater setDaysUntilPrompt:7];
[Appirater setUsesUntilPrompt:5];
[Appirater setSignificantEventsUntilPrompt:-1];
[Appirater setTimeBeforeReminding:2]; // Days after selecting later
[Appirater setDebug:NO]; // Debug mode production should be NO
[Appirater appLaunched:YES]; // Launch when starting application
</pre>
<h3>コードサンプル SKStoreProductViewController</h3>
SKStoreProductViewControllerを使ってよりシンプルに実装する場合
<pre class="brush: cpp;">
NSNumber *appId = [NSNumber numberWithInteger:YOUR_ITUNES_APP_ID]
SKStoreProductViewController *storeViewController = [[SKStoreProductViewController alloc] init];
[storeViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:appId} completionBlock:nil];
storeViewController.delegate = self; // SKStoreProductViewControllerDelegate
[self presentViewController:storeViewController animated:YES completion:^{
// Open store view controller complete event
}];
</pre>
<h3>UIApplicationを利用した場合</h3>
<pre class="brush: cpp;">
NSString *templateReviewURLiOS7 = @"itms-apps://itunes.apple.com/app/idAPP_ID";
NSString *reviewURL = [templateReviewURLiOS7 stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", @"YOUR_APP_ID"]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]];
</pre>
YOUR_ITUNES_APP_ID は, iTunes 上でのアプリのIDです
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-45606568774428619192014-01-26T11:00:00.001+09:002014-01-26T11:00:53.823+09:00UINavigationControllerとUIViewControllerで画面固定こんにちは, 今回は最近はまったUINavigationControllerとUIViewController, <a href="http://atmarkplant.com/orientation-fix/">English</a>
<b>UIViewController</b>のOrientationの固定方法はいろいろなところで述べられています
が, ここに<b>UINavigationController</b>をはさむといろいろおこります.
Storyboardやxibを使っている場合は特に問題ないはずですが, コードでガリガリUIを書いている場合は, こういった問題があります
オリジナルは<a href="http://atmarkplant.com/orientation-fix/">こちら(English)</a>
早速いきましょう
<h3>ViewController画面固定</h3>
この例では, portraitにしています
<pre class="brush: cpp;">
@interface FixOrientationViewController ()
@end
@implementation FixOrientationViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view setBackgroundColor:[UIColor whiteColor]];
}
#pragma mark - Orientation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // iOS5
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL) shouldAutorotate {
return YES;
}
- (UIInterfaceOrientation) preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
-(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
@end
</pre>
#pragma mark 以下の部分がキーです.
その部分からが, portraitのfixコードです
これを通常のコードで呼び出すと, 特に問題なく画面が固定されます
<h3>問題のあるコード</h3>
UINavigationContrllerを入れます
<pre class="brush: cpp;">
FixOrientationViewController *controller = [[FixOrientationViewController alloc] init];
[controller setTitle:@"Bee"];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:nav animated:YES completion:nil]; // FixOrientationViewController setting doesn't work
</pre>
そうすると, UINavigationContrllerが加わるのと同時に, 中に存在するはずのFixOrientationViewControllerが画面固定されなくなります
<h3>対処法</h3>
UINavigationControllerをextendsしてportrait固定のUINavigationControllerをつくってしまおうというのがアイディアです
<pre class="brush: cpp;">
@interface FixNavViewController ()
@end
@implementation FixNavViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
#pragma mark - Orientation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // iOS5
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL) shouldAutorotate {
return YES;
}
- (UIInterfaceOrientation) preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
-(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
</pre>
あらためてこれを使ってコードを書きます
<pre class="brush: cpp;">
FixOrientationViewController *controller = [[FixOrientationViewController alloc] init];
[controller setTitle:@"Bee"];
FixNavViewController *nav = [[FixNavViewController alloc] initWithRootViewController:controller];
[self presentViewController:nav animated:YES completion:nil];
</pre>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBvVwMTqf3C4WNDMMleG1fgWLPymcBwJBeA8KEBIuSlGOXtVMzB4g7XKb4_6J4g92DEmKVdiMJO9TSJPlNPCop-XRCiRcBVraYX6kyS5dr3QrvPcrqFVltGrerlE-xQNAMno0E7oi_psw/s1600/iod-simulator.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBvVwMTqf3C4WNDMMleG1fgWLPymcBwJBeA8KEBIuSlGOXtVMzB4g7XKb4_6J4g92DEmKVdiMJO9TSJPlNPCop-XRCiRcBVraYX6kyS5dr3QrvPcrqFVltGrerlE-xQNAMno0E7oi_psw/s320/iod-simulator.png" /></a>
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-82262194352829766632014-01-11T21:58:00.000+09:002014-01-11T22:05:34.339+09:00Git for Macオリジナルはもうひとつのプロフェッショナルプログラマー(<a href="http://atmarkplant.com/install-git-for-mac/">English</a>)です
MacでGitを使いたい。どうやってインストールするの? です。EclipseのEgitを使うのも一つの手です。
個人的には, SourceTreeなんかもいいかなと思います。
通常のコマンドGitをインストールするにはちょっとしたことが必要です。
いくつか方法があると思いますが, 個人的には, homebrewを使った方法が簡単だと思っています。
Marveriks, Mounttain Lionで実行済
<h3>手順</h3>
homebrewをインストールしていない場合は最初からですRubyはデフォルトであるはずです。
<ul>
<li>homebrewをインストール</li>
<li>homebrewを使って, git をインストールする</li>
</ul>
<h3>インストールhomebrew</h3>
homebrewは, MacPortsと同じようなものです。
Rubyがあれば簡単にインストールできます。詳しくは, <a href="http://brew.sh/">こちら</a>
インストールは以下のコマンドで行きます。
<pre>
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
</pre>
<h3>gitをhomebrewでインストール</h3>
homebrewがインストールできれば次はgitです。
<pre>
brew doctor # check brew
brew update # this is update(not required)
brew install git
</pre>
本当に必要なのは最後のコマンドです。他はbrewのチェックと言ったところでしょうか
<h3>確認</h3>
gitが正しくインストールされてかを確認(ターミナル)
<pre>
git --version
</pre>atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-58433854379203092342013-12-30T22:14:00.002+09:002013-12-30T22:15:27.525+09:00iOSとAndroidでjsonもうひとつのプロフェッショナルプログラマーからの転載まとめ版です。
こちらがオリジナル
<a href="http://atmarkplant.com/ios-json/">iOS版(日本語)</a>
<a href="http://atmarkplant.com/android-json/">Android版(English)</a>
ネットワーク関係のプログラミングをしていると, json形式のデータを扱うことがあります。
サーバ側, クライアント側両方です。
筆者の場合, iOS, Android, Amazon KindleのIn app purchase(billing)なんかの, レシートを扱うときに,使用しました。
<h2>iOS</h2>
iOSには, 5以降で <b>NSJSONSerialization</b> クラスというが登場しました。
JSONObjectWithData というメソッドで, NSDataをNSArrayや, NSDictionaryなどに変換できます
返り値はidで, NSArray, NSDictionaryもしくはMutable系となります。
NSDataを引数として取るので, NSString の場合は変換の必要があります。
オプションとして, NSJSONReadingMutableContainers, NSJSONReadingMutableLeaves, NSJSONReadingAllowFragments 後ろの二つがどのような場面で利用できるかいまいちわかっていないです。
iOS Developer Libraryに説明がでています。
<h4>NSString -> json -> NSArray, NSDictionary</h4>
<pre class="brush: cpp;">
NSString *str = @"[{\"key\":\"value\"}]";
NSError *error = nil;
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSLog(@"%@", array);
NSLog(@"%@", [array objectAtIndex:0]);
</pre>
この場合, NSDictonaryとしてデータを扱えます。
このように, NSArray, NSMutableArray, NSDictionary, NSMutableDictionary として扱えるので,
大変便利です。
<br>
<br>
<h4>NSDictionary -> json -> NSData</h4>
<pre class="brush: cpp;">
// dict to json(NSData)
NSMutableDictionary *mdic = [[NSMutableDictionary alloc] init];
[mdic setObject:@"madoka" forKey:@"name"];
[mdic setObject:[NSNumber numberWithInt:14] forKey:@"age"];
if([NSJSONSerialization isValidJSONObject:mdic]){
NSData *json = [NSJSONSerialization dataWithJSONObject:mdic options:NSJSONWritingPrettyPrinted error:&error];
NSLog(@"json %@", [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding] );
}
// Result
// json {
// "age" : 14,
// "name" : "madoka"
//}
</pre>
<br>
NSJSONSerializationのdataWithJSONObjectを使って, NSMutableDictionaryを
jsonにそして, NSDataにしてNSStringで表示させています。isValidJSONObjectを
使うと, 正しい形式かどうかをチェックできます。
<br>
<br>
<h2>Android</h2>
さて, こちらは外部ライブラリの力を借ります。
<a href="https://github.com/douglascrockford/JSON-java">JSON-java</a>というのがあります。
以前, サーバ側で利用していたのですが, Androidでも利用できそうなので, Android仕様でコンパイルしてみましたが
うまくいきましたので, そのまま利用してみました。
<h4>サンプル</h4>
文字列をJSONObjectに変換, キーを使って取り出したり, 文字列型に変換したりします。
<pre class="brush: java;">
String str = "{\"key\":\"value\"}";
JSONObject jobj = new JSONObject(str);
String value = jobj.getString("key");
// Long, Boolean, Object, JSONArray, JSONOject
System.out.println(value); // "value"
String str2 = "{\"name\":\"Homuhomu\", \"age\":14}";
JSONObject jobj2 = new JSONObject(str2);
Integer age = jobj2.getInt("age");
System.out.println(age); // 14
System.out.println(jobj2.toString()); // String style
</pre>
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-55735654581999290702013-12-03T23:09:00.002+09:002013-12-03T23:10:16.574+09:00Android Chromeからアプリを起動するもうひとつのProfessional Programmerと同じ内容です。 English is <a href="http://atmarkplant.com/run-from-chrome/">here</a>
iOSとSafariの組み合わせと同様に, AndroidのChromeアプリから, AndroidのIntentが呼び出せます。つまりアプリが起動できます。
アプリに対してパラメータを入れたりすることができます。
<br>
<br>
<h3>ステップ</h3>
<ul>
<li>Androidアプリケーションの準備</li>
<li>URLの準備</li>
<li>Chromeを開いて, リンクを表示し, 実行</li>
</ul>
<br>
<br>
<h3>Androidアプリケーションの準備</h3>
例として, 起動するためのActivity, MainActivityに
設定は, AndroidManifest.xml に書きます。
<pre class="brush: xml;">
<activity android:label="@string/app_name" android:name="com.atmarkplant.webapp.MainActivity" android:screenOrientation="portrait" android:theme="@android:style/Theme.Holo.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="key" android:host="application" android:path="/"/>
</intent-filter>
</activity>
</pre>
ポイントは, 2段目のintentfilterです。
action, category のところはコピペでO.K. 最後のdataの<b>android:schme</b>, <b>android:host</b>, <b>android:path</b>は, Chromeのリンクのパラメータとして
利用するので, ここをカスタマイズしていきます。
<br>
<br>
<h3>URLの準備</h3>
リンクを作ります。筆者は, これ用のサーバを用意し, HTMLファイルを用意してリンクを作りました。
<pre class="brush: html;">
<a href="intent://application/#Intent;scheme=key;package=com.atmarkplant.webapp;end"> Start Web App </a>
</pre>
書式は, このようになります。Google Developer参照
<pre>
intent:
HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string];
scheme=[string];
end;
</pre>
上でいう例は, hostに, application, path は, /, schemeには, key というのをそれぞれ入れました。
package は, アプリのパッケージのことです。
<br>
<br>
<h3>Chromeを開いて, リンクを表示し, 実行</h3>
早速実行してみましょう。
実行方法は, Chrome を開いて, このリンクを表示させます。そしてリンクをたたきます。
リンクを開くと, Androidアプリケーションが起動します。
<br>
<br>
<h3>パラメータの渡し方・受け取り方</h3>
まずは, 渡し方です。これはリンクの中に埋め込みます。
<pre class="brush: html;">
<a href="intent://application/#Intent;scheme=key;package=com.atmarkplant.webapp;S.key1=value1;S.key2=value2;end"> Start Web App </a>
</pre>
<b>S.</b> をつけて, key = value という形で表現します。ここでいう例は, key1, value1, key2, value2 になります。
<br>
<br>
続いて受け取り方です。結構迷いましたが, 単純に考えればわかります。intent で受け取っているので, Activity からgetIntent()で
Intentを取得して, そこから値を取り出します。通常の起動方法の場合は, 値はnullになります。
<pre class="brush: java;">
Intent intent = getIntent();
String value1 = intent.getStringExtra("key1");
String value2 = intent.getStringExtra("key2");
</pre>
このコードを, Activityのどっかに埋め込みます。それにより挙動が返られますね。先ほど申しましたとおり, パラメータを渡さない
通常のアプリとして起動すると, value1, value2は, <b>null</b>になります。
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-25803354952189574022013-11-09T09:42:00.000+09:002013-11-11T11:06:18.501+09:00APKとjarを解体するこんにちは, 最近iOSとAndroidとJava Server系を行ったり来たりする日々が続いている開発者です。
今回筆者がはまった問題は以下の2つです
<ol>
<li>3rdパーティー提供のライブラリ(.jar)の中身が知りたい</li>
<li>自分で作成したapkが正しく作成されているかを確認</li>
</ol>
1は, まああまりよろしくないリバースエンジニアリングなわけですが... Bugなのか, 自分が悪いのかどうしてもわからなかったので少し
見させていただきました, それ以外の用途はないですよ。
2は, Androidの<b>proguard</b>をかけたときにまったく動かなくなって
こちらが元記事(<a href="http://atmarkplant.com/apk-analysis/">APK Analysis(English)</a>) 筆者の別ブログです
APK Anaysis is English post. If you want to read in English, please access this.
さて本題です。
<h3>内容</h3>
<ul>
<li>APKの中身を見る(構成)</li>
<li>jarを解体そしてコードを見る</li>
<li>APKの中身をみる(コードも)</li>
</ul>
<h3>APKの中身を見る(構成)</h3>
apk というのは, 実はただのzipです。.zipに拡張子を変更して, 解凍すると構成するファイルはだいたい見えます
これだけではよくわからないので, 解析ツールを使います
<a href="https://code.google.com/p/android-apktool/">apktool</a>というのがあります。
Windows, Linux, Mac バージョンそれぞれあります。私は, Macバージョンで試してみました。
ダウンロードして解凍すると, それぞれ, スクリプト がありますのでそれを利用します
<pre>
./apktool decode xxx.apk
</pre>
Macの場合
これで, フォルダに展開されます, 中身はこんな感じです
<pre>
xxx
| - AndroidManifest.xml
| - apktoolyml
| - assets
| - lib
| - res
| - smali
</pre>
<h3>jarを解体そしてコードを見る</h3>
.jar というのはただのzipであるのは, よく知られています。
jarが構成するクラスファイルを閲覧するには, javaのコマンドを使います。
<pre>
jar -tf xxx.jar
</pre>
xxx.jar が jarファイル
しかしこれではソースの中身は見れません。そこでデコンパイルします
<a href="http://jd.benow.ca/">Java Decompiler</a>というツールがありまして, jarを取り込んで解析してくれます。
<b>JD-GUI</b>など, GUIツールもあります。
JD-GUIの使い方は非常に簡単なので, 特にここでは触れません。
<br>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDw8gGaL2eD-8JlrJLRqk40DJ3_1lFWBzMe2VzjZ7bxwHW8YZMe18UGNOLO-hD5UBF2oiWSNymuGlOLd7NjsSDk2sLNw_lXY8GX1fFXZJhyphenhyphenv_2xDA6qJR8aC8p_mmBPpuSubdfNHbM0V4/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2013-11-07-3.13.34-PM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDw8gGaL2eD-8JlrJLRqk40DJ3_1lFWBzMe2VzjZ7bxwHW8YZMe18UGNOLO-hD5UBF2oiWSNymuGlOLd7NjsSDk2sLNw_lXY8GX1fFXZJhyphenhyphenv_2xDA6qJR8aC8p_mmBPpuSubdfNHbM0V4/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2013-11-07-3.13.34-PM.png" /></a>
<br>
JD-GUIを使用した例です(Mac OS X)
これでjarファイルのソースはばっちり見えます。
<h3>APKの中身をみる(コードも)</h3>
さて, はじめの方法ではファイル構成は見えても肝心のコードは見えません。
<b>apktool</b>を使ってもコードは無理です。
そこで, apk内にあるdexをjarに変換して, JD-GUIでjarを展開することで, ソースをみます
<h4>Steps</h4>
<ul>
<li>dex2jar を使ってclass.dexをjarに変換</li>
<li>Java Decompileを利用してjarを読み込む</li>
</ul>
<h4>dexをjarに変換</h4>
先ほど, apk をzip にして解凍しました。その時に, <b>class.dex</b>というファイルが生成されています
dexの詳しい説明は省きます, Dalvikが読み込むための形式とでもいいましょうか
<a href="https://code.google.com/p/dex2jar/">dex2jar</a>は, dexをjarに変換するツールです, ダウンロードして解凍すると
スクリプトとjarがあります。Windows, Linux, Macで動作します
<pre>
./dex2jar.sh classes.dex
</pre>
これで, カレントディレクトリに, jarファイルが生成されますので, あとはJava Decompileで閲覧できます
※それにしてもproguardはやっかいなやつです(ぼやき)
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-80159569300600479122013-09-23T23:04:00.003+09:002013-09-23T23:06:33.931+09:00iOS7 対応アプリにする(マイクとUI)iOS7が解禁です。 UIが大幅にアップデートするなど, いろいろあるみたいですが, そのおかげで以前に作っていたアプリに
変更を入れないといけないことが判明。SDKの変更をしたら色々出てきたそのレポートです。
変更が必要になってしまった部分
<br />
<ul>
<li>マイクの使用にパーミッションが必要になったこと</li>
</ul>
SDKのアップデータの為に変更を余儀なくされた部分。
<br />
<ul>
<li>storyboard がiOS6, iOS7用のviewに分かれ, iOS7用のviewで不具合が</li>
</ul>
<br />
<h3>内容</h3>
<ul>
<li>マイクのパーミッション</li>
<li>バージョンの取得と動作</li>
<li>Navigationbarの処理</li>
</ul>
<br />
<h3>マイクのパーミッション</h3>
GPSのデータを使用する際やプッシュ通信の許可と同様に, ダイアログが出てきます(iOS7)。
その許可なくマイクロを使用すると, Applicationが落ちるそうです。
以下のコードを追加します。
<br />
<pre class="brush: cpp;">
if([[AVAudioSession sharedInstance] respondsToSelector:@selector(requestRecordPermission:)])
{
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL allowed){
NSLog(@"Allow microphone use? %d", allowed);
if ( !allowed ) {
// If not enabled microphone, here is
}
}];
}
</pre>
<b>iOS6ではこのコードは無視されます</b>。<b>当然i<span style="color: red;">OS7 SDKでコンパイル</span>しないといけません</b>。requestRecordPermission というメソッドが存在しないからです。
一度許可すると, allowed の部分が常に, trueになります。ダイアログは出ません。
ですので, マイクを使用する部分にこのコードを入れておくとよいでしょう。
ブロックコードでないバージョンもあります。
<br />
<br />
<h3>バージョンの取得と動作</h3>
いろいろ7のおかげで動かなくなったUIのために, バージョン別コードを挿入する必要があったので作っておく
<pre class="brush: cpp;">
+(BOOL)isOver7 {
const float version = [[[UIDevice currentDevice] systemVersion] floatValue];
return version >= 7.0;
}
</pre>
iOS7の場合, trueが返ります。 UIDevice の systemVersionを使います。
<br />
<br />
<br />
<h3>Navigationbarの処理</h3>
ここからはUIの管理です。
UIに関しては, <a href="http://www.qubop.com/ios7.pdf">Guide</a>が詳しい。いろいろ対処法を伝授してくれるはず。
端的に問題をいいますと, Navigationbar が透明になって, 座標の位置が変わった。
今まで viewがbar の下から始まっていましたが透明になって, barの位置からになりました。ので, UIがずれます。
<br />
<h4>不透明にしてiOS6と同じ座標位置にする</h4>
これは簡単な対処です。
<pre class="brush: cpp;">
self.navigationController.navigationBar.translucent = NO;
</pre>
UINavigationBar の translucent を使います。これで不透明になります。
<br />
<h4>地道にデルタを使ってUIの位置を直す</h4>
※AutoLayoutのプロパティをチェックしてればこの設定はありません。そして必要ありません。(デルタの部分は出てきません)
iOS6の位置と, iOS7の位置をInterfaceBuider のデルタを使って変更することができます。つまり, 透明になった部分だけずらすということです。
さて, storyboardがどう変わったのか見てみましょう。
<br /><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbz672hoxuv51vsSzJpZ_jxwlQYx7zAPVKSOPwqdhq5XXjbkhYQ-Y5rXPY_UNIvy7NimBQm2ooTpN8N5mUr16vDDc_ZKyk0M_R-Zcuw8_BUz8pmsYNfSPoztSV7M2EYmra4q637ZJYDSU/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2013-09-23+20.47.31.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbz672hoxuv51vsSzJpZ_jxwlQYx7zAPVKSOPwqdhq5XXjbkhYQ-Y5rXPY_UNIvy7NimBQm2ooTpN8N5mUr16vDDc_ZKyk0M_R-Zcuw8_BUz8pmsYNfSPoztSV7M2EYmra4q637ZJYDSU/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2013-09-23+20.47.31.png" /></a>
<br /><br />
storyboardのviewという欄でiOS6, iOS7のViewが登場します。選択すると, 見た目の違いがここで確認できます。
<br />
<br />
<h3>UITabbar</h3>
Navigationbarと同じでありまた, カスタムで画像や色を変更している場合は, 結構引っ掛かります。
tabbaritem の画像を変更している場合 <b>setFinishedSelectedImage </b>を使いますが, なぜかいうことを聞きません。
selectedImage というプロパティを直接操作します。
<pre class="brush: cpp;">
item.selectedImage = [UIImage imageNamed:@"filename"];
</pre>
同様に, 不透明にできます。(ただしiOS7しか使えないので, 6では実行されないように処理する必要があります)
<pre class="brush: cpp;">
self.tabBarController.tabBar.translucent = NO;
</pre>
※UIに関しては, AutoLayout機能でなんとかしてくれたりします。ですので一概に問題が発生するとは言い難いです。
AutoLayoutはiOS6以降ですけども。
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-51070101239896938842013-09-22T14:17:00.004+09:002013-09-22T14:18:50.640+09:00iOS Admob 簡単導入iOSでAdmobを導入する方法です。<br />
<br />
こちらのアプリ(<a href="https://itunes.apple.com/jp/app/karamemo/id707254892">Colomemo</a>) で実際に試している方法です。<br />
無料ですので, 是非試してみてくださいな。
<br />
Admobはバージョンによって結構違ってくるので, その都度調べなおすのがまあまあ面倒です。<br />
<br />
Versionは, <b><span style="color: red;">6.5.1</span></b>です。
このエントリーは, 筆者の別のブログ, <a href="http://atmarkplant.com/ios-admob-intro/">iOS Admob(導入編)</a>, <a href="http://atmarkplant.com/ios-admob-delegate/">iOS Admob(表示されなかった場合の処理)</a> の合併号のようなものです。
今回の目的は, 表示すること, ネットに接続されていない場合にスペースが余るのを回避することです
<h3>ステップ</h3>
<ol>
<li>GoogleAdMobAdsSdkiOSをダウンロード</li>
<li>依存関係ライブラリ(Framework)を追加</li>
<li>Liner flagを追加</li>
<li>AdmobのWebサイトでIDを発行してもらう</li>
<li>GADBannarViewの設置</li>
<li>GADBannerViewDelegateの実装</li>
</ol>
<h3>GoogleAdMobAdsSdkiOSをダウンロード</h3>
GoogleAdMobAdsSdkiOS.zipを<a href="https://developers.google.com/mobile-ads-sdk/download">Google</a>よりダウンロードして解凍します。libGoogleAdMOobAds.aおよび, .h ヘッダー達を使いたい
プロジェクトにコピーして.a はリンクします。<br>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM1gZxQeOgfbL1bbOs9Org6zhlpb1UxtldL4NPFAwV1au6WymrdT4fL_CZqKteAv34U0i_GRlLIWoNRQ4seW6l7ZX2ZoEBgcX_xjXOPX2ENOPZEXrW8-zcI92xY1aeKqYV8lx5N8t5vas/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2013-09-12-22.17.11-300x287.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM1gZxQeOgfbL1bbOs9Org6zhlpb1UxtldL4NPFAwV1au6WymrdT4fL_CZqKteAv34U0i_GRlLIWoNRQ4seW6l7ZX2ZoEBgcX_xjXOPX2ENOPZEXrW8-zcI92xY1aeKqYV8lx5N8t5vas/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2013-09-12-22.17.11-300x287.png" /></a>
<br>
解凍すると, thirdparty がどうとかいうディレクトリがありますが今回は使いません。
<h3>依存関係ライブラリ(Framework)を追加</h3>
必要なFrameworkを追加します。
ドキュメントによると以下のFrameworkが必要です
<ul>
<li>AudioToolbox</li>
<li>MessageUI</li>
<li>SystemConfiguration</li>
<li>CoreGraphics</li>
</ul>
実際にリンクしてみて追加しなくてはならなかったもの(使ってないと思うんだけど)
<ul>
<li>AdSupport</li>
<li>StoreKit</li>
</ul>
<h3>Liner flagを追加</h3>
これをやらないとコンパイルが通りません。
Targetの<b>”Other Liner Flags“</b>に, <b>-ObjC</b>を追加します。
<h3>AdmobのWebサイトでIDを発行してもらう</h3>
必要なものは, <b>パブリッシャー ID</b>です。アプリケーションを追加してこれを入手してください。
サイトURLなどは必要ないので空欄でいいのです。(登録に関しては省略)
(Googleアカウントもしくはadmobアカウントが必要です)
<h3>GADBannarViewの設置</h3>
さて今回は, サンプルに従ってボトムにバナーを表示させてみましょう。
<h5>必要なヘッダ</h5>
<pre class="brush: cpp;">
#import "GADBannerView.h"
</pre>
<h5>本体</h5>
<pre class="brush: cpp;">
@interface ViewController ()
@property(nonatomic, readwrite)GADBannerView *bannerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self initAd];
}
-(void)initAd {
self.bannerView = [[GADBannerView alloc]
initWithFrame:CGRectMake(0.0,
self.view.frame.size.height -
GAD_SIZE_320x50.height,
GAD_SIZE_320x50.width,
GAD_SIZE_320x50.height)];
self.bannerView.adUnitID = kADUNITID;
self.bannerView.rootViewController = self;
[self.view addSubview:self.bannerView];
}
</pre>
kADUNITIDというのがパブリッシャーID(文字列です)です。これだけで表示されます。
<h3>GADBannerViewDelegateの実装</h3>
Admobは, web広告ですので, もちろんネットにつながっていないと,当然表示されません。
デザインなどで, 広告が表示されない場合にぽっかりスペースが空くのはなんかしのびありません。
そこで, 表示される場合と, 表示されない場合の挙動を変更するように直します。
<b>GADBannerViewDelegate</b> というのがこの機能を提供しています。
広告を表示するUIを使うクラスにGADBannerViewDelegateをimplementsします。
<h5>ViewController</h5>
<pre class="brush: cpp;">
@interface ViewController : UIViewController<GADBannerViewDelegate>
@end
</pre>
<h5>GADBannerView にdelegateをset</h5>
<pre class="brush: cpp;">
[self.bannerView setDelegate:self];
</pre>
self.bannerViewは,GADBannerViewです。
<h5>実装</h5>
<pre class="brush: cpp;">
- (void)adViewDidReceiveAd:(GADBannerView *)bannerView {
// Success
}
- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error {
// Fail
}
</pre>
成功した場合がadViewDidReceiveAd, 失敗したときがdidFailToReceiveAdWithErrorです。
筆者はここで, UIのサイズなどを調整したりしています。
atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0tag:blogger.com,1999:blog-5040468453311432717.post-66888647012981895152013-09-02T08:54:00.000+09:002013-09-02T08:54:12.151+09:00iOS 下へ引っ張って更新(UIRefreshControlを使う) Twitterなどでよく見られるUIで、新しいアイテムを手動で更新する場合にスクロールして一番上の位置より
下へ引っ張り新しいアイテムを上位にのせるというようなUIのことです。
iOS6より <b><span style="color: red;">UIRefreshControl</span></b>というのが登場した模様で簡単に実装できそうです。
<br />
<h3>特徴</h3>
<ul>
<li>storyboardなどUI側の操作は必要ない</li>
<li>下に引っ張って更新するタイプのみ, Gmailみたいに上に引っ張って古いアイテムを取得するタイプのものはサポートしていない</li>
</ul>
スクリーンショットがうまくとれませんでした。他にもいろいろな方がのせているので参考に。
<br />
<h3>Sample</h3>
UIはテーブルをひとつ用意したものだけです。それ以外は何もありません。
<br />
<br />
<h4>Header</h4>
<pre class="brush: cpp;">@interface ViewController : UIViewController
@property(nonatomic, strong)NSArray *items;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property(nonatomic) UIRefreshControl *refreshControl;
@end&nbsp
</pre>
UIRefreshControlが入りました。
<br />
<br />
<h4>Code</h4>
<pre class="brush: cpp;">@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.items = [[NSMutableArray alloc] init];
[self addData:30];
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(controlRefresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:self.refreshControl];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self loadItem];
}
-(void)addData:(int)number
{
NSMutableArray *tmps = [[NSMutableArray alloc] initWithArray:self.items];
NSUInteger current = [self.items count];
for ( int i=0; i < number; i++ )
{
[tmps addObject:[NSNumber numberWithInt:current+i]];
}
self.items =[NSArray arrayWithArray:tmps];
}
-(void)loadItem
{
self.items = [self.items sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj2 intValue] - [obj1 intValue];
}];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark -
#pragma mark UIRefreshControl
-(void)controlRefresh:(UIRefreshControl *)sender
{
[self addData:20];
[self loadItem];
// Update
[self.refreshControl endRefreshing];
}
#pragma mark -
#pragma mark UITableViewDataSource, UITableViewDelegate
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell"];
if ( cell == nil )
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
}
cell.textLabel.text = [NSString stringWithFormat:@"%d", [[self.items objectAtIndex:indexPath.row] intValue]];
return cell;
}
@end
</pre>
テーブルに数字を突っ込んでいます。逆順に並べ替えるコードを入れています。
-(void)controlRefresh:(UIRefreshControl *)senderというのが特徴です。これを実装しますselectorで指定したメソッドなので名前はどうとでもなります。
ここで更新が発生した場合の処理を書きます。
更新終了後endRefreshing を呼び出します。それで終了です。
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDwgO5YxEIVEOPpLOyHAg2lsVaJUjcEYoIwHO-iHB1kQ6sIjuQ9sd3GfHy8akmBVi5JeFgGrZqdqqU8o-XyyrIaVekzAGU3PRkBsJiiDd4Q_E3Lj3IxnYtNzHo4zCI7MpY6pf9yAvgNLY/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2013-09-01+11.15.01.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDwgO5YxEIVEOPpLOyHAg2lsVaJUjcEYoIwHO-iHB1kQ6sIjuQ9sd3GfHy8akmBVi5JeFgGrZqdqqU8o-XyyrIaVekzAGU3PRkBsJiiDd4Q_E3Lj3IxnYtNzHo4zCI7MpY6pf9yAvgNLY/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2013-09-01+11.15.01.png" /></a></div>atmarkplanthttp://www.blogger.com/profile/00027238148144670768noreply@blogger.com0