ふるすたっくえんじにあの日記

ASP .NET MVC (C#)、.NET Framework、iOS (Objective-c) アプリ、Androidアプリ (Java)、AWSあたり

Xcode10 Abort trap:6

たまには書く。

To iOSえんじにあーな皆様
Xcode10にすると以下のコードが使えなくなる模様だぉ
なんでだかわからんけど、調べる暇はないんだぉ

import UIKit

final class FooViewController: UIViewController {
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

こっちならおっけーらしいけどさーけどさー
convenience initってinitじゃないじゃん?

import UIKit

final class FooViewController: UIViewController {
    convenience init() {
        self.init(nibName: nil, bundle: nil)
    }
}


【Cordova】 bump


プラグインとかあるけど、よくよく考えたら、fastlane使ってりゃRubyで書けばいいよね。
なお、Unity / Cocos2d-x に加え、Cordova pluginの作り方も覚えた。

  lane :bump do
    require 'rexml/document'
    doc = REXML::Document.new(File.read('../config.xml'))
    element = doc.elements['widget']
    element.attributes['android-versionCode'] = element.attributes['android-versionCode'].to_i + 1
    element.attributes['ios-CFBundleVersion'] = element.attributes['ios-CFBundleVersion'].to_i + 1
    File.write('../config.xml', doc)
  end


【WPF】KeyBinding with TriggerAction(Data Binding対応版)

はい。昨日こんなの書きました。動きませんでした。
参考記事によるとTriggerBaseはFreeazableだから、当然TriggerBaseを継承しているやつなら問題ないと思ったんですけどね。ICommandを実装してるとだめらしい。
というわけで書き直しました。

devdevdev.hatenablog.com


InputBindingTrigger

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace Hoge.Trigger
{
    public class InputBindingTrigger : TriggerBase<FrameworkElement>
    {
        public static readonly DependencyProperty InputBindingProperty =
            DependencyProperty.Register("InputBinding", typeof(InputBinding) , typeof(InputBindingTrigger) , new UIPropertyMetadata(null));

        public InputBinding InputBinding
        {
            get => (InputBinding)GetValue(InputBindingProperty);
            set => SetValue(InputBindingProperty, value);
        }

        private InputBindingCommand command = new InputBindingCommand();

        protected override void OnAttached()
        {
            if (InputBinding == null)
            {
                base.OnAttached();
                return;
            }

            InputBinding.Command = command;
            command.OnExecute += Command_OnExecute;
            if (AssociatedObject.Focusable)
                AssociatedObject.InputBindings.Add(InputBinding);
            else
            {
                FrameworkElement root = null;
                AssociatedObject.Loaded += delegate
                {
                    root = GetRootElement(AssociatedObject);
                    if (!root.InputBindings.Contains(InputBinding))
                        root.InputBindings.Add(InputBinding);
                };
                AssociatedObject.Unloaded += delegate
                {
                    root.InputBindings.Remove(InputBinding);
                };
            }
            base.OnAttached();
        }

        private FrameworkElement GetRootElement(FrameworkElement frameworkElement)
        {
            var parent = frameworkElement.Parent as FrameworkElement;
            if (parent == null)
            {
                Debug.Assert(frameworkElement.Focusable);
                frameworkElement.Focus();
                return frameworkElement;
            }
            return GetRootElement(parent);
        }

        protected override void OnDetaching()
        {
            command.OnExecute -= Command_OnExecute;
            base.OnDetaching();
        }

        private void Command_OnExecute(object parameter) => InvokeActions(parameter);
    }

    internal class InputBindingCommand : ICommand
    {
        public event EventHandler CanExecuteChanged = delegate { };

        public Action<object> OnExecute = delegate { };

        public bool CanExecute(object parameter) => true;

        public void Execute(object parameter) => OnExecute.Invoke(parameter);
    }
}

ICommandを実装するのやめただけですね。
あと、RootElement取得するように変えてるっす。ので、ちょっとxamlが変わります。
ルート要素のFocusableをTrueに。
TriggerActionはTrigger.Actionsに。
なお、TriggerActionは前回用意したやつなので省略。



<Window x:Class="Hoge.Views.MainWindow"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                 xmlns:action="clr-namespace:Hoge.Action"
                 xmlns:trigger="clr-namespace:Hoge.Trigger"
                 Focusable="True">
    <Grid>
        <i:Interaction.Triggers>
            <trigger:InputBindingTrigger>
                <trigger:InputBindingTrigger.InputBinding>
                    <KeyBinding Modifiers="Ctrl" Key="N"/>
                </trigger:InputBindingTrigger.InputBinding>
                <trigger:InputBindingTrigger.Actions>
                    <action:ShowFolderBrowserDialogAction Description="選択せーや"
                                                                                       SelectedPath="{Binding SelectedPath.Value, Mode=OneWayToSource}" />
                </trigger:InputBindingTrigger.Actions>
            </trigger:InputBindingTrigger>
        </i:Interaction.Triggers>
    </Grid>
</Window>


参考:
qiita.com

【WPF】KeyBinding with TriggerAction

追記。これ、このままじゃData Binding動かへん。書き直します。。


KeyBindingで実行対象がCommandなら別になんてことないんですけどね。
TriggerAction使う場合ね。


いつも通りおもむろに以下を作りましょう。

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace Hoge.Action
{
    public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
    {
        public static readonly DependencyProperty InputBindingProperty =
            DependencyProperty.Register("InputBinding", typeof(InputBinding) , typeof(InputBindingTrigger) , new UIPropertyMetadata(null));

        public InputBinding InputBinding
        {
            get => (InputBinding)GetValue(InputBindingProperty);
            set => SetValue(InputBindingProperty, value);
        }

        public event EventHandler CanExecuteChanged = delegate { };

        protected override void OnAttached()
        {
            if (InputBinding != null)
            {
                InputBinding.Command = this;
                AssociatedObject.InputBindings.Add(InputBinding);
            }
            base.OnAttached();
        }

        public bool CanExecute(object parameter) => true;

        public void Execute(object parameter) => InvokeActions(parameter);
    }
}



使い方

こんなTriggerActionが用意してあります。フォルダ選択ダイアログですね。

namespace Hoge.Action
{
    /// <summary>
    /// FolderBrowserDialog Confirmation
    /// </summary>
    public sealed class FolderBrowserDialogConfirmation : Confirmation
    {
        public Boolean? ShowNewFolderButton { get; set; }
        public String SelectedPath { get; set; }
        public Environment.SpecialFolder? RootFolder { get; set; }
        public String Description { get; set; }
        public Object Tag { get; set; }
    }

    /// <summary>
    /// FolderBrowserDialog表示アクション
    /// </summary>
    public sealed partial class ShowFolderBrowserDialogAction
    {
        public Boolean? ShowNewFolderButton
        {
            get => (Boolean?)GetValue(ShowNewFolderButtonProperty);
            set => SetValue(ShowNewFolderButtonProperty, value);
        }
        private static readonly DependencyProperty ShowNewFolderButtonProperty = DependencyProperty.Register("ShowNewFolderButton", typeof(Boolean?), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public String SelectedPath
        {
            get => (String)GetValue(SelectedPathProperty);
            set => SetValue(SelectedPathProperty, value);
        }
        private static readonly DependencyProperty SelectedPathProperty = DependencyProperty.Register("SelectedPath", typeof(String), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public Environment.SpecialFolder? RootFolder
        {
            get => (Environment.SpecialFolder)GetValue(RootFolderProperty);
            set => SetValue(RootFolderProperty, value);
        }
        private static readonly DependencyProperty RootFolderProperty = DependencyProperty.Register("RootFolder", typeof(Environment.SpecialFolder), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public String Description
        {
            get => (String)GetValue(DescriptionProperty);
            set => SetValue(DescriptionProperty, value);
        }
        private static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public Object Tag
        {
            get => GetValue(TagProperty);
            set => SetValue(TagProperty, value);
        }
        private static readonly DependencyProperty TagProperty = DependencyProperty.Register("Tag", typeof(Object), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));
    }

    public sealed partial class ShowFolderBrowserDialogAction : TriggerAction<DependencyObject>
    {
        private static readonly FolderBrowserDialogConfirmation NullObject = new FolderBrowserDialogConfirmation();

        protected override void Invoke(object parameter)
        {
            var param = parameter as InteractionRequestedEventArgs;
            var confirmation = param == null ? NullObject : param.Context as FolderBrowserDialogConfirmation;

            var dialog = GenerateDialog(confirmation);
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                confirmation.Confirmed = true;
                ApplyDialogPropertyValues(confirmation, dialog);
            }
            else
            {
                confirmation.Confirmed = false;
            }

            param?.Callback();
        }

        private FolderBrowserDialog GenerateDialog(FolderBrowserDialogConfirmation confirmation)
        {
            var dlg = new FolderBrowserDialog();
            dlg.ShowNewFolderButton = confirmation.ShowNewFolderButton ?? ShowNewFolderButton ?? dlg.ShowNewFolderButton;
            dlg.RootFolder = confirmation.RootFolder ?? RootFolder ?? dlg.RootFolder;
            dlg.Description = confirmation.Description ?? Description ?? dlg.Description;
            dlg.Tag = confirmation.Tag ?? Tag ?? dlg.Tag;
            SelectedPath = string.Empty;
            return dlg;
        }

        private void ApplyDialogPropertyValues(FolderBrowserDialogConfirmation n, FolderBrowserDialog dlg)
        {
            if (n != NullObject)
            {
                n.ShowNewFolderButton = dlg.ShowNewFolderButton;
                n.SelectedPath = dlg.SelectedPath;
                n.RootFolder = dlg.RootFolder;
                n.Description = dlg.Description;
                n.Tag = dlg.Tag;
            }

            ShowNewFolderButton = dlg.ShowNewFolderButton;
            SelectedPath = dlg.SelectedPath;
            RootFolder = dlg.RootFolder;
            Description = dlg.Description;
            Tag = dlg.Tag;
        }
    }
}


んで、xaml。コードビハインドとViewModelは省略。ReactiveProperty使ってます。

<Window x:Class="Hoge.MainWindow"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:action="clr-namespace:HogeAction"
                 mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <i:Interaction.Triggers>
            <action:InputBindingTrigger>
                <action:InputBindingTrigger.InputBinding>
                    <KeyBinding Modifiers="Ctrl" Key="N"/>
                </action:InputBindingTrigger.InputBinding>
                <action:ShowFolderBrowserDialogAction Description="フォルダを選択してください"
                                                                                   SelectedPath="{Binding SelectedPath.Value, Mode=OneWayToSource}" />
            </action:InputBindingTrigger>
        </i:Interaction.Triggers>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Menu Grid.Row="0" >
            <MenuItem Header="ファイル(_F)">
                <MenuItem Header="新規(_N)" InputGestureText="Ctrl+N">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <action:ShowFolderBrowserDialogAction Description="フォルダを選択してください" 
                                                                                               SelectedPath="{Binding SelectedPath.Value, Mode=OneWayToSource}" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </MenuItem>
            </MenuItem>
        </Menu>
    </Grid>
</Window>

【C#】【Google Cloud Vision API】拡張

何に使うのかはわからない。
とりあえずSelectManyの繰り返しがひどかったので用意してみた。
何に使うのかはわからない。
ぶっちゃけTextAnnotationの仕様がよくわかってない。。
TextAnnotation.Text.Split('\n')とBlocks.Textが一致してほしいんだけどなぁ。。



using Google.Cloud.Vision.V1;
using System.Linq;

public static class CloudVisionEx
{
    // TextAnnotation → Page → Block → Paragraph → Word → Symbol → Text

    public static Block[] Blocks(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Blocks).ToArray();

    public static Paragraph[] Paragraphs(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Paragraphs()).ToArray();

    public static Paragraph[] Paragraphs(this Page page) => page.Blocks.SelectMany(x => x.Paragraphs).ToArray();

    public static Word[] Words(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Words()).ToArray();

    public static Word[] Words(this Page page) => page.Blocks.SelectMany(x => x.Words()).ToArray();

    public static Word[] Words(this Block block) => block.Paragraphs.SelectMany(x => x.Words).ToArray();

    public static Symbol[] Symbols(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Symbols()).ToArray();

    public static Symbol[] Symbols(this Page page) => page.Blocks.SelectMany(x => x.Symbols()).ToArray();

    public static Symbol[] Symbols(this Block block) => block.Paragraphs.SelectMany(x => x.Symbols()).ToArray();

    public static Symbol[] Symbols(this Paragraph paragraph) => paragraph.Words.SelectMany(x => x.Symbols).ToArray();

    public static string[] Text(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Text()).ToArray();

    public static string[] Text(this Page page) => page.Blocks.SelectMany(x => x.Text()).ToArray();

    public static string[] Text(this Block block) => block.Paragraphs.Select(x => x.Text()).ToArray();

    public static string Text(this Paragraph paragraph) => string.Join(string.Empty, paragraph.Words.Select(x => x.Text()));

    public static string Text(this Word word) => string.Join(string.Empty, word.Symbols.SelectMany(x => x.Text));
}

【WPF】親からの比率でいろいろ指定したい、Data Binding, Prism, Reactive

ずーっと何年もこのブログのアクセスランキングTOP5は全部C#の記事なんですぉ。
ということでたまにはC#だぉ。


はい、表題の通り。例えば親の幅の半分の幅にしたいとかいうことあるよね。
Gridとかならさあれだけどさ。Canvas想定ね、Canvas
親のActualWidthを取ってこないとだめなケースの場合に使えるかと?VMでそんなもん持ちたくないやん?

はい、いつも通りおもむろに以下のクラスを作りましょう。OneWay前提なのでConvertBackは適当。

using System;
using System.Globalization;
using System.Windows.Data;

namespace Hoge.Converter
{
    public sealed class RatioConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            => (double)values[0] * (double)values[1];

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            => null;
    }
}

で、VM

using Prism.Mvvm;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;

namespace Hoge.Controls
{
    /// <summary>
    /// View model for HogeControl
    /// </summary>
    public sealed partial class HogeControlVM:  BindableBase
    {
        public ReadOnlyReactiveProperty<double> WidthRatioProperty { get; }
        private double Width { get => width; set => SetProperty(ref width, value); }
        private double width;
    }

    public partial class HogeControlVM
    {
        public HogeControlVM()
        {
            WidthRatioProperty = this.ObserveProperty(x => x.Width).ToReadOnlyReactiveProperty();
            Width = 0.5;
        }
    }
}



コードビハインドは省略で、View

<UserControl x:Class="Hoge.Controls.HogeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:c="clr-namespace:Hoge.Converter"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <c:RatioConverter x:Key="Ratio" />
    </UserControl.Resources>
    <Canvas Name="Canvas">
        <TextBox Text="Hogeeee">
            <TextBox.Width>
                <MultiBinding Converter="{StaticResource Ratio}">
                    <Binding ElementName="Canvas" Path="Width" />
                    <Binding Path="WidthRatioProperty.Value" />
                </MultiBinding>
            </TextBox.Width>
        </TextBox>
    </Canvas>
</UserControl>

IDE使わずに書いてるから動かなかったらごめんちゃい?


【2018年版】iOSアプリ新規で作る時にやること③

これ年に10回くらいはやるんだけど、どうにかならんのかなぁ。シェルスクリプト書けばいけそうだけど、Xcodeについてくのだるいし、労力と比較して迷う。

Carthage / RxSwift / Compass / XCGLogger / Reachability / Alamofire / R.swift / SwiftLint / Generamba / VIPER / fastlaneあたりが技術キーワードでしょうか。箇条書きでいきまっせ。
ぼくはもうCarthage対応してないやつは使わないので、Cocoapodsは出てきません。


③CD編です。fastlaneが主。
CIは対応してません。
あとたぶん設定抜けてたけど、AppIconは設定しておきましょう。
↑に挙げたやつ残はRxSwift / Compass / XCGLogger / Reachability / Alamofire / VIPERとまぁ実装寄りだし、このシリーズは今回で終わりかなぁ。

①はこちら
devdevdev.hatenablog.com

②はこちら
devdevdev.hatenablog.com

とりあえず

Xcode開いて、TARGETS→General→Signingで「Automatically manage signing」を外しておきましょう。

fastlane設定など

順番通りにやってくればfastlaneが使えるようになっているはずなのでインストールは省略。

bundle exec fastlane init

しましょう。あとは質問に答えていくだけ。
とりあえず2. Automate beta distribution to TestFlightやったあとに、3.Automate App Store distributionをやりました。Schemaが2つあるので2つともね。ただ、StagingのApp Store distributionはいらないので計3回。

・・・・・・という風にはうまくいきません。実際にはやっていません。


そして、Generambaとfastlaneの相性がよくない。ってゆーかGenerambaが見てるxcodeprojが古すぎ。本家Generamba更新なさすぎ。つらたん。自作?いや、めんどい。Fork?いや、誰かやってるっしょってわけで以下の方を発見。Xcode9.4で動いてるっぽいことをぼくが確認しました。これでもxcodeproj 1.5.9だからぎり。。fastlaneはxcodeproj 1.5.7以上。。fastlaneをグローバルにいれればいける気がするけど、でもグローバルにはいれたくない。

github.com

とゆーわけでGemfileを更新。

source "https://rubygems.org"
  
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'generamba', github: "Bassov/Generamba", branch: "develop"
gem "fastlane", "2.99.0"

bundle updateとかbundle installとかしましょう。



んで、本題。

とりあえず証明書管理用のgitリポジトリを作りましょう。

で、devices.txtを作成。
バイス追加するときはこいつを更新しましょう。

Device ID	Device Name
A5B5CD50-14AB-5AF7-8B78-AB4751AB10A8	NAME1
A5B5CD50-14AB-5AF7-8B78-AB4751AB10A7	NAME2

fastlane/Appfileとfastlane/Fastfileを作成。
Fastfileは手抜き。ちゃんと書きましょう。

Appfile

itc_team_id("123456789") # iTunes Connect Team ID
team_id("ABCDEFGHIJ") # Developer Portal Team ID

Fastfile

default_platform(:ios)

platform :ios do
  desc "Get the development certifacate files to Local Machine"
  lane :get_development_cert do
    match(type: "development",
          readonly: true,
          git_url: "証明書管理用のgitリポジトリURL",
          app_identifier: ["本番用Bundle Identifier", "ステージング用Bundle Identifier"])
  end

  desc "Push a new Staging beta build to TestFlight"
  lane :staging_beta do
    beta(
      app_identifier: "ステージング用Bundle Identifier",
      scheme: "Hoge_Staging"
    )
  end

  desc "Push a new Production beta build to TestFlight"
  lane :production_beta do
    beta(
      app_identifier: "本番用Bundle Identifier",
      scheme: "Hoge_Production"
    )
  end

  private_lane :beta do |options|
    # テスト
    # run_tests(scheme: options[:scheme])

    # デバイス更新
    register_devices(devices_file: "./devices.txt")

    # 証明書更新
    match(type: "appstore",
          force_for_new_devices: true,
          git_url: "証明書管理用のgitリポジトリURL",
          app_identifier: ["本番用Bundle Identifier", "ステージング用Bundle Identifier"])

    # ビルド番号更新
    increment_build_number

    # ビルド
    build_app(scheme: options[:scheme])

    # Testflightにアップロード
    upload_to_testflight(skip_waiting_for_build_processing: true,
                         app_identifier: options[:app_identifier])
  end

  private_lane :refresh_development_cert do
    match(type: "development",
          force: true,
          git_url: "証明書管理用のgitリポジトリURL",
          app_identifier: ["本番用Bundle Identifier", "ステージング用Bundle Identifier"])
  end
end



refresh_development_cert

bundle exec fastlane refresh_development_cert

開発用の証明書の更新をしまっせ。
なんかあんま頻繁に更新されたくない気がしたので、private。使うときは書き換え。運用でカバー)

get_development_cert

bundle exec fastlane get_development_cert

一般エンジニアが使う用。
初めてrefresh_development_certした時にパスワード設定するので、それを共有しといてあげましょう。

staging_beta

bundle exec fastlane staging_beta

ステージング用のアプリをTestflightへ
バイスとProvisioning Profileの更新(必要なら)もやってます。

production_beta

bundle exec fastlane production_beta

本番用のアプリをTestflightへ
バイスとProvisioning Profileの更新(必要なら)もやってます。

余談

ちなみにぼくはrswiftがArchive対象に入っちゃってたらしく最初エラーになったぉ。
最近のXcodeってほんと親切だよね。
何回もApple IDいれんのめんどくさいよね。user_nameっていう設定がたぶんAppfileにできるよ。



最後に

Xcode開いて、TARGETS→General→Signingで選べるやつ選んでおきましょう。