ディーバ Blog

大阪発 C#の会社、株式会社ディーバの Blog です。

iOS 11 対応でアプリを修正したところ

開発している iOS 10 アプリをそのまま iOS 11.0 で動かすと不具合があったので修正したところです。

※ Xamarin.iOS + MvvmCross およびその他ライブラリを使っている環境です。

Document picker のデザインが異なる

iOS 11 では Document picker の navigation bar が白に、status bar の style が黒文字前提のデザインになっていました。

インポート時 (UIDocumentPickerMode.Import) の UIDocumentPickerViewController を表示したところ。

f:id:jz5_diva:20170927071101p:plain

左: iOS 11 右: iOS 10

iOS 10 アプリでしていたこと

  • アプリ全体の navigation bar の背景色・テキストの色を UINavigationBar.Appearance.BarTintColorUINavigationBar.Appearance.TintColor とで指定
  • アプリ全体の status bar の style を info.plist で指定
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>

iOS 11 アプリ用に修正したところ

  • iOS 10 で行なっていたアプリ全体の navigation bar の色、status bar の style 指定をやめ、ViewController ごとに毎回指定するようにしました。
NavigationController.NavigationBar.BarTintColor = UIColor.FromRGB(0x3f, 0x51, 0xb5);
NavigationController.NavigationBar.TintColor = UIColor.White;
NavigationController.NavigationBar.BarStyle = UIBarStyle.Black;

修正後の画面

f:id:jz5_diva:20170927071648p:plain

左: iOS 11 右: iOS 10

Document picker で選んだファイルを開けない

肝心なことが動かなくなっていました。DidPickDocument イベントが発生せず、iOS 11 からの DidPickDocumentAtUrls で処理する必要があるようです。

iOS 10 アプリのコード

var picker = new UIDocumentPickerViewController(new[] { "public.item" }, UIDocumentPickerMode.Import);
picker.DidPickDocument += (sender, args) =>
{
    // Do something
};
PresentViewController(picker, true, null);

iOS 11 アプリ用に修正したコード

var picker = new UIDocumentPickerViewController(new[] { "public.item" }, UIDocumentPickerMode.Import);

if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
    picker.DidPickDocumentAtUrls += (sender, args) =>
    {
        // Do something
    };
}
else
{
    picker.DidPickDocument += (sender, args) =>
    {
        // Do something
    };
}
PresentViewController(picker, true, null);

UIActivity で リソースファイルを共有できない

BundleResource としてアプリに組み込んでいるファイルを直接 BundlePath から URL 文字列を作成し UIActivityViewController へ渡すと「〜にコピー」というようなアプリへはファイルを渡せません。

f:id:jz5_diva:20170927082106p:plain

iOS 10 アプリのコード

var path = Path.Combine(NSBundle.MainBundle.BundlePath, "help.pdf");
var url = new NSUrl(path, false); // filepath to url (file://)

var activityItems = new NSObject[] { url };
var activityController = new UIActivityViewController(activityItems, null);
if (activityController.PopoverPresentationController != null)
{
        activityController.PopoverPresentationController.SourceView = View;
        activityController.PopoverPresentationController.BarButtonItem = _shareButton;
}
PresentViewController(activityController, true, null);

iOS 11 アプリように修正したコード

対象のファイルを一時フォルダーにコピーするようにしました。

var path = Path.Combine(NSBundle.MainBundle.BundlePath, "help.pdf");
var tempPath = Path.Combine(Path.GetTempPath(), "help.pdf");
File.Copy(path, tempPath, true);
var url = new NSUrl(tempPath, false); // filepath to url (file://)

var activityItems = new NSObject[] { url };
var activityController = new UIActivityViewController(activityItems, null);
if (activityController.PopoverPresentationController != null)
{
    activityController.PopoverPresentationController.SourceView = View;
    activityController.PopoverPresentationController.BarButtonItem = _shareButton;
}
PresentViewController(activityController, true, null);

UIToolbar 上の UIButton が押せない

トリッキーなコードですが、UIToobar の Subview に追加した UIButton がタップできません。

このコードがあるのが BTProgressHUD 。loading/progress 画面のキャンセルボタンが反応しません。ACR User Dialogs で参照しているライブラリなので ACR User Dialogs を使っていても問題が起こります。

f:id:jz5_diva:20170927074358p:plain

※ iOS 10 では動いているので view の UserInteractionEnabled を true する話とは違います。

調べていると UIToolbar にひとつ以上 UIBarButtonItem があると UIButton も押せるという回避策を見つけました。

iOS 11 アプリでは、とりあえずライブラリのコードを修正して UIToolbar を使わず UIView に置き換えました。

MvvmCross で UITableViewCell の要素の binding で表示不具合

ニッチなのでさらっと紹介すると、MvvmCross を使って ViewModel 側でリスト項目のテキストやアイコン画像を cell の view(ImageVIew など)に binding していました。

iOS 11 では UITableView の表示と ViewModel に値を設定するタイミングで、ImageView.Image への binding している画像が表示されない現象が起きました。

View 側:

public class CustomStyleCell : MvxTableViewCell
{
    [Export("initWithStyle:reuseIdentifier:")]
    public CustomStyleCell(UITableViewCellStyle style, NSString cellIdentifier) : base("", UITableViewCellStyle.Value1, cellIdentifier)
    {
        var set = this.CreateBindingSet<CustomStyleCell, ListItemViewModel>();
        set.Bind(TextLabel).To(vm => vm.Text);
        set.Bind(DetailTextLabel).To(vm => vm.DetailText);
        set.Bind(ImageView).For(v => v.Image).To(vm => vm.Image).WithConversion(new NameToUIImageValueConverter());
        set.Apply();
    }
}
public class NameToUIImageValueConverter : MvxValueConverter<string, UIImage>
{
    protected override UIImage Convert(string value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value == null)  ? null : UIImage.FromBundle(value);
    }
}

ViewModel:

public class ListItemViewModel : MvxNotifyPropertyChanged
{
    public string Text
    {
        get => _text;
        set => SetProperty(ref _text, value);
    }
    private string _text;

    public string DetailText
    {
        get => _detailText;
        set => SetProperty(ref _detailText, value);
    }
    private string _detailText;

    public string Image
    {
        get => _image;
        set => SetProperty(ref _image, value);
    }
    private string _image;
}

iOS 11 アプリ用に修正したコード

iOS 11 アプリでは MvxTableViewSource の GetOrCreateCellFor 内で生成した cell の ImageView.Image へ直接 ViewModel の値を設定するように「も」しました。

public class CustomTableViewSource<TCell> : MvxTableViewSource where TCell : MvxTableViewCell
{
    protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
    {
        var cell = tableView.DequeueReusableCell("cellIdentifier");
        var vm = (ListItemViewModel)item;
        if (vm != null)
        {
            // workaround
            if (!string.IsNullOrWhiteSpace(vm.Image))
                cell.ImageView.Image = UIImage.FromBundle(vm.Image);
        }
        return cell;
    }
}