*

iOSプログラミングのキモ(2:AppDelegate説明 )

公開日: : 最終更新日:2014/01/16 Apple, iOS, iPad, iPhone, XCode

実際の AppDelegate.h、AppDelegate.m のソースコードです

AppDelegate.h

#import <UIKit/UIKit.h>

// ヘッダーファイルの中で 使われるクラスを宣言
@class CountrySetting;
@class VideoDownloadManager;
@class QTubeViewController;

@interface AppDelegate : UIResponder
{ //{ } の中ではクラス内部で使うクラス変数を定義する、クラス変数であることをわかりやすくするため
  // 変数名のアタマもしくはオシリに "_" (アンダースコア) を付ける

 NSMutableArray *countrySettingList_; // 国別コードを格納する配列

 VideoDownloadManager *videoDownloadManager_; // 動画ダウンロードを管理するクラス

 UIStoryboard *storyboard_; // ストーリーボード管理クラス
 QTubeViewController *qTubeViewController_; // キャッシュした動画一覧画面のビューコントローラ
}

// 外部クラスから アクセス可能なクラス変数
@property (nonatomic,retain) VideoDownloadManager *videoDownloadManager;
@property (nonatomic,retain) NSMutableArray *countrySettingList;

@property (strong, nonatomic) UIWindow *window;
@property (assign, nonatomic) BOOL isRetina4; // iPhone4S以前かiPhone5以降かを保持する変数

// Static関数 クラスをインスタンス化しなくても 使える
// 数字を3桁毎にカンマを挟んだ文字列にして表示するメソッド
+ (NSString *)createStringAddedCommaFromInt:(int)number;

-(NSString *)getLocaleCountryCodeFromNSUserDefaults;
-(void)setLocaleCountryCodeFromNSUserDefaults:(NSString*)localeCountryCode;
-(NSString*)getLocaleCountryCode;
-(NSString*)setLocaleCountryCode:(NSString*)localeCountryCode;
-(NSString*)setDefaultLocaleCountryCode;
-(NSMutableArray *)loadCountrySettingList;
+(void)iRateDisplay;

@end

AppDelegate.m
コメントに説明をいれてあります。

#import "AppDelegate.h"
#import "CountrySetting.h"

#import "VideoDownloadManager.h"
#import "VideoDownloader.h"

#import "RightDemoViewController.h"
#import "LeftDemoViewController.h"
#import "CountrySettingViewController.h"
#import "SearchHistoryViewController.h"
#import "QTubeViewController.h"
#import "MainViewController.h"
#import "SlidingViewController1.h"
#import "SettingListViewController.h"
#import "MyUINavigationController.h"

#import <AVFoundation/AVAudioPlayer.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVAudioSession.h>

/*
 exceptionHandler関数を AppDelegate に記述しておくと
 アプリが不正終了した場合に呼ばれるため 書いといた方が良いです。
*/
void exceptionHandler(NSException *exception)
{
    /*
     LOG関数は コンソールログを出力する関数 DEBUG時にとても有効です。
     別エントリーで解説します。
    */
    // 不正終了した箇所を示す スタックトレースをコンソールログに出力する
    LOG(@"stackSymbols:%@", [exception callStackSymbols]);
}


@implementation AppDelegate

// ローカルクラス変数を外部参照可能にする
@synthesize countrySettingList = countrySettingList_;
@synthesize videoDownloadManager = videoDownloadManager_;

/*
 NSUserDefaultsを使って保存しておいた 国コードを取り出す、なかった場合はnilが返る
*/
-(NSString *)getLocaleCountryCodeFromNSUserDefaults
{
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    NSString *s = [ud stringForKey:LocaleCountryCode];
    return s;
}

/*
 NSUserDefaultsに国コードを保存する
*/
-(void)setLocaleCountryCodeFromNSUserDefaults:(NSString*)localeCountryCode
{
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setObject:localeCountryCode forKey:LocaleCountryCode];
    [ud synchronize];
}

/*
 国コードを取り出す
*/
-(NSString*)getLocaleCountryCode
{
    // ユーザーデフォルトから取り出す
    NSString *localeCountryCode = [self getLocaleCountryCodeFromNSUserDefaults];
    if(localeCountryCode)
        return localeCountryCode;

    // ユーザーデフォルトになかった場合、システム設定値を使う
    localeCountryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];

    return [self setLocaleCountryCode:localeCountryCode];
}

/*
 ユーザーがiPhoneに設定した国コードを取り出して 標準国コードとして保存する
*/
-(NSString*)setDefaultLocaleCountryCode
{
    NSString *localeCountryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];

    return [self setLocaleCountryCode:localeCountryCode];
}

/*
 CountrySettingデータベースに 国コードをセットする 但し YouTubeがサービスを展開していない国の場合は 強制的にUS(アメリカ)にセットされる
*/
-(NSString*)setLocaleCountryCode:(NSString*)localeCountryCode
{
    //DBにあるか検索
    CountrySetting *countrySetting = [CountrySetting MR_findFirstByAttribute:@"countryCode" withValue:localeCountryCode];
    if(countrySetting){
        //CountrySetting DBにあれば、それを使う
        [self setLocaleCountryCodeFromNSUserDefaults:localeCountryCode];
        countrySetting.isSelectedValue = YES;
    }else{
        // CountrySetting DBになかった場合、デフォルト値としてUSに設定
        localeCountryCode = @"US";//[[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
        countrySetting = [CountrySetting MR_findFirstByAttribute:@"countryCode" withValue:localeCountryCode];
        [self setLocaleCountryCodeFromNSUserDefaults:localeCountryCode];
        countrySetting.isSelectedValue = YES;
    }

    NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
    [context MR_saveOnlySelfAndWait];

    [self setLocaleCountryCodeFromNSUserDefaults:localeCountryCode];

    return localeCountryCode;
}

/*
 数字を3桁毎にカンマを入れた文字列として返す
 参考URL http://qiita.com/exilias/items/bcbf01eee9f122b74dc1
 
 このメソッドはスタティックメソッドになっています
 AppDelegateのインスタンスを取り出さなくても呼び出せる
 ようになっています。

 メソッドを定義する際、汎用性があるメソッドの場合は スタティックメソッドでの
 実装を検討すると良いでしょう。
*/
+ (NSString *)createStringAddedCommaFromInt:(int)number
{
    NSNumberFormatter *format = [[NSNumberFormatter alloc] init];
    [format setNumberStyle:NSNumberFormatterDecimalStyle];
    [format setGroupingSeparator:@","];
    [format setGroupingSize:3];

    return [format stringForObjectValue:[NSNumber numberWithInt:number]];
}

/*
 アプリ起動時 最初に呼ばれるメソッド このメソッドの中で アプリの全体の初期化を行う
 メソッドが長いので 適宜 別メソッドに分割したり してあります。
*/
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // アプリが異常終了した時に 呼ばれる関数を登録する 最初にやっておく
    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // Set AudioSession
    NSError *sessionError = nil;
    [[AVAudioSession sharedInstance] setDelegate:self];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&sessionError];

    // NSNotificationを登録する
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(regionChanged:) name:NOTIFICATION_COUNTRY_SELECTED object:nil];

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(endVideoDownload:) name:VIDEO_DOWNLOAD_END object:nil];

    // CoreDataを手軽に使うライブラリ MagicalRecordの初期化
    [MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"QTube.sqlite"];

    // 国別コードの読み込み
    [self loadCountrySettingList];

    // 動画ダウンロード管理クラスの初期化
    videoDownloadManager_ = [VideoDownloadManager instance];


    // 画面の初期化
    // デバイスの画面サイズを取り出す
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    LOG(@"self.window.frame = %@",NSStringFromCGRect(self.window.frame));
    if(self.window.frame.size.height > 480)
        // 画面縦サイズが480ピクセルより大きい場合は iPhone5以降のモデルと判断する
        self.isRetina4 = YES;
    else
        // 画面縦サイズが480ピクセル以下の場合は iPhone4S以前のモデルと判断する
        self.isRetina4 = NO;

    LOG(@"self.isRetina4 = %@", self.isRetina4 ? @"YES":@"NO");

    // 画面サイズに合わせて 使用するストーリーボードを選択する
    storyboard_ = nil;
    if (self.isRetina4) {
        storyboard_ = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:[NSBundle mainBundle]];
    }else{
        storyboard_ = [UIStoryboard storyboardWithName:@"MainStoryboard_retina3" bundle:[NSBundle mainBundle]];
    }

    // ストーリーボードに登録されている画面の中で 初期画面に設定される画面情報を取り出し
    // rootViewControllerとして設定する
    self.window.rootViewController = [storyboard_ instantiateInitialViewController];

    // ストーリーボードの定義から rootViewController は UITabBarController なので
    // UITabBarControllerの変数を取り出し、タブに設定された 他の画面の配列(NSArray)を取り出す
    UITabBarController *tabbarController = (UITabBarController*)self.window.rootViewController;
    NSArray *controllers = tabbarController.viewControllers;

    // 画面を横にスライドさせて 右端(もしくは左端)に設定画面等を表示させる PKRevealController
    // https://github.com/pkluz/PKRevealController
    // を用意する

    // LeftDemoViewControllerは YouTubeのフィード・カテゴリを設定する画面
    UIViewController *leftViewController = [[LeftDemoViewController alloc] init];

    // CountrySettingViewController は YouTubeの国の設定を行う画面
    CountrySettingViewController *countrySettingViewController = [[CountrySettingViewController alloc]initWithNibName:@"CountrySettingViewController" bundle:[NSBundle mainBundle]];

    // PKRevealController に 左右の画面 中央の画面を指定して インスタンス化
    PKRevealController *pKRevealController1 = [PKRevealController revealControllerWithFrontViewController:[controllers objectAtIndex:0] leftViewController:leftViewController rightViewController:countrySettingViewController options:nil];

    // 検索用画面の定義 検索履歴画面のインスタンス化
    SearchHistoryViewController *searchHistoryViewController = [[SearchHistoryViewController alloc]initWithNibName:@"SearchHistoryViewController" bundle:[NSBundle mainBundle]];

    // 検索画面も PKRevealController を使う
    PKRevealController *pKRevealController2 = [PKRevealController revealControllerWithFrontViewController:[controllers objectAtIndex:1]
                                                                                       leftViewController:searchHistoryViewController
                                                                                      rightViewController:nil
                                                                                                  options:nil];
    // TabBar に並べる順番を再定義する
    NSArray *reorderedControllers = [[NSArray alloc]initWithObjects:pKRevealController1, pKRevealController2, [controllers objectAtIndex:2], [controllers objectAtIndex:3], nil];
    pKRevealController1.tabBarItem.title = @"YouTube";
    pKRevealController2.tabBarItem.title = @"Search";

    // タブに表示するタイトルの設定
    QTubeViewController *qTubeViewController = [controllers objectAtIndex:2];
    qTubeViewController.tabBarItem.title = @"QTube";
    qTubeViewController.navigationItem.title = @"QTube";

    SettingListViewController *settingListViewController = [controllers objectAtIndex:3];
    settingListViewController.tabBarItem.title = @"Setting";
    settingListViewController.navigationItem.title = @"Setting";

    // タブに表示するアイコンの設定
    pKRevealController1.tabBarItem.image = [UIImage imageNamed:@"chart_bar_up.png"];
    pKRevealController2.tabBarItem.image = [UIImage imageNamed:@"magnifier.png"];

    settingListViewController.tabBarItem.image = [UIImage imageNamed:@"gear.png"];

    UINavigationController *qvc = [controllers objectAtIndex:2];
    qvc.tabBarItem.image = [UIImage imageNamed:@"QTubeIconForTabbar.png"];
    qTubeViewController_ = (QTubeViewController*)[qvc topViewController];

    [tabbarController setViewControllers:reorderedControllers];


    // SDSegmentView の見た目の設定
    SDSegmentView *segmenteViewAppearance = SDSegmentView.appearance;
    [segmenteViewAppearance setTitleColor:UIColor.whiteColor forState:UIControlStateSelected];


    // 画面を表示
    [self.window makeKeyAndVisible];

    // iTunesに評価を書いてもらうよう促す、iRateライブラリの初期化
    [self iRateInit];

    // 初期化が終わったら YES を返して終了
    return YES;
}

/*
  アプリがバックグラウンドから回復した場合に呼ばれるメソッド 今のところ何もしない
*/
- (void)applicationWillResignActive:(UIApplication *)application
{
    LOG(@"application.description=%@",application.description);
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

/*
  アプリがバックグラウンドに回った場合に呼ばれるメソッド
*/
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    LOG(@"application.description=%@",application.description);
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    @try {
        // NSNotificationCenter を使って 他の関連インスタンスに バックグラウンドに周ったことを通知
        [[NSNotificationCenter defaultCenter]postNotificationName:APPLICATION_DID_ENTER_BACKGROUND_NOTIFICATION object:self];
    }
    @catch (NSException *exception) {
        LOG(@"%@", [exception debugDescription]);
    }
    @finally {
    }
}

/*
  アプリがバックグラウンドから回復する直前に呼ばれるメソッド 今のところ何もしない
*/
- (void)applicationWillEnterForeground:(UIApplication *)application
{
    LOG(@"application.description=%@",application.description);
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

/*
  アプリがバックグラウンドから回復が完了した後に呼ばれるメソッド 今のところ何もしない
*/
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    LOG(@"application.description=%@",application.description);
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    @try {
        [[NSNotificationCenter defaultCenter]postNotificationName:APPLICATION_DID_BECOME_ACTIVE_NOTIFICATION object:nil];
    }
    @catch (NSException *exception) {
        LOG(@"%@", [exception debugDescription]);
    }
    @finally {
    }

}

/*
  アプリが終了する時に呼ばれるメソッド 今のところ何もしない
*/
- (void)applicationWillTerminate:(UIApplication *)application
{
    LOG(@"application.description=%@",application.description);
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

#pragma mark - regionChanged

-(IBAction)regionChanged:(id)sender
{
    NSNotification *notification = (NSNotification*)sender;
    CountrySetting *countrySetting = (CountrySetting*)notification.object;
    [self setLocaleCountryCode:countrySetting.countryCode];
}

#pragma mark - NSNotification receivers

/*
  動画のキャッシュが完了した際に呼ばれるメソッド
*/
-(IBAction)endVideoDownload:(id)sender
{
    NSNotification *notification = (NSNotification*)sender;
    VideoDownloader *videoDownloader = (VideoDownloader*)notification.object;

    UITabBarController *tabbarController = (UITabBarController*)self.window.rootViewController;
    tabbarController.selectedIndex = 2; // 2 -> QTubeViewController

    // キャッシュが完了すると 即座に再生を開始する画面を呼び出すようにする
    if(qTubeViewController_){
        [qTubeViewController_ pushAVPlayer:videoDownloader.qTubeVideo];
    }else
        LOG(@"qTubeViewController_ is nil");
}

#pragma mark - Application's documents directory

/**
 Returns the path to the application's documents directory.
 iOSの場合 アプリ毎にデータを格納する場所が決められている 下記のメソッドでは そのディレクトリPATHを取り出す
 下の例では 一時的なファイルを置くディレクトリPATHを取り出す
 */
- (NSString *)applicationDocumentsDirectory {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
    return basePath;
}

#pragma mark - CoreData methods

/*
  国別コードを取り出す
*/
-(NSMutableArray *)loadCountrySettingList
{
    // CoreDataを使って SQLite DBに格納された 国別コードを配列に格納する
    NSMutableArray *mutableFetchResults = (NSMutableArray*)[CountrySetting MR_findAllSortedBy:@"countryName" ascending:YES];

    LOG(@"%@#loadCountrySettingList [mutableFetchResults count]=%d", [self class], [mutableFetchResults count]);
    countrySettingList_ = mutableFetchResults;

    // もし SQLite DBの中が空だった場合、DBに国別コードデータをインサートする
    // 以下の処理は アプリを初めて起動した時だけ 実行される
    if([countrySettingList_ count]==0){
        NSMutableArray *countryCodeArray =  [NSMutableArray arrayWithObjects:
                                             //@"ALL",
                                             @"AR",
                                             @"AU",
                                             //@"AT",
                                             @"BE",
                                             @"BR",
                                             @"CA",
                                             @"CL",
                                             @"CO",
                                             @"CZ",
                                             @"EG",
                                             @"FR",
                                             @"DE",
                                             @"GB",
                                             @"HK",
                                             @"HU",
                                             @"IN",
                                             @"IE",
                                             @"IL",
                                             @"IT",
                                             @"JP",
                                             @"ID",
                                             @"JO",
                                             @"MY",
                                             @"MX",
                                             @"MA",
                                             @"NL",
                                             @"NZ",
                                             @"PE",
                                             @"PH",
                                             @"PL",
                                             @"RU",
                                             @"SA",
                                             @"SG",
                                             @"ZA",
                                             @"KR",
                                             @"ES",
                                             @"SE",
                                             //@"CH",
                                             @"TW",
                                             @"AE",
                                             @"US",
                                             nil];

        NSMutableArray *countryNameArray =  [NSMutableArray arrayWithObjects:
                                             //@"ALL",
                                             @"Argentina",
                                             @"Australia",
                                             //@"Austria",
                                             @"Belgium",
                                             @"Brazil",
                                             @"Canada",
                                             @"Chile",
                                             @"Colombia",
                                             @"Czech Republic",
                                             @"Egypt",
                                             @"France",
                                             @"Germany",
                                             @"Great Britain",
                                             @"Hong Kong",
                                             @"Hungary",
                                             @"India",
                                             @"Ireland",
                                             @"Israel",
                                             @"Italy",
                                             @"Japan",
                                             @"Country Region",
                                             @"Jordan",
                                             @"Malaysia",
                                             @"Mexico",
                                             @"Morocco",
                                             @"Netherlands",
                                             @"New Zealand",
                                             @"Peru",
                                             @"Philippines",
                                             @"Poland",
                                             @"Russia",
                                             @"Saudi Arabia",
                                             @"Singapore",
                                             @"South Africa",
                                             @"South Korea",
                                             @"Spain",
                                             @"Sweden",
                                             //@"Switzerland",
                                             @"Taiwan",
                                             @"United Arab Emirates",
                                             @"United States",
                                             nil];

        countrySettingList_ = [[NSMutableArray alloc]initWithCapacity:[countryNameArray count]];

        // SQLiteに国別コードをインサートする
        for(int i = 0; i< [countryCodeArray count]; i++){
            // 1つの国別コードを保持するエンティティクラスを生成する
            CountrySetting *countrySetting = [CountrySetting MR_createEntity];
            
            // エンティティクラス内の変数に値をセット
            countrySetting.countryCode = (NSString*)[countryCodeArray objectAtIndex:i];
            countrySetting.countryName = (NSString*)[countryNameArray objectAtIndex:i];
            countrySetting.flagImage = [countrySetting.countryCode stringByAppendingString:@".png"];
            countrySetting.isSelected = [[NSNumber alloc]initWithBool:NO];
            
            // 配列に格納
            [countrySettingList_ addObject:countrySetting];
        }

        // SQLite DBにまとめてインサート
        NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
        [context MR_saveOnlySelfAndWait];

        // デフォルトの国別コードをセットする
        [self setDefaultLocaleCountryCode];

        // 改めて 国別コード配列を取り出す
        countrySettingList_ = (NSMutableArray*)[CountrySetting MR_findAllSortedBy:@"countryName" ascending:YES];
    }

    return countrySettingList_;
}

#pragma mark - iRate methods

-(void)iRateInit
{
    [iRate sharedInstance].daysUntilPrompt = 2;
    [iRate sharedInstance].usesUntilPrompt = 5;
}

+(void)iRateDisplay
{
    if([[iRate sharedInstance]shouldPromptForRating]){
        [iRate sharedInstance].promptAtLaunch = NO;
        [[iRate sharedInstance]promptIfNetworkAvailable];
    }
}

@end

コメントにもある程度書いてますが、特に重要な箇所を 別途説明していきます。

関連記事

FileQ iOS版を開発しようと思った理由

私の会社では FileQというファイル転送サービスを 2008年3月末から始めています、かれこれ6年

記事を読む

iOSプログラミングのキモ(Delegate iOSプログラミングで避けて通れないしくみ)

Delegate(委任)の考え方を説明します。iOSのプログラミングでは このDelegateが頻繁

記事を読む

iOSプログラミングのキモ(行き当たりばったりなプログラミングでも、何とか形にするために守っていること その2)

先週に引き続き、今週も文字中心のエントリーです、今回は 下記3つのことを書いていきます。 M

記事を読む

iOSプログラミングのキモ(デバッグをやりやすくするための工夫:Debug.hのインクルード )

前回 紹介した Debug.h は、使う際 以下のようにソースファイル(ここでは拡張子が m のもの

記事を読む

iOSプログラミングのキモ(2:ソースコード概説 )XCode

iOSプログラミングでは Appleが提供している XCodeという開発ツールを使います。

記事を読む

iOSプログラミングのキモ(MainViewController説明 : dispatch_asyncとブロック構文を使った 今どきのお手軽マルチスレッドプログラミング)

TwitterやYouTubeといったサービスでは、コンテンツの見出しにサムネイルが付加されることが

記事を読む

FileQ iOS版 公開しました。

5/2にFileQ iOS版を無事、公開しました。Appleの審査もスンナリ通り ホットしています(

記事を読む

iOSプログラミングのキモ(AppDelegate説明 NSUserDefaultsに設定情報を格納する )

QTubeは、YouTubeを閲覧するときに 国別コードを設定しています。国別コードは iOSに設定

記事を読む

iOSプログラミングのキモ(行き当たりばったりなプログラミングでも、何とか形にするために守っていること その1)

FileQ iOS版がリリースされて、のんびりしたい気持ちもありますが、FileQ Android版

記事を読む

iOSプログラミングのキモ(iOS7から使えるようになったマルチタスク機能、NSURLSessionUploadTask の困った現象)

このブログでも度々書いてきたFileQ iOS版ですが、今月末に Appleに申請できそうな

記事を読む

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

FileQ Hosting 月額99円 容量1GB


サイト管理 Mezzanine
Django上で動くCMS Mezzanine 用のモジュールを作ってみる その1

Django上で動くCMS Mezzanine上で動く、モジュールを作

ホーム Mezzanine
Django上で動くCMS Mezzanine を インストールする MacOSX Yesemite 編

Mezzanineは Django WEBフレームワーク上で動くCMS

EclipseにGWT(Google Web Toolkit) Plugin for Eclipseを入れようとしてハマった

最近PHPでちょっとした業務システムを作りました。業務システムの特徴と

ブログを半年やった成果を Google Analytics から眺める

今年の1月からブログを書き始め、そろそろ半年が経とうとしています。

母校で特別 講義をやってきました。

少し 間が空いてしまいました(^_^;) ちょっと前になりますが

→もっと見る

mautic is open source marketing automation
PAGE TOP ↑