iOS

UIKit使いが戸惑ったSwiftUIのハマりどころ

そろそろSwiftUIを覚えないと…という事で既存アプリの新機能をSwiftUIだけで追加してみました。

そこでUIKitと比べた時に、SwiftUIだと分かりづらかったり面倒だったりした事をこの記事にまとめておきます。

これからSwiftUIを使っていきたい!と思っている方の参考になりますように。

 

NavigationBarを消したい

僕のアプリは、上端のボタンは指が届かないので下に戻るボタンを追加して、ヘッダーは消すようにしています。
でも、UIKit同様、SwiftUIもデフォルトでNavigationBarが表示されてしまいます。

と思ってたら、iOS16以降だとデフォルトで非表示みたいですね。
今スクリーンショットを撮っていて気づきました。
なのでこれはアプリがiOS16未満を対象としている場合の話となります。

iOS16未満でナビゲーションバーを消す方法を調べてみたら

答えは「NavigationView + navigationBarHidden」でした。

NavigationView {  // これを追加して
  Text("新しい機能の画面だよ")
 }
.navigationBarHidden(true)   // これを指定

バッチリ、ナビゲーションバーが消えました!

ここで使っているNavigationViewは、iOS16以降では非推奨となり、新しくNavigationStackが登場しています。

iOS16以降だけを対象としている場合は、NavigationStackを利用してください。

 

UITableViewを使いたい

簡単な設定画面を作りたかったので、UIKitで言う所のUITableViewを使いたい!

SwiftUIはだいたい「UI」を抜いたらその機能が見つかるので「TableView」と打ってみたけど無かったです。

答えは「List」でした。

きっちりUITableViewの機能を抑えられていたので知ってみたらシンプルで便利。
UIKitとは全然違うけど直感的ですね。

List {
  Section { // セクション分割
    Text("パスコードの更新")
    Text("パスコードの削除")
  } header: {
    Text("パスコード") // セクション名
  }
                
  Section { // セクション分割
    HStack {
      Image("momo")
      Text("パスコードの使い方")
    }
  }
}
.listStyle(.grouped) // これでGroupにする

 

Listのセルの高さを変えたい

何か窮屈そうだから、UIKitで言うところのUITableViewCellの高さを変えたい!

SwiftUIは大体frameっていうパラメータで高さを変えるみたいなので、各Sectionに高さを指定してみたら、良さそうだけどSection名「パスコード」の所も広くなってて何か変

List {
  Section {
    Text("パスコードの更新")
    Text("パスコードの削除")
  } header: {
    Text("パスコード")
  }.frame(height: 60) // 高さを指定!
                
  Section {
    HStack {
      Image("momo")
      Text("パスコードの使い方")
    }
  }.frame(height: 60)  // 高さを指定!
}
.listStyle(.grouped)

 

答えは「defaultMinListRowHeightで指定」でした。

List {
  Section {
    Text("パスコードの更新")
    Text("パスコードの削除")
  } header: {
    Text("パスコード")
  }
       
  Section {
    HStack {
      Image("momo")
      Text("パスコードの使い方")
    }
  }
}
.listStyle(.grouped)
.environment(\.defaultMinListRowHeight, 60)   // コレで高さ指定!


セクション名に変な隙間が入ったりしないでいい感じ!

 

背景にパターン画像を配置したい

このアプリではサブスクで綺麗な背景をセットできるようになるので、SwiftUIの背景にも適用したい!

UIKitだと、UIViewControllerのviewの、backgroundColorに指定すれば簡単にできるけど…

答えは「ZStack + Image」でした。

ZStack {
  // 背景
  Image("sushi").resizable(resizingMode: .tile) // tile指定でパターン画像になる
    
  // リストのコード...
}

(Listの背景で隠れすぎだけど…)よっしゃ出た!
けど、SafeAreaで途切れちゃってる…。全面表示にしたい。

答えは「edgesIgnoringSafeArea」でした。

ZStack {
  // 背景
  Image("sushi").resizable(resizingMode: .tile)
  .edgesIgnoringSafeArea(.all)  // これでSafeAreaをぶち抜ける
    
  // リストのコード...
}

(Listの背景で隠れすぎだけど…)やったー!SafeAreaにも背景画像が出た!!

 

Listの背景を透明にしたい

Listの背景色で、背景画像が隠れすぎ!背景色を消したい!

SwiftUIは直感的にできてるから、Listのbackgroundに透明色を指定するだけだよね。

List {
  Section {
    Text("パスコードの更新")
    Text("パスコードの削除")
  } header: {
    Text("パスコード")
  }
                
  Section {
    HStack {
      Image("momo")
      Text("パスコードの使い方")
    }
  }
}
.listStyle(.grouped)
.background(Color.clear)  // こうでしょ!

ハズレ!!!!!!!

ここ、仕様変更が何回もされてるようで苦労しました…。

答えは
・「iOS15までは、UITableView.appearance().backgroundColor = .clear、を指定」
・「iOS16以降は、新パラメータ .scrollContentBackground(.hidden)、を指定」
でした。

// iOS15までは、これを追加

init() {
  UITableView.appearance().backgroundColor = .clear
}
// iOS16以降

List {
  Section {
    Text("パスコードの更新")
    Text("パスコードの削除")
  } header: {
    Text("パスコード")
  }
                
  Section {
    HStack {
      Image("momo")
      Text("パスコードの使い方")
    }
  }
}
.listStyle(.grouped)
.scrollContentBackground(.hidden)  // これを指定

できた! けど、Listのbackgroundで指定させて!!!!!

上記のiOS16用のコードはiOS16未満で、そんなパラメータ無いよ!ってエラーになるので次のように書くとOKです。

struct ListBackgroundModifier: ViewModifier {
  @ViewBuilder
  func body(content: Content) -> some View {
    if #available(iOS 16.0, *) {
      content
        .scrollContentBackground(.hidden)

    } else {
      content
    }
  }
}

....


List {
 // hoge
}
.listStyle(.grouped)
.modifier(ListBackgroundModifier()) // 背景透明を指定

 

StackOverflowに、iOS16以降は、UICollectionにextensionを足すっていう方法が一番上に来てて、これだ!って思って使ったのですが…

これを使うと、Listの罫線が消えるし、他の画面でUICollectionViewと使っている場合、UICollectionViewCellの背景色が指定できなくなって大混乱を巻き起こすので絶対使わないようにしてください。(使って大混乱した)

extension UICollectionReusableView {
  override open var backgroundColor: UIColor? {
    get { .clear }
    set {}
  }
}

struct ContentView: View {
    init() {
        UICollectionView.appearance().backgroundColor = .clear
    }

....

参考:
https://stackoverflow.com/questions/72649907/ios-16-swiftui-list-background

 

まだチョロチョロあるのですが、今回はこの辺にしておきます!

SwiftUIはiOS13から登場しましたが、iOS14で強化され、破壊的変更を加えながら、iOS16で大きな機能強化が行われました。

現状、iOS17もリリースされ、iOS14以降は切っていいかなと思いますが、iOS15はまだ残しておきたい所です。

しかし、iOS15以下とiOS16で動作が違う部分がチョロチョロ出てきたので表示確認は必ずiOS15、16両方で行うことを強くオススメします。

 

おまけ

サブスクの成約率は一般的に1%が平均と言われていますが、今回SwiftUIを導入してみたアプリは、DAU比で成約率が13%と、非常に高くなっています。

どんな機能的な工夫をしたのかは、ココに
https://zenn.dev/ataka/articles/dd9a58f02f373c465532

どうすれば売れるサブスクにできるのかは、ココに
https://zenn.dev/ataka/articles/22a2b40aaa34c1

書いているのでお時間がありましたら、よろしくおねがいします。

 

SwiftUIについてはコチラの記事もよろしくおねがいします。

課金画面のUIをSwiftUIで作る手順を紹介SwiftUIでサブスクの購入画面を組み上げてみたので、どうやってSwiftUIで作っていくのかを解説します。 完成品はこんな感じ...