【2018年版】iOSアプリ新規で作る時にやること①
これ年に10回くらいはやるんだけど、どうにかならんのかなぁ。シェルスクリプト書けばいけそうだけど、Xcodeについてくのだるいし、労力と比較して迷う。
Carthage / RxSwift / Compass / XCGLogger / Reachability / Alamofire / R.swift / SwiftLint / Generamba / VIPER / fastlaneあたりが技術キーワードでしょうか。箇条書きでいきまっせ。
ぼくはもうCarthage対応してないやつは使わないので、Cocoapodsは出てきません。
①プロジェクト作成~設定・各種ライブラリ導入編です。
Apple Developerアカウントもらう
誰かにもらいましょう
App ID決める
誰かに決めてもらいましょう
PROJECT作る
作りましょう。
.gitignore投入
### https://raw.github.com/github/gitignore/160d27e2bebf784c4f4a1e070df057f3868b62bc/Objective-C.gitignore # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xcuserstate ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ *.generated.swift vendor/bundler
PROJECT→Info
iOS Deployment Target
誰かに決めてもらいましょう
Configurations
Debug→ProductionDebug
Release→ProductionRelease
に変更して
それぞれをDuplicateして
StagingDebugとStagingReleaseを作る。
Developが必要ならDevelopDebugとDevelopReleaseも。
でもDevelopはあんま作りたくない。なんでiOSエンジニアがローカルにバックエンドの開発環境作らなきゃいけないのかうんぬんかんぬん。
Productionも正直用意するか迷う。何人かで開発するときはいらないかも。
Schema
デフォルトでできてるやつにsuffix:_Productionをつける
Duplicateして_Stagingを作る
両方Sharedにする
_Stagingのほうを編集。Build ConfigurationをStagingDebugとStagingReleaseに変更。
PROJECT→Build Settings
Versioning
Current Project Version:1
Versioning System:Apple Generic
に変更。
Swift Compiler - Custom Flags→Other Swift Flags
ProductionDebug:-D DEBUG
StagingDebug: -D DEBUG -D STAGING
追加。
TARGETS→General
Identity→Version
Signing
Deployment Info
をいじる
Deployment Info→Main Interfaceはクリアして、Main.storyboardを削除。
TARGETS→Capabilities
必要なのをオン
TARGETS→Info
Bundle Name:$(BundleNamePrefix)アプリ名
TARGETS→Build Settings
User-DefinedにBundleNamePrefixとConfigurationNameを追加。Stagingだけ設定をお好みで追加。
Packaging→Product Bundle Identifier:[お好みで]$(ConfigurationName)
SwiftLint導入
インストール。グローバルにいれるのすきじゃないんだけど、Build Phaseで使いたいのでまぁしゃーない。もう、homebrewのインストールはいいっしょ。。このブログ内検索すればあるはず。
brew install swiftlint
TARGETS→Build PhaseでNew Run Script Phase
#!/bin/sh if which swiftlint >/dev/null; then swiftlint autocorrect swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi
で、プロジェクトルートに.swiftlint.ymlを用意。
included: - [Project Module Nameを] excluded: - Pods/ - Carthage/ - fastlane - [Project Module Nameを]/Application/R.generated.swift disabled_rules: - force_cast - force_try - force_unwrapping file_length: warning: 1000 error: 1500 type_body_length: warning: 500 error: 750 function_body_length: warning: 200 error: 300 line_length: warning: 200 error: 300 variable_name: min_length: 1
で、ビルドエラーになったら修正。
Carthage導入
インストール。
brew install carthage
プロジェクトルートにCartfileを作る。基本は以下。
バージョンは2018/06現在の最新。
# General github "ReactiveX/RxSwift" "4.2.0" github "hyperoslo/Compass" "6.0.0" # Resources github "mac-cain13/R.swift.Library" "v4.0.0" # Debug github "DaveWoodCom/XCGLogger" "6.0.4" # API github "ashleymills/Reachability.swift" "v4.1.0" github "Alamofire/Alamofire" "4.7.2"
作れたら
carthage update --cache-builds --platform iOS
Cartfileを変更したら毎回これやりましょう。初回はものすごい時間がかかりまっせ。
TARGETS→General→Linked Frameworks and Librariesで+→Add Other...で
Carthage/Build/iOSの下にできた.frameworkたちを追加。
TARGETS→Build PhaseでNew Run Script Phase
/usr/local/bin/carthage copy-frameworks
Input Filesにさっき入れたFrameworkを全て追加。。。
・7/1追記
github.com
公式の説明の通り、ドラッグ&ドロップだとなんかうまくいかなかった。
たぶんOutput Filesもちゃんと書いたほうがよい気がする
R.swift導入
github.com
ここから任意のバージョンのzipをダウンロードして解凍。
rswiftをプロジェクト内のお好みの場所に。
TARGETS→Build PhaseでNew Run Script Phase
これはComple Sourcesより前にやったほうがよい(上から順番に実行されるから上のほうにおくとよい)と思う。
"$PROJECT_DIR/rswiftのパス" generate "$PROJECT_DIR/R.generated.swiftを置きたいディレクトリのパス(.swiftlint.yml参照)"
ビルドするとR.generated.swiftができるので、プロジェクトに追加しておきましょう。
【Android】SimpleRecyclerViewAdapter
めずらしくあんどろいだーーーーーー
ねいてぃぶはらくちんでよいね!!!最高!ねむい!体調悪い!つらい!でも納期も時間も待ってくれない!
はい、RecyclerViewAdapterね。簡単なやつならこうしましょう。
app/build.gradle
抜粋です。databindingも使うのでね。
apply plugin: 'kotlin-kapt' android { dataBinding { enabled = true } } dependencies { ext.android_support_version = "27.1.1" implementation "com.android.support:recyclerview-v7:$android_support_version" }
SimpleRecyclerViewAdapter.kt
おもむろに以下をじっそうしましょう。
package hoge; import android.content.Context import android.support.v7.widget.RecyclerView import android.view.View import android.view.ViewGroup import java.lang.reflect.ParameterizedType abstract class SimpleRecyclerViewAdapter<V : View, I : Any?>(defaultItems: ArrayList<I>? = null) : RecyclerView.Adapter<SimpleRecyclerViewAdapter<V, I>.ItemViewHolder>() { protected val isEmpty get() = !items.any() private val items: ArrayList<I> = arrayListOf() init { defaultItems?.let { items.addAll(it) } } fun removeAll() { items.clear() notifyDataSetChanged() } fun addItem(item: I) { items.add(item) notifyItemInserted(items.count()) } fun removeItem(item: I) { val index = items.indexOf(item) items.removeAt(index) notifyItemRemoved(index) } fun updateItem(position: Int, item: I) { items[position] = item notifyItemChanged(position) } final override fun getItemCount() = items.count() final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(onCreateItemView(parent.context).apply { layoutParams = RecyclerView.LayoutParams( RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT ) }) @Suppress("UNCHECKED_CAST") final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { (holder.itemView as? V)?.let { onBindItemView(it, items[position], position) } } @Suppress("UNCHECKED_CAST") protected open fun onCreateItemView(context: Context) = ((javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>) .constructors.first().newInstance(context) as V protected abstract fun onBindItemView(itemView: V, item: I, position: Int) inner class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) }
使い方
view_hoge_list_item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/textViewTitle" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
HogeListItemView.kt
package hoge import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.widget.LinearLayout import hoge.R import kotlinx.android.synthetic.main.view_hoge_list_item.view.* internal class HogeListItemView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) { init { LayoutInflater.from(context).inflate(R.layout.view_hoge_list_item, this) } fun setTitle(title: String) { textViewTitel.text = title } }
HogeFragment.kt
省略。Bindingのサンプル書くのめんどい。
HogeFragmentVM.kt
package hoge import android.content.Context import hoge.SimpleRecyclerViewAdapter import hoge.HogeListItemView internal class HogeFragmentVM() { val recyclerViewAdapter = object : SimpleRecyclerViewAdapter<HogeListItemView, String>(arrayListOf<String>("Hogeeeeee", "Aaaaaaaaaaa")) { override fun onBindItemView(itemView: HogeListItemView, item: String, position: Int) { itemView.setTitle(item) } } }
fragment_hoge.xml
抜粋。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="vm" type="hoge.HogeFragmentVM" /> </data> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="android.support.v7.widget.LinearLayoutManager" app:recyclerViewAdapter="@{vm.recyclerViewAdapter}" /> </layout>
onBindItemViewだけoverrideすればいいから簡単ですね。はい。いじょー
Prism PubSubEventを簡単に
GetEventとか長ったらしい。
おもむろに以下のクラスを実装しましょう。
using Prism.Events; using System; namespace Hoge { public abstract class StaticPubSubEvent<T> : PubSubEvent where T : StaticPubSubEvent<T>, new() { private static EventAggregator eventAggregator = new EventAggregator(); private static readonly T instance = eventAggregator.GetEvent<T>(); protected StaticPubSubEvent() { } public static new void Publish() => eventAggregator.GetEvent<T>().InternalPublish(); public static new SubscriptionToken Subscribe(Action action) => Subscribe(action, ThreadOption.PublisherThread); public static new SubscriptionToken Subscribe(Action action, ThreadOption threadOption) => Subscribe(action, threadOption, false); public static new SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive) => Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive); public static new SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive) { var actionReference = new DelegateReference(action, keepSubscriberReferenceAlive); EventSubscription subscription; switch (threadOption) { case ThreadOption.PublisherThread: subscription = new EventSubscription(actionReference); break; case ThreadOption.BackgroundThread: subscription = new BackgroundEventSubscription(actionReference); break; case ThreadOption.UIThread: if (instance.SynchronizationContext == null) throw new InvalidOperationException(); subscription = new DispatcherEventSubscription(actionReference, instance.SynchronizationContext); break; default: subscription = new EventSubscription(actionReference); break; } return eventAggregator.GetEvent<T>().InternalSubscribe(subscription); } } }
で、イベントクラスを用意
namespace Hoge { public sealed class HogeEvent : StaticPubSubEvent<HogeEvent> { } }
Subscriber
HogeEvent.Subscribe(() => { }, ThreadOption.UIThread);
Publisher
HogeEvent.Publish();
ぼくは気にしないけど、若干いまいちですね。何がいまいちかわかる人だけ自己責任で使ってくださいw(ヒント:EventAggregator
Enum Radio Button Binding
WPFのMVVMは好きです。他の言語で似非MVVM書いてるとなんだかなーって思う。
が、やっぱりWPFは嫌いです。
はい。EnumをBindingしたいとき。
まずおもむろにEnumの拡張を用意しましょう。このブログでも出てきたと思いますよー。
public static class EnumEx { public static string StringValue(this Enum e) { var fieldInfo = e.GetType().GetField(e.ToString()); var attribute = (StringValueAttribute)fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false).FirstOrDefault(); return attribute == null ? null : attribute.StringValue; } }
次にConverterを2つ用意しましょう。
using System; using System.Globalization; using System.Windows.Data; namespace Hoge { [ValueConversion(typeof(Enum), typeof(bool))] public class EnumToRadioButtonConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value.Equals(Enum.Parse(value.GetType(), parameter.ToString())); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (!(bool)value) return Binding.DoNothing; return Enum.Parse(targetType, parameter.ToString()); } } }
using System; using System.Globalization; using System.Windows.Data; namespace Hoge { [ValueConversion(typeof(Enum), typeof(string))] public class EnumToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (Enum.Parse(value.GetType(), parameter.ToString()) as Enum).StringValue(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (!(bool)value) return Binding.DoNothing; return Enum.Parse(targetType, parameter.ToString()); } } }
んで、enumとViewModel。双方向は省略。INotifyPropertyChangedを実装してくだされ。
namespace Hoge { public enum Gender { [StringValue("男性")] Male, [StringValue("女性")] Female } public sealed class HogeViewModel { public Gender Gender { get; set; } } }
で、View
<Page x:Class="Hoge.SamplePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Hoge" mc:Ignorable="d" d:DesignHeight="500" d:DesignWidth="300"> <Page.DataContext> <local:HogeViewModel x:Name="viewModel" /> </Page.DataContext> <Page.Resources> <local:EnumToRadioButtonConverter x:Key="EnumToRadioButton"/> <local:EnumToStringConverter x:Key="EnumToString"/> </Page.Resources> <Grid> <StackPanel Orientation="Horizontal"> <RadioButton GroupName="Gender" Content="{Binding Gender, ConverterParameter=Male, Converter={StaticResource EnumToString}}" IsChecked="{Binding Gender, ConverterParameter=Male, Converter={StaticResource EnumToRadioButton}}"/> <RadioButton GroupName="Gender" Content="{Binding Gender, ConverterParameter=Female, Converter={StaticResource EnumToString}}" IsChecked="{Binding Gender, ConverterParameter=Female, Converter={StaticResource EnumToRadioButton}}"/> </StackPanel> </Grid> </base:BasePage>
Windowsタスクトレイ常駐型のアプリを作る(WPF版)
よく作るんですけどね。忘れるよね。
なお、ググるキーワードはNotifyIcon。
Component作成
Componentsフォルダを作って、右クリック→「追加」→「新しい項目」
「コンポーネントクラス」を探し出して、お好きな名前をつけて追加しましょう。
ここでは「NotifyIcon」としました。
NotifyIconクラスの編集
ツールボックスから「ContextMenuStrip」と「NotifyIcon」を追加します。
ツールボックスで検索して、ドラッグ&ドロップで持っていきましょう。
デフォルトの名前が嫌なので変えましたよ。
contextMenuStripの設定
contextMenuStripを右クリックで「項目の編集」
MenuItemを追加
「デザイン」→「(Name)」と「表示」→「Text」を編集
Nameはご自由に。Textはタスクトレイのアイコンを右クリックしたときに出るメニューに表示されますよ。
myNotifyIconの編集
プロパティを開いて、ContextMenuStripとIconを設定
NotifyIcon.csの編集
終了メニュークリックしたらアプリケーションを終了。
using System; using System.ComponentModel; using System.Windows; namespace Hoge.Components { public partial class NotifyIcon : Component { public NotifyIcon() { InitializeComponent(); toolStripMenuItemExitApp.Click += delegate (object sender, EventArgs e) { Application.Current.Shutdown(); }; } public NotifyIcon(IContainer container) { container.Add(this); InitializeComponent(); } } }
App.xamlの編集
App.xaml.csのほうから
using Hoge.Components; using System.Windows; namespace Hoge { /// <summary> /// App.xaml の相互作用ロジック /// </summary> public partial class App : Application { private NotifyIcon notifyIcon = new NotifyIcon(); protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ShutdownMode = ShutdownMode.OnExplicitShutdown; } protected override void OnExit(ExitEventArgs e) { base.OnExit(e); notifyIcon.Dispose(); } } }
んで、App.xaml
StartupUri="MainWindow.xaml"って記載があるので削除しましょう。
<Application x:Class="Questionnaire.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Hoge"> <Application.Resources> </Application.Resources> </Application>
Windowsインストーラーの作成
2018年最新版!
Visual Studio2017向け!ぼくはVisual Studio Community 2017だよ!
既存のソリューションにインストーラープロジェクトを追加するよ!最近C#ばっかだよ!
作成
Visual Studioを起動。インストーラーを追加したいソリューションを開いてね!
ソリューションを右クリックで「追加」→「新しいプロジェクト」
「インストール済み」→「その他のプロジェクトの種類」→「Setup Project」で名前をつけて「OK」
インストールするファイルの指定
追加されたプロジェクトを右クリックで「View」→「ファイルシステム」
ちなみに「インストール」「アンインストール」からインストーラーとあんインストーラーが起動できるよ!
単純なものだったら「Application Folder」を右クリック→「Add」→「プロジェクト出力」でexeとかDLLが追加できるよ!
よその記事見るとインストーラービルド時に構成を切り替えてーとかやってたけど、インストーラーなんてここでRelease Any CPUにしとけばいいと思うよ!
インストーラー設定
必要最低限。セットアッププロジェクトのプロパティね。
Author→お名前を書きましょう
Manufacturer・ProductName→C:¥Program Files¥[Manufacturer]¥[ProductName]にデフォルトでインストールされるよ!
TargetPlatform→ちゃんと合わせようね!
Docker環境構築(Rails)②
devdevdev.hatenablog.com
基礎編はこちら
前回作ったコンテナ上にrailsの実行環境を作っていきますよー
ホスト側は一切汚しません。
とりあえずね
$ docker-compose run app bundle init
app/Gemfileが生成されるよ
Gemfileを編集
デフォルトはこう
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # gem "rails"
最後の行を以下のように書き換えます
gem 'rails', '5.1.4'
docker-compose.ymlの編集
appの下に以下を追記
environment: RAILS_ENV: development
app / volumesの下に以下を追記
- bundle:/usr/local/bundle
appの一番下に以下を追記
command: /bin/sh -c 'mkdir -p tmp/sockets/ && bundle exec puma -C config/puma.rb'
一番下に以下を追記
volumes: bundle:
Dockerfileの編集
docker/app/Dockerfileに以下を追記
RUN apt-get update && apt-get install -y nodejs ADD ./app/Gemfile* $APP_ROOT/ RUN bundle install
ビルド
$ docker-compose build
railsアプリケーション作成
appにsshではいる
$ docker-compose run app bash
普通に作る
$ rails new . -d mysql
Gemfileを上書きしていいか聞かれるので「Y」
app/config/database.yml書き換え
development: password: password
app/config/puma.rbに以下を追記
app_root = File.expand_path("../..", __FILE__) bind "unix://#{app_root}/tmp/sockets/puma.sock"
ビルドして起動
$ docker-compose build $ docker-compose up
http://localhost/にアクセスしてrailsの画面が表示されればおk。