もくもく Xamarin

自分で使うための iOS アプリを Xamarin で開発している。 Swift や Objective-C で開発しないのは、 大人の事情であり、 宗教的理由でもある。

最初 Xamarin.Forms で始めたが、 実現したい UI のためには、 Xamarin.Forms だとカスタムレンダラーをいくつも作る必要があったので、 早い段階で Xamarin.iOS に切り替えた。 Xamarin.iOS なので、当然 iOS 開発の知識が必要。 そこは RubyMotion のときと同じ。 仮に Xamarin.Forms で開発したとしても、どのみち必要になると思うが。

今回は作ろうとしているアプリの要件に合わなかっただけで、Xamarin.Forms は良いものだった。 WPF で慣れているのもあり、 Storyboard や Interface Builder でポチポチやるよりも、 XAML を書く方が圧倒的に効率が良かった。 Xamarin.Forms は macOSWPF を 2017 年第 3 四半期にサポート予定みたいだし、 アプリを新規に作るときは、これからもXamarin.Formsでいけそうか最初に試すと思う。

Swift から Xamarin.iOS への移行は、 Objective-C から Swift に移行したときとあまり変わらない印象。 iOS のライブラリで有名どころは、 Xamarin にポートしたものかバインディングが NuGet で入手できたりする。 無ければ自分でバインディングを作成すればいい。 ただ、RubyMotion の時はシームレスに iOS のライブラリを使えたので、 そこだけが障壁だな。 Xamarin から Swift に戻るのも難しくなさそう。 async/await 除けば。

サムライジェラート

梅雨が明けて夏本番。連日猛暑日。もうアイスでも食べないとやってられないので、前から雑誌で見て気になっていた警固公園そばにある『サムライジェラート』に行ってみた。

ジェラートの種類が豊富でどれにするか迷った。搾りたて阿蘇小国ジャージーミルクを食べることは決めていたので、あと一つ。イタリア旅行で思い出深いクイーンピスタチオにしてみた。

クイーンピスタチオは風味が予想以上に強く出ていて、食べ終わった後もしばらく残っていた。超ピスタチオって感じ。余韻を長く楽しめた。イタリアで食べたピスタチオのジェラートよりも美味しいかも。美化されているであろう記憶より美味いとなるとよっぽどだな。

阿蘇ジャージーミルクは、ミルクの濃厚さを味わいつつも、余韻はキレが良くて、総じてすっきりとした甘さという印象になった。クイーンピスタチオと組み合わせたのは正解だったな。

ハッピーあまおうや純正バニラ、ミルクショコラやビターショコラなど、他にも気になるジェラートがたくさんあった。搾りたて阿蘇小国ジャージーミルクとクイーンピスタチオもまた食べたい。全種類制覇するのは大変そうだ。

関連ランキング:アイスクリーム | 西鉄福岡駅(天神)天神南駅天神駅

UINavigationController の rootViewController を変更する

UINavigationController の setViewControllers メソッドを使って、ごっそり入れ替えればいい。 以下サンプルコード。Xamarin.iOS だけど。

NavigationController.SetViewControllers(
    new [] { newRootViewController },
    animated);

博多 旨鮨 小野

お祝いで寿司を食べることになったので、木の葉モールにある『博多 旨鮨 小野』に行ってきた。木の葉モールはファミリー向けのショピングモールで、その中の飲食店も子供連れで入りやすくて重宝している。

お昼に行ったけど、お祝いの席でお得なランチメニューを注文するのは興が削がれる気がしたので、 ランチにしてはちょっとだけお高めの『極厳選握り寿司十一貫』を注文。

茶碗蒸しは海鮮の出汁が効いた優しい味で良かった。子供もパクパク食べてた。

f:id:griefworker:20170720221204p:plain

お吸い物。

f:id:griefworker:20170720221229p:plain

そしてメインの寿司。 どれも美味かった。

f:id:griefworker:20170720221341p:plain

イクラ苦手だったけど克服したかも。

f:id:griefworker:20170720222118p:plain

f:id:griefworker:20170720222148p:plain

f:id:griefworker:20170720222744p:plain

f:id:griefworker:20170720222805p:plain

小野グループなだけあって味は期待通りだった。 とにかく小さい子供を連れて入りやすいのがポイント高いので、 お祝いで寿司でもという話になったら、 当分はファーストチョイスだろうな。

博多旨鮨 小野

食べログ 博多旨鮨 小野

『星野、目をつぶって。(1)~(6)』を読んだ

週刊少年マガジンで連載中の『星野、目をつぶって。』をKindleでまとめ買いして読んだ。「クラス一の日陰者・小早川が、 クラス一の人気者・星野海咲の素顔を知ったことがきっかけで、 メイクの依頼を請け負うことになる」というあらすじのマンガ。

ヒロインの星野は後先考えずに行動してメイクを崩すので、メイクを担当している小早川は必然的に巻き込まれながらも、クラスメイトや他の生徒の悩みを解決していく。 本作は一応、ラブコメに分類されているのかな。でも前述のように、お悩み解決の話が多かったので、ラブコメというよりも学園ものというか、青春ものというか、そんな色が強いように思う。ラブコメブコメしてない。

星野に巻き込まれる形で生徒の悩み解決をしてきた小早川だが、最近では星野の協力を得て自分自身を変えるための話にシフトしてきた。ラブコメなんで誰と結ばれるかも気になるが、それと同じくらいに小早川含む主要な登場人物がどう変わっていくのか楽しみ。

余談だけど、最初悪役的な立場で出てきた不良の加納、登場人物の中で一番ハイスペックではないだろうか。 不良な点を除けば、学力は学年8位だし、家事得意だし、 運動能力も星野ほどでは無いがかなり高い。 登場時の印象は最低だったけど、 小早川との因縁が絡んだ怒涛の連続イベントで手のひらクルってなった。

ASP.NET Core MVC で Basic 認証

先日 ASP.NET Core で Basic 認証を行うサンプルを書いた。

tnakamura.hatenablog.com

ただ、これだと全てのパスで認証が必要になってしまう。 もともとやりたかったことは、 「ASP.NET Core MVC で Authorized 属性を付けたアクションだけを認証必須にすること」。

Microsoft.AspNetCore.Authentication をベースに Basic 認証ミドルウェアを作れば実現できそうだったけど、既に同じことをやっている人がいた。

github.com

NuGet でパッケージが公開されているので、使わせてもらうことにした。

using idunno.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Security.Claims;
using System.Threading.Tasks;

namespace BasicAuthExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseApplicationInsights()
                .Build();

            host.Run();
        }
    }

    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            // Basic 認証を使用する
            app.UseBasicAuthentication(new BasicAuthenticationOptions()
            {
                Realm = "BasicAuthExample",
                Events = new BasicAuthenticationEvents()
                {
                    OnValidateCredentials = context =>
                    {
                        // ユーザー名とパスワードはとりあえず固定
                        if (context.Username == "tnakamura" &&
                            context.Password == "test12345")
                        {
                            var claims = new Claim[]
                            {
                                new Claim(ClaimTypes.Name, context.Username),
                            };
                            var identity = new ClaimsIdentity(claims, context.Options.AuthenticationScheme);
                            var principal = new ClaimsPrincipal(identity);
                            context.Ticket = new AuthenticationTicket(
                                principal,
                                new AuthenticationProperties(),
                                context.Options.AuthenticationScheme);
                        }
                        return Task.FromResult(true);
                    },
                }
            });

            app.UseMvcWithDefaultRoute();
        }
    }

    public class HomeController : Controller
    {
        [HttpGet("/")]
        public string Index()
        {
            return "Hello World!";
        }

        // アクセスすると認証ダイアログが表示される
        [Authorize]
        [HttpGet("/hello")]
        public string Hello()
        {
            return $"Hello {User.Identity.Name}!";
        }
    }
}

これで、Authorize 属性が付いたアクションの呼び出しに Basic 認証が効くようになった。 今回もユーザー名とパスワードは固定なので、ここはデータベースから取得した方が良いだろう。

OnValidateCredentials に登録するデリゲートでは、context.HttpContext.RequestServices を使って DI コンテナに登録した DbContext や自作リポジトリなんかを取得できるので、実装するのは難しくないはず。