ぎじゅつめもブログ

主にアプリ開発の技術メモを残していきます。

【Objective-C】UICollectionViewでのページスクロール

UICollectionViewをページスクロールにしたいときのメモです。
(環境:xcode5.1.1, iOS 7.1)

pagingEnabled = YES とすればできますが、各セルの間に間隔をいれたいときは、これだと表示位置がずれていってしまいます。
そこで、UICollectionViewFlowLayout (UICollectionViewのレイアウトをカスタマイズできるクラス) を継承したクラスを作成し、セルが画面の中央に常にくるように処理を書きます。

targetContentOffsetForProposedContentOffset::メソッドをオーバーライドします。
下記の例は、1列のシンプルなコレクションビューで、セルの真ん中(x軸)が画面の真ん中にくるように設定しています。

/*HogeCollectionViewFlowLayout.m*/

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    CGFloat offsetAdjustment = MAXFLOAT;
    CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
    
    CGRect targetRect = CGRectMake(proposedContentOffset.x, 
                                                        0.0, 
                                                        self.collectionView.bounds.size.width, 
                                                        self.collectionView.bounds.size.height);
    
    NSArray* array = [self layoutAttributesForElementsInRect:targetRect];
    
    for (UICollectionViewLayoutAttributes* layoutAttributes in array) {
        if (layoutAttributes.representedElementCategory != UICollectionElementCategoryCell)
            continue;
    
        CGFloat itemHorizontalCenter = layoutAttributes.center.x;
        offsetAdjustment = itemHorizontalCenter - horizontalCenter;
        layoutAttributes.alpha = 0;
        
        if (velocity.x < 0) break;
    }
    
    return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}

また、ページングの動作に近くするため、UICollectionViewクラスでスクロールの速度を速くします。

/*HogeCollectionViewController.m*/
//viewDidLoadなどで...
self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;

以上です。

※参考
http://stackoverflow.com/questions/13492037/targetcontentoffsetforproposedcontentoffsetwithscrollingvelocity-without-subcla

【Android】画像をローディング(ぐるぐる)にする

ローディングを使う際、AndroidではProgressBarのスタイルを progressBarStyle に設定することでデフォルトのぐるぐる部品が表示されます。そのぐるぐる画像を変えたいときのメモです。

(環境:android 4.2, 確認端末:F-03F)

こちらのサイトをそのまま使えばOKでした。

まず、custom_progress_background.xml (名前は何でもいいです)を用意し、drawableフォルダに格納します。
上記サイトの例では2つの画像を用意してそれらが逆向きに回るローディングを作成しています。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <rotate
            android:drawable="@drawable/spinner_outer"
            android:pivotX="50%"
            android:pivotY="50%"
            android:fromDegrees="0"
            android:toDegrees="1080" />
    </item>
    <item>
        <rotate
            android:drawable="@drawable/spinner_inner"
            android:pivotX="50%"
            android:pivotY="50%"
            android:fromDegrees="720"
            android:toDegrees="0" />
    </item>
</layer-list>

画像をdrawableフォルダにいれます。

  • spinner_inner.png
  • spinner_outer.png


あとは呼び出したいアクティビティで作成したプログレスバーを指定します。

<ProgressBar
    android:id="@+id/custom_progress"
    android:indeterminate="true"
    style="?android:attr/progressBarStyleLarge"
    android:layout_width="210dip"
    android:layout_height="210dip"
    android:layout_gravity="center"
    android:indeterminateDrawable="@drawable/custom_progress_background"
    /> 
  • イメージ

f:id:tsuyushiga:20140607145242p:plain


以上です。


※参考
http://pankajchunchun.wordpress.com/2011/09/10/customization-of-spinner-progress/

【cocos2d-x】picojsonでJSONファイルを読み込む

cocos2d-x 3.x になってから2.x系から大幅に変更があって戸惑いました。(クラス名の頭からCCがとれたからそう感じるだけでしょうか。)

ローカルのjsonを読み込むメモです。cocos2d-xプロジェクトへ追加したjsonを使用します。
picojsonというjsonをパースしてくれる便利なライブラリを使います。
(環境:xcode5.1, cocos2d-x-3.1rc0)

{
    "hoge" :[
                {"x":1, "y":1, "z":1}, 
                {"x":2, "y":1, "z":1}
                ]
}
//ファイルパスの取得
FileUtils* fileUtils = FileUtils::getInstance();
const char* path = "hogehoge.json";
std::string fullpath = fileUtils->fullPathForFilename(path);

// ファイルオープン
ifstream inputStream;
string thisLine;
inputStream.open(fullpath.c_str());
if (!inputStream.is_open())
{
    cerr << "cannot open file!" << endl;
    exit(1);
}

stringstream sstream;
while (getline(inputStream, thisLine))
{
    sstream << thisLine;
}
inputStream.close();
cout << "finish opening file!" << endl;
    
CCLOG("sstream:%s", sstream.str().c_str());
    
// JSONのパース
picojson::value v; 
picojson::parse(v, sstream);

picojson::object& all = v.get<picojson::object>();
picojson::array& array = all["hoge"].get<picojson::array>();
for (picojson::array::iterator it = array.begin(); it != array.end(); it++)
{
    picojson::object& tmpObject = it->get<picojson::object>();
    int x = (int)tmpObject["x"].get<double>();
    int y = (int)tmpObject["y"].get<double>();
    int z = (int)tmpObject["z"].get<double>();
    CCLOG("x:%d, y:%d, z:%d", x, y, z);
}

以上です。

参考
http://taichino.com/programming/2068
http://nirasan.hatenablog.com/entry/2013/10/24/232905
http://stackoverflow.com/questions/12171445/file-i-o-using-cocos2d-x

【cocos2d-x】CCScrollViewでページスクロール

スクロールビューを使って、ページ切り替えをしたいときのメモです。
(環境:xcode5.1, cocos2d-x 2.2.2)

iPhone開発ではUIScrollViewにpagingEnabledというプロパティがあり、これをオンにするだけで実装可能ですが、cocos2d-xでは同じようなプロパティはないみたいです。

なのでCCScrollViewを少しいじってページ切り替えを出来るようにします。

まずヘッダーを編集します。以下の宣言を追加します。

/**
 * CCScrollView.h
 */
//...略
public:
    bool isPagingEnabled() { return _isPagingEnabled; }
    void setEnablePaging(bool isPagingEnabled) { _isPagingEnabled = isPagingEnabled; }
    void setPageSize(CCSize _pageSize){ pageSize = _pageSize; }
    CCSize getPageSize(){ return pageSize; }
protected:
    bool _isPagingEnabled;
    CCSize pageSize;
//略...

次にrelocateContatiner(bool animated)というメソッドに以下のように追記します。

/**
 * CCScrollView.cpp
 */
void CCScrollView::relocateContainer(bool animated)
{
//...略

    newX     = oldPoint.x;
    newY     = oldPoint.y;
    
    // ここから
    {
        if (_isPagingEnabled) {
            int horizontalPageNum = roundf(newX / pageSize.width);
            int verticalPageNum = roundf(newY / pageSize.height);
            
            if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionHorizontal){
                newX = horizontalPageNum * pageSize.width;
            }
            
            if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionVertical){
                newY = verticalPageNum * pageSize.height;
            }
        }
    }
    // ここまで追記
    
    if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionHorizontal)
    {
        newX     = MAX(newX, min.x);
        newX     = MIN(newX, max.x);
    }
//略...
}

実際にスクロールビューを使うときには以下のようにして、ページ切替機能をオンにしてあげます。

    CCScrollView *scrollView = CCScrollView::create(CCSize(visibleSize.width, visibleSize.height));
    // スクロールビューをお好みで設定...
    scrollView->setEnablePaging(true);

以上です。

※参考
http://blackxxxwhite.blogspot.jp/2013/07/cocos2d-x-ccscrollview-semi-paging.html




  • 2014/8/9追記

cocos2d-x-3.x以降の場合は下記のようにします。

/**
 * CCScrollView.h
 */
//...略
public:
    bool isPagingEnabled() { return _isPagingEnabled; }
    void setEnablePaging(bool isPagingEnabled) { _isPagingEnabled = isPagingEnabled; }
    void setPageSize(Size _pageSize){ pageSize = _pageSize; }
    Size getPageSize(){ return pageSize; }
protected:
    bool _isPagingEnabled;
    Size pageSize;
//...略
/**
 * CCScrollView.cpp
 */
void CCScrollView::relocateContainer(bool animated)
{
    //...略

    newX     = oldPoint.x;
    newY     = oldPoint.y;

    // ここから
    {
        if (_isPagingEnabled) {
            int horizontalPageNum = roundf(newX / pageSize.width);
            int verticalPageNum = roundf(newY / pageSize.height);
            
            if (_direction == Direction::BOTH || _direction == Direction::HORIZONTAL){
                newX = horizontalPageNum * pageSize.width;
            }
            
            if (_direction == Direction::BOTH || _direction == Direction::VERTICAL){
                newY = verticalPageNum * pageSize.height;
            }
        }
    }
    // ここまで追記
    
    if (_direction == Direction::BOTH || _direction == Direction::HORIZONTAL)
    {
        newX     = MAX(newX, min.x);
        newX     = MIN(newX, max.x);
    }

    //略...
}

【Android】ExpandableListViewの文字色を変える

これからはAndroidの開発メモも残していきます(次の案件がAndroidなので。。)

AndroidSDK付属のレイアウト(android.R.layout.simple_expandable_list_item_1, android.R.layout.simple_expandable_list_item_2)を使ってExpandableListViewをつくる際に、文字色を変える方法です。
(環境:Android4.2.2)

SimpleExpandableListAdapterをオーバーライドします。

※ExpandableListViewの基本的な使い方は、ここを参考にしました。

SimpleExpandableListAdapter adapter = new SimpleExpandableListAdapter(
    getApplicationContext(),
    groupData,
    android.R.layout.simple_expandable_list_item_1,
    new String[] {"group"},
    new int[] {android.R.id.text1},
    childData,
    android.R.layout.simple_expandable_list_item_2,
    new String[] {"name", "group"},
    new int[] {android.R.id.text1, android.R.id.text2}) {
        @Override
	public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
	    final View itemRenderer = super.getChildView(groupPosition, childPosition, isLastChild, convertView, parent);
            final TextView tv1 = (TextView)itemRenderer.findViewById(android.R.id.text1);
            final TextView tv2 = (TextView)itemRenderer.findViewById(android.R.id.text2);
            tv1.setTextColor(0xff000000); // 子リストのタイトルは黒に設定
            tv2.setTextColor(0xff0000ff); // 子リストのサブタイトルは青に設定
            return itemRenderer;
        }
	@Override
	public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
	    final View itemRenderer = super.getGroupView(groupPosition, isExpanded, convertView, parent);
            final TextView tv1 = (TextView)itemRenderer.findViewById(android.R.id.text1);
            tv1.setTextColor(0xff0000ff); // 親リストのタイトルは青に設定
            return itemRenderer;				
	}
};
		
ExpandableListView listView = (ExpandableListView)findViewById(R.id.hogelistView);
listView.setAdapter(adapter);

以上です。

※参考
http://stackoverflow.com/questions/16588659/android-changing-color-of-textview-in-simpleexpandablelistadapter

【Objective-C】SDWebImageで画像サイズを変える

SDWebImageはFacebookアプリにも使われていると言われている、ものすご便利〜なライブラリです。

  • SDWebImage

 https://github.com/rs/SDWebImage

サーバーからイメージを取得する前に、アプリ側で画像サイズ変えられないですか?みたいな事態が生じたので、めんどくさいな〜と思いつつ、方法を探してみました。デリゲートメソッドを使用する方法です。

まずヘッダファイルではSDWebImageManagerDelegateプロトコルを採用します。

// HogeFugaViewController.h
@interface HogeFugaViewController : UITableViewController <SDWebImageManagerDelegate>

どこかでデリゲートを設定します。

// HogeFugaViewController.m
// webイメージ読み込みデリゲート設定
SDWebImageManager.sharedManager.delegate = self;

デリゲートメソッドを実装します。

// HogeFugaViewController.m
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL
{
    /*イメージをリサイズする*/
    CGSize resizedImageSize = CGSizeMake(50, 50);
    UIGraphicsBeginImageContextWithOptions(resizedImageSize, NO, 0.0);
    [image drawInRect:CGRectMake(0, 0, resizedImageSize.width, resizedImageSize.height)];
    UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    image = resizedImage;
    return image;
}

上記のように実装するとSDWebImageライブラリを使って画像を設定する際、画像がキャッシュに保存される前にイメージをリサイズしてくれます。

NSURL* url = /*イメージのURL*/;
UIImage* placeholder = /*プレースホルダイメージ*/;
[cell.hogeImage setImageWithURL:url placeholderImage:placeholder];

しかし、上記の対応だけではSDWebImageでキャッシュするイメージがスケール1.0で保存されてしまっていました。。
SDWebImageライブラリだとURLに @2x という名前がないとUIImageのスケールが 1.0 で保存されるっぽいです。(ちゃんとは確かめてません)
スクリーンのスケールからRetinaディスプレイかどうか判断して,Retinaディスプレイの場合はUIImageのスケールを2倍に設定し直すようにライブラリを修正しました。(微妙な対応ですね。。もっといい方法はないでしょうか)

// UIImageView+WebCache.m
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock {
/*略*/
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image) {       //ここから
                    if ([UIScreen mainScreen].scale == 2.0)
                    {
                        wself.image = [UIImage imageWithCGImage:image.CGImage scale:2.0f orientation:UIImageOrientationUp];
                    }
                    else
                    {                  //ここまで追記
                        wself.image = image;
                    }
                    [wself setNeedsLayout];
                }
/*略*/
}

以上です。



※補足

上記の例ではイメージのサイズを幅50、高さ50で固定でしたので、以下のライブラリを使えば出来そうです。
https://github.com/toptierlabs/ImageCacheResize

ただし、動的にサイズを変えたいときはデリゲートを使うのが良さそうです。

■参考ページ
https://github.com/rs/SDWebImage
http://hackemist.com/SDWebImage/doc/Protocols/SDWebImageManagerDelegate.html#//api/name/imageManager:transformDownloadedImage:withURL:
http://stackoverflow.com/questions/12928103/sdwebimage-process-images-before-caching

【Objective-C】iOSアプリ iPhone iPad 縦横レイアウト対応

iPhoneの3.5inch, 4inch, iPadそれぞれ縦横対応する方法です。
いろいろなやり方があると思いますが、ここで紹介するやり方は、縦横それぞれレイアウトが違う場合に有効です。
もちろん、Auto Layoutだけで大丈夫ならここでの方法は使わない方がいいと思います。
(環境:xcode5.1, iOS 7.0)


■全体の流れ

  1. iPhone3.5inch、iPhone4inch、iPadそれぞれ縦・横のxibファイルを用意する
  2. 各xibファイルにUIViewを配置する
  3. UIViewControllerで作成したxibファイルからUIViewを呼び出して表示させる


1. iPhone3.5inch、iPhone4inch、iPadそれぞれ縦・横のxibファイルを用意する

例えば、iPhonePortrait.xib, iPhoneLandscape.xib, iPadPortrait.xib, iPadLandscape.xibというファイルを用意します。

2. 各xibファイルにUIViewを配置する

用意した各xibファイルにUIViewを配置していきます。
f:id:tsuyushiga:20140416233217p:plain

3. UIViewControllerで作成したxibファイルからUIViewを呼び出して表示させる

xibファイルからUIViewオブジェクトを呼び出します。以下のようなメソッドを用意してあげて、

- (NSArray *)viewLayoutFromOrientation {
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    NSArray *topLevelObjects;
    //Landscape(横レイアウトの場合)
    if(UIInterfaceOrientationIsLandscape(orientation)) {
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
            // iPhone4inchの場合
            if ([[UIScreen mainScreen] bounds].size.height == 568) {
                topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"iPhoneLandscape" owner:self options:nil];
            // iPhone3.5inchの場合
            } else {
                topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"iPhone3_5Landscape" owner:self options:nil];
            }
        // iPadの場合
        } else {
            topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"iPadLandscape" owner:self options:nil];
        }
    //Portrait(縦レイアウトの場合)
    } else {
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
            if ([[UIScreen mainScreen] bounds].size.height == 568) {
                topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"iPhonePortrait" owner:self options:nil];
            } else {
                topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"iPhone3_5Portrait" owner:self options:nil];
            }
        } else {
            topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"iPadPortrait" owner:self options:nil];
        }
    }
    return topLevelObjects;
}

ビューの設定をするときにこのメソッドを呼びます。

// xibファイルの上から何番目に配置したUIViewか。
// 0 であれば一番最初のUIViewを示す。(2. の図の選択されて青くなっているUIView)
int viewIndex = 0; 

// UIViewをxibから呼び出して設定
UIView* sampleView = [[self viewLayoutFromOrientation] objectAtIndex:viewIndex];
[self.view addSubview:sampleView];





※補足
気をつけなければならないことは、画面回転したときに都度xibファイルから呼び出して再設定しないといけないことです。

// これか
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [self setUpViewForOrientation];
}

// これのどちらかで、
// xibファイル読込処理を呼び出して上げると画面回転したときにきれいに縦横レイアウトが切り替わる。
- (void)viewWillLayoutSubviews{
    [self setUpViewForOrientation];
}

以上です。