- 投稿日:2019-07-19T23:17:48+09:00
AppDelegateでのPUSH通知の受信処理
はじめに
iOSでPUSH通知を受信する時のAppDelegateのメソッドをまとめておきます。
環境
- Xcode : 10.2.1
- ios : 12.3.1
AppDelegateでのPUSH通知の種類
iosアプリがPUSH通知を受信する種類は以下の3つがあります。
以下ではそれぞれを説明します。
- アプリが起動していない時にPUSH通知を受信した場合
- アプリが起動している時にPUSH通知を受信した場合
- バックグランドPUSH通知を受信した場合
アプリが起動していない時にPUSH通知を受信した場合
アプリが起動していない時にPUSH通知を受信した場合は、通知センター等に通知されます。この通知をタップするとアプリが起動されます。アプリが起動される時には、AppDelegateの以下のメソッドが呼ばれます。
userNotificationCenter(_:didReceive:withCompletionHandler:)アプリが起動している時にPUSH通知を受信した場合
アプリが起動している時(Foreground)は、AppDelegateの以下のメッセージが呼ばれます。通知センターには表示されません。
userNotificationCenter(_:willPresent:withCompletionHandler:)バックグランドPUSH通知を受信した場合
画面にPUSH通知表示せず、アプリが起動していない状態でも、PUSH通知を受信した時に30秒間だけアプリを起動して処理を実行できるバックグランド通知があります。通知メッセージには
content-availableを入れます。サーバ側からのPUSH通知に関しては、以前の記事を参照ください。バックグランドPUSH通知を受信するためには、Xcodeのプロジェクトで、以下のようにCapabilityでRemote Notificationsにチェックを入れます。これによって、バックグランドPUSH通知を受信することができます。
バックグランド通知を受信した場合は、AppDelegateの以下のメソッドが呼ばれます。
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)参考文献
- 投稿日:2019-07-19T20:02:08+09:00
iOS 13からのダークモード対応のコツ
iOS 13からのダークモード対応について、具体的な実装方法、注意点、現行バージョンと並行開発するコツなどをまとめてみました。
定義済みのダイナミックシステムカラーについて
これまで色の指定は
UIColor.whiteのように静的な色を使っていましたが、iOS 13からはライト・ダークのモードに応じて変化するダイナミックカラーという仕組みが導入されました。システムで定義されたダイナミックカラーを
UIColorから利用することができます。label,systemBackgroundのように用途に応じた名前で定義されるものはセマンティックカラーとも呼ばれます。システムと同じ色を使うのが適当なケースではこれらを利用しましょう。ダークモードかどうかを判定する
ダークモードかどうかは
UITraitCollectionのプロパティuserInterfaceStyleで判定できます。UIViewやUIViewControllerはtraitCollectionプロパティからUITraitCollectionのインスタンスにアクセスできます。また、以下のようなエクステンションを書いておくと便利だと思います。 availability の判定をあちこちに書かずに済ませるためにも有効です。
extension UITraitCollection { public static var isDarkMode: Bool { if #available(iOS 13, *), current.userInterfaceStyle == .dark { return true } return false } }ただし、以下の項目で説明しますが、実際にこのコードを利用するシーンはあまりないと思います。
ダイナミックカラーを生成する
ビューの生成時などにダークモードかどうかを判定して静的な色をセットしても、アプリ動作中にモードが切り替わったときに追随できなくなってしまいます。
モード切り替え時に追随できるようにするにはダイナミックカラーを利用します。
UIColorにクロージャーを渡して、クロージャーの中でモードごとの色を返すようにすることで、動的な色を作れるイニシャライザが新たに導入されました。UIColorが直接利用できる場面ではこれを使うのがいいでしょう。let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in if traitCollection.userInterfaceStyle == .dark { return .black } else { return .white } }こちらも以下のようなメソッドを
UIColorのエクステンションに書いておくと便利です。/// ライト/ダーク用の色を受け取ってDynamic Colorを作って返す public class func dynamicColor(light: UIColor, dark: UIColor) -> UIColor { if #available(iOS 13, *) { return UIColor { (traitCollection) -> UIColor in if traitCollection.userInterfaceStyle == .dark { return dark } else { return light } } } return light }カスタムのセマンティックカラーを定義する
アプリ独自のカラーセットをセマンティックカラーとして
UIColorのエクステンションに定義して、実際に色を指定する箇所ではこちらを利用するのがいいと思います。ダークモード導入以前からこのように定義しているアプリも多いのではないでしょうか。ダイナミックカラーを返すようにすることで、モード切り替え時も自動的に色が置き換わるようになります。
/// 背景色 public static var background: UIColor { return dynamicColor( light: .white dark: .black ) } /// メインのテキストの色 public static var text: UIColor { return dynamicColor( light: UIColor(hex: 0x212121), dark: UIColor(hex: 0xF5F5F5) ) }※
init(hex:)も独自のエクステンションです。deployment targetがiOS 11以上であればAsset CatalogでColor Setを定義することができます(画像はMacアプリの例ですがiOSも同様です)。右ペインのAppearacesから「Any, Dark」を選択するとダークモード用の色を定義できます。
Supporting Dark Mode in Your Interface よりStoryboardから指定したり、コードから
init(named:)で指定すればダイナミックカラーとして利用できます。ダイナミックカラーを直接使えないケース
色指定に
CGColorを使わないといけないケースや、withAlphaComponent(_:)でalphaをかけているケースなどは、ダイナミックカラーを直接利用できないので注意が必要です。このようなケースでは、モード切り替え時に呼ばれるメソッドの中で色の指定をしておくことで、適切にアップデートできます。モード切り替え時に呼ばれるメソッドは以下の通りです。
UIView
traitCollectionDidChange(_:)layoutSubviews()draw(_:)updateConstraints()tintColorDidChange()UIViewController
traitCollectionDidChange(_:)updateViewConstraints()viewWillLayoutSubviews()viewDidLayoutSubviews()UIPresentationController
traitCollectionDidChange(_:)containerViewWillLayoutSubviews()containerViewDidLayoutSubviews()
CGColorで色を指定する例override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) textLabel.layer.borderColor = UIColor.border.cgColor }現行バージョン向けと並行開発する
iOS 13のリリースまでまだ時間がありますので、それまでに新規の画面開発や既存画面の大幅な改修などが入ることもあると思います。その場合、今のうちにダークモード対応のことを考慮しておきたいので、現行バージョンのXcode 10用ブランチとiOS 13対応をしているXcode 11用ブランチで同一のコードが動く状態にしました。
例えば上に挙げたdynamic colorを返すメソッドは、以下のように
#if swift(>=5.1)を挟むことによってどちらの環境でもビルドして動作させることができます。public class func dynamicColor(light: UIColor, dark: UIColor) -> UIColor { #if swift(>=5.1) if #available(iOS 13, *) { return UIColor { (traitCollection) -> UIColor in if traitCollection.userInterfaceStyle == .dark { return dark } else { return light } } } #endif return light }参考リンク
- 投稿日:2019-07-19T19:19:35+09:00
【備忘録】Swift4.0以降で音を鳴らす
音を鳴らしたいだけ
ある教材でアプリ開発を学んでいたら、音を鳴らすところがうまくいかない。そりゃそうだ。その教材、Swift3.0なんだもの。まぁ、私が過去に書いた4.2を引っ張ってきてぶち込んだから問題なくできたけど。。。こんな感じで今後も「音を鳴らすだけ」のためにわざわざ過去のProject開いて確認するのめんどいな、、と思ってここに書いておく。
こんな感じ
ViewController.swiftimport UIKit import AVFoundation class ViewController: UIViewController { var audioPlayer: AVAudioPlayer! @IBAction func btn(_ sender: UIButton) { playSound(name: "yourFileName") } override func viewDidLoad() { super.viewDidLoad() } } extension ViewController: AVAudioPlayerDelegate{ func playSound(name: String){ guard let path = Bundle.main.path(forResource: name, ofType: "mp3") else { print("I cannot find any music file...") return } do{ audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) audioPlayer.delegate = self audioPlayer.play() } catch { } } }yourFileName.mp3っていうファイルを入れておくと、ボタンを押した時にそれが流れる。
- 投稿日:2019-07-19T17:10:26+09:00
オプショナルのお勉強
Swift超初心者がひとまず10分勉強した。
オプショナルの入り口の備忘録。
ちょっとずつ付け足すつもり。*.swift// nilを取り扱えない通常の変数を宣言 var suji_A: Int8 = 10 var moji_A: String = "moji_A" // nilを取り扱えるオプショナル変数を宣言 var suji_B: Int8? = nil var moji_B: String? = nil // 型名をデバッグ表示 print(type(of: suji_A)) // Int8 print(type(of: moji_A)) // String print(type(of: suji_B)) // Optional<Int8> print(type(of: moji_B)) // Optional<String> // エラー(Value of optional type 'Int8?' must be unwrapped to a value of type 'Int8') ////print( suji_A + suji_B ) // エラー(alue of optional type 'String?' must be unwrapped to a value of type 'String') ////print( moji_A + moji_B ) // アンラップ // nilなので実行時エラー // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). // printの結果(Fatal error: Unexpectedly found nil while unwrapping an Optional value) ////print( suji_A + suji_B! ) ////print( moji_A + moji_B! ) // nil解消 suji_B = 20 moji_B = "moji_B" // エラー((Value of optional type 'Int8?' must be unwrapped to a value of type 'Int8') ////print( suji_A + suji_B ) // エラー((Value of optional type 'String?' must be unwrapped to a value of type 'String') ////print( moji_A + moji_B ) // アンラップ print( suji_A + suji_B! ) // 30 print( moji_A + moji_B! ) // moji_Amoji_B // おまけ(マークアップコメント) // マークダウン形式でコメントをつけることができる。 // 表示の切り替えは、 // [Editor] - [Show Raw Markup] /*: - - - # 設計文書(クラス設計) ## マークアップコメント 1. Step1 1. Step2 1. Step3 * 箇条書き1 * 箇条書き2 * 箇条書き2 ## 参考記事 [XcodeでSwiftの型を把握する](https://qiita.com/bannzai/items/bc6e286dc590e30d3714) [どこよりも分かりやすいSwiftの"?"と"!"](https://qiita.com/maiki055/items/b24378a3707bd35a31a8) */
- 投稿日:2019-07-19T14:32:34+09:00
Swift ジェネリッククラスでシングルトンを実現する
Swiftでシングルトンを実現する場合、以下のようになりますが
class foo { static let shared = foo() private init(){} }ジェネリッククラスにするとエラーになってしまいます。
class foo<T> { static let shared = foo<T>() // Static stored properties not supported in generic types private init(){} }コンピューテッドプロパティにすれば回避できますが、アクセスする度にインスタンスを生成してしまいます。インスタンスはできるかぎり再利用したいです。
class foo<T> { static var shared: foo<T> { return foo<T>() } private init(){} }そこで、いささか不格好ですがグローバル空間にインスタンスを保存する作戦に至りました。さいわいにもSwiftはfileprivateという便利なアクセスレベルがあるので、グローバル空間もそこまで汚れません。
fileprivate var instances : [String: AnyObject] = [:] class foo<T> { static var shared : foo<T> { let type = String(describing: T.self) if let instance = instances[type] as? foo<T> { return instance } let instance = foo<T>() instances[type] = instance return instance } private init() { } }もっとイケてる方法がありましたら教えてください。
- 投稿日:2019-07-19T12:18:07+09:00
Kingfisher v5.7.0対応の既存Swiftソースの修正
Kingfisherバージョンを最新のv5.7.0にあげると既存いくつのメソットが
deprecatedされたので、その対応をメモして置きます。 Swiftバージョンは4.21) KingfisherManager.shared.cache.retrieveImageInDiskCache() メソット
ImageDownloader.swiftlet diskImage = KingfisherManager.shared.cache.retrieveImageInDiskCache(forKey:URLString)以下のようにクローチャでImageを読み込むように
ImageDownloader.swiftvar diskImage : UIImage? = nil KingfisherManager.shared.cache.retrieveImageInDiskCache(forKey:URLString) { result in switch result { case .success(let value): if let image = value?.alwaysOriginal { diskImage = image } case .failure(let error): print("Error: \(error)") } }2) KingfisherManager.shared.cache.store() メソット
ImageDownloader.swiftKingfisherManager.shared.cache.store(roundedImage, original: originalData, forKey: URLString, toDisk: true, completionHandler: { print("キャッシュ後の処理") })以下のようにクローチャでそれぞれメモリキャッシュとハードディスクキャッシュ後の処理をそれぞれ対応できるように
ImageDownloader.swiftlet parsedOptions = KingfisherParsedOptionsInfo([]) KingfisherManager.shared.cache.store(roundedImage, original: originalData, forKey: URLString, options: parsedOptions, toDisk: true, completionHandler: { result in switch result.memoryCacheResult { case .success(): print("メモリキャッシュ成功後の処理") } switch result.diskCacheResult { case .success(): print("ハードディスクキャッシュ成功後の処理") case .failure(let error): print("Error: \(error)") } })3) KingfisherManager.shared.downloader.downloadImage() メソット
ImageDownloader.swiftKingfisherManager.shared.downloader.downloadImage(with: URL, options: optionInfo, progressBlock: progressBlock) { (image, error, imageURL, originalData) -> () in if let image = image, let originalData = originalData { print("ダウンロードされたImageの処理") } }以下のようにクローチャのresult結果でそれぞれのImageとDataを取得するように
ImageDownloader.swiftKingfisherManager.shared.downloader.downloadImage(with: URL, options: optionInfo, progressBlock: progressBlock) { result in switch result { case .success(let value): let image = value.image let originalData = value.originalData print("ダウンロードされたImageの処理") case .failure(let error): print("Error: \(error)") } }詳しい説明になりませんが、助けになったらと思います。
間違い所がありましたら、ご指摘くださいますように。参考URL:
https://github.com/onevcat/Kingfisher/wiki/Kingfisher-5.0-Migration-Guide
- 投稿日:2019-07-19T08:21:17+09:00
Xcode 11 beta 3 → beta 4 での変更点
はじめに
Xcode 11 beta 4が公開されたので、SwiftUI Tutorialsで確認できたbeta 3→beta 4での変更点について、以下にまとめていきます。
(文末にbeta 2→beta 3での変更点も記載しています。)beta 3 → beta 4 での変更点
本記事内で触れていない変更点などもありますので、詳細を知りたい場合は公式のリリースノートを参照してみてください。
- iOS & iPadOS 13 Beta 4 Release NotesBindableObjectの仕様変更
The BindableObject protocol’s requirement is now willChange instead of didChange, and should now be sent before the object changes rather than after it changes. This change allows for improved coalescing of change notifications. (51580731)
didChangeがwillChangeに変更されました。
仕様自体もオブジェクトが変更された後ではなく、変更される前に送信されるようになり、変更通知の統合が改善されたようです。Landmarks/Models/UserData.swift
final class UserData: BindableObject { - let didChange = PassthroughSubject<UserData, Never>() + let willChange = PassthroughSubject<Void, Never>() var showFavoritesOnly = false { - didSet { - didChange.send(self) + willSet { + willChange.send() } } var landmarks = landmarkData { - didSet { - didChange.send(self) + willSet { + willChange.send() } } }Textの仕様変更
The color(:) modifier for Text is renamed foregroundColor(:) for consistency with the more general foregroundColor(_:) view modifier. (50391847)
colorがforegroundColorに変名されました。Landmarks/CategoryRow.swift
Text(landmark.name) - .color(.primary) + .foregroundColor(.primary) .font(.caption)Collectionの仕様変更
The identified(by:) method on the Collection protocol is deprecated in favor of dedicated init(
selection:rowContent:) and init(
content:) initializers. (52976883)
identified(by:)メソッドがdeprecatedに変更され、ForEach(_:id:)またはList(_:id:)の利用が推奨となりました。Landmarks/Home.swift
- ForEach(categories.keys.sorted().identified(by: \.self)) { key in + ForEach(categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: self.categories[key]!) } .listRowInsets(EdgeInsets())DatePickerの仕様変更
'init(_:minimumDate:maximumDate:displayedComponents:)' is deprecated: DatePicker labels are now required
labelを引数に取らない初期化メソッドがdeprecatedになりました。
Landmarks/Profiles/ProfileEditor.swift
DatePicker( - $profile.goalDate, + "Goal Date", + selection: $profile.goalDate, minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate), maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate), displayedComponents: .date)なお、beta 4のSwiftUI Tutorialでは、仕様変更の対応以外に次のリファクタリングも行われていました。
+ var dateRange: ClosedRange<Date> { + let min = Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate)! + let max = Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate)! + return min...max + } DatePicker( - $profile.goalDate, - minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate), - maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate), + "Goal Date", + selection: $profile.goalDate, + in: dateRange, displayedComponents: .date)AnyTransitionの仕様変更
Cannot invoke 'scale' with no arguments
scaleがメソッドからプロパティに変更されました。Landmarks/HikeView.swift
- let removal = AnyTransition.scale() + let removal = AnyTransition.scalepresentation(_:), Sheet, Modal, PresentationLink
Added improved presentation modifiers: sheet(isPresented:onDismiss:content:), actionSheet(isPresented:content:), and alert(isPresented:content:) — along with isPresented in the environment — replace the existing presentation(_:), Sheet, Modal, and PresentationLink types. (52075730)
presentation(_:), Sheet, Modal, PresentationLinkがdeprecatedになりました。
併せて、これらの機能に相当する機能がViewのメソッドとして追加されています。
- sheet(isPresented:onDismiss:content:)
- actionSheet(isPresented:content:)
- alert(isPresented:content:)
SwiftUI Tutorialsでは
PresentationLinkが利用されていましたが、sheet(isPresented:onDismiss:content:)に変更されていました。Landmarks/Home.swift
+ @State var showingProfile = false + + var profileButton: some View { + Button(action: { self.showingProfile.toggle() }) { + Image(systemName: "person.crop.circle") + .imageScale(.large) + .accessibility(label: Text("User Profile")) + .padding() + } + } + var body: some View { NavigationView { List { FeaturedLandmarks(landmarks: featured) .scaledToFill() .frame(height: 200) .clipped() .listRowInsets(EdgeInsets()) ForEach(categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: self.categories[key]!) } .listRowInsets(EdgeInsets()) NavigationLink(destination: LandmarkList()) { Text("See All") } } .navigationBarTitle(Text("Featured")) - .navigationBarItems(trailing: - PresentationLink(destination: ProfileHost()) { - Image(systemName: "person.crop.circle") - .imageScale(.large) - .accessibility(label: Text("User Profile")) - .padding() - } - ) + .navigationBarItems(trailing: profileButton) + .sheet(isPresented: $showingProfile) { + ProfileHost() + } } }Animationの仕様変更
Updated the APIs for creating animations. The basic animations are now named after the curve type — such as linear and easeInOut. The interpolation-based spring(mass:stiffness:damping:initialVelocity:) animation is now interpolatingSpring(mass:stiffness:damping:initialVelocity:), and fluidSpring(stiffness:dampingFraction:blendDuration:timestep:idleThreshold:) is now spring(response:dampingFraction:blendDuration:) or interactiveSpring(response:dampingFraction:blendDuration:), depending on whether or not the animation is driven interactively. (50280375)
spring(mass:stiffness:damping:initialVelocity:), fluidSpring(stiffness:dampingFraction:blendDuration:timestep:idleThreshold:)がdeprecatedになりました。
併せて、これらのメソッドに相当するメソッドが新たに追加されています。
- interpolatingSpring(mass:stiffness:damping:initialVelocity:)
- spring(response:dampingFraction:blendDuration:)
- interactiveSpring(response:dampingFraction:blendDuration:)
Landmarks/Supporting View/GraphCapsule.swift
var animation: Animation { - Animation.spring(initialVelocity: 5) + Animation.spring(dampingFraction: 0.5) .speed(2) .delay(0.03 * Double(index)) }その他の変更点
- リファクタリング
- LandmarkListへの遷移元(Home.swift)でNavigationViewが生成されているため、LandmarkListではNavigationViewが削除されました。
Landmarks/LandmarkList.swift
var body: some View { - NavigationView { - List { - Toggle(isOn: $userData.showFavoritesOnly) { - Text("Show Favorites Only") - } - - ForEach(userData.landmarks) { landmark in - if !self.userData.showFavoritesOnly || landmark.isFavorite { - NavigationLink( - destination: LandmarkDetail(landmark: landmark) - .environmentObject(self.userData) - ) { - LandmarkRow(landmark: landmark) - } + List { + Toggle(isOn: $userData.showFavoritesOnly) { + Text("Show Favorites Only") + } + + ForEach(userData.landmarks) { landmark in + if !self.userData.showFavoritesOnly || landmark.isFavorite { + NavigationLink( + destination: LandmarkDetail(landmark: landmark) + ) { + LandmarkRow(landmark: landmark) } } } - .navigationBarTitle(Text("Landmarks"), displayMode: .large) } + .navigationBarTitle(Text("Landmarks"), displayMode: .large) }beta 2 → beta 3 での変更点
画面遷移に関する構造体の仕様変更
構造体名が
XXXButtonからXXXLinkに変更されました。
SwiftUI Tutorials では以下の2つが利用されており、名前が変更されています。
beta 2 beta 3 NavigationButton NavigationLink PresentationButton PresentationLink Landmarks/Home.swift
- NavigationButton(destination: LandmarkList()) { + NavigationLink(destination: LandmarkList()) { Text("See All") } } .navigationBarTitle(Text("Featured")) .navigationBarItems(trailing: - PresentationButton(destination: ProfileHost()) { + PresentationLink(destination: ProfileHost()) { Image(systemName: "person.crop.circle") .imageScale(.large) .accessibility(label: Text("User Profile"))ScrollViewの仕様変更
beta 3ではAxis.SetとshowsIndicatorsの組合せで振る舞いを指定するように仕様変更されているのですが、beta 2では指定できていたBounceの指定や細かい組合せが実現できないように見えます。
このため、beta 4でまた仕様の追加・変更があるかもしれません。
beta 2のScrollViewのinit仕様public init(isScrollEnabled: Bool = true, alwaysBounceHorizontal: Bool = false, alwaysBounceVertical: Bool = false, showsHorizontalIndicator: Bool = true, showsVerticalIndicator: Bool = true, content: () -> Content)
beta 3のScrollViewのinit仕様public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, content: () -> Content)Landmarks/CategoryRow.swift
- ScrollView(showsHorizontalIndicator: false) { + ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 0) { ForEach(self.items.identified(by: \.name)) { landmark inTextFieldの仕様変更
beta 2での初期化方法がdeprecatedに変更され、beta 3では新たな初期化方法が追加されました。Landmarks/Profiles/ProfileEditor.swift
HStack { Text("Username").bold() Divider() - TextField($profile.username) + TextField("Username", text: $profile.username) }UIWindowの仕様変更
SwiftUI Tutorialsで利用されていた初期化方法が、
beta2とbeta3では異なりました。
beta 2では、UIViewから継承したinitが利用されていましたが、beta3ではUIWindowとして定義されているinitが利用されており、より良い方法に変更されたようです。Landmarks/SceneDelegate.swift
- let window = UIWindow(frame: UIScreen.main.bounds) - window.rootViewController = UIHostingController(rootView: CategoryHome().environmentObject(UserData())) - self.window = window - window.makeKeyAndVisible() + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: CategoryHome().environmentObject(UserData())) + self.window = window + window.makeKeyAndVisible() + }最後に
上記内容は、SwiftUI Tutorialsの変更点を元に調査を行い変更点をまとめました。
最新のbeta(現時点ではXcode 11.0 beta3 - Xcode 11.0 beta 4)の詳細な変更点につきましては、以下のApple公式サイトで確認することが可能ですので、興味がある方は確認してみてください。
https://developer.apple.com/documentation/swiftui?changes=latest_beta
- 投稿日:2019-07-19T08:21:17+09:00
SwiftUI(Xcode 11) beta 3 → beta 4 での変更点
はじめに
Xcode 11 beta 4が公開されたので、SwiftUI Tutorialsで確認できたSwiftUIのbeta 3→beta 4での変更点について、以下にまとめていきます。
(文末にbeta 2→beta 3での変更点も記載しています。)beta 3 → beta 4 での変更点
本記事内で触れていない変更点などもありますので、詳細を知りたい場合は公式のリリースノートを参照してみてください。
- iOS & iPadOS 13 Beta 4 Release NotesBindableObjectの仕様変更
The BindableObject protocol’s requirement is now willChange instead of didChange, and should now be sent before the object changes rather than after it changes. This change allows for improved coalescing of change notifications. (51580731)
didChangeがwillChangeに変更されました。
仕様自体もオブジェクトが変更された後ではなく、変更される前に送信されるようになり、変更通知の統合が改善されたようです。Landmarks/Models/UserData.swift
final class UserData: BindableObject { - let didChange = PassthroughSubject<UserData, Never>() + let willChange = PassthroughSubject<Void, Never>() var showFavoritesOnly = false { - didSet { - didChange.send(self) + willSet { + willChange.send() } } var landmarks = landmarkData { - didSet { - didChange.send(self) + willSet { + willChange.send() } } }Textの仕様変更
The color(:) modifier for Text is renamed foregroundColor(:) for consistency with the more general foregroundColor(_:) view modifier. (50391847)
colorがforegroundColorに変名されました。Landmarks/CategoryRow.swift
Text(landmark.name) - .color(.primary) + .foregroundColor(.primary) .font(.caption)Collectionの仕様変更
The identified(by:) method on the Collection protocol is deprecated in favor of dedicated init(
selection:rowContent:) and init(
content:) initializers. (52976883)
identified(by:)メソッドがdeprecatedに変更され、ForEach(_:id:)またはList(_:id:)の利用が推奨となりました。Landmarks/Home.swift
- ForEach(categories.keys.sorted().identified(by: \.self)) { key in + ForEach(categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: self.categories[key]!) } .listRowInsets(EdgeInsets())DatePickerの仕様変更
'init(_:minimumDate:maximumDate:displayedComponents:)' is deprecated: DatePicker labels are now required
labelを引数に取らない初期化メソッドがdeprecatedになりました。
Landmarks/Profiles/ProfileEditor.swift
DatePicker( - $profile.goalDate, + "Goal Date", + selection: $profile.goalDate, minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate), maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate), displayedComponents: .date)なお、beta 4のSwiftUI Tutorialでは、仕様変更の対応以外に次のリファクタリングも行われていました。
+ var dateRange: ClosedRange<Date> { + let min = Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate)! + let max = Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate)! + return min...max + } DatePicker( - $profile.goalDate, - minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate), - maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate), + "Goal Date", + selection: $profile.goalDate, + in: dateRange, displayedComponents: .date)AnyTransitionの仕様変更
Cannot invoke 'scale' with no arguments
scaleがメソッドからプロパティに変更されました。Landmarks/HikeView.swift
- let removal = AnyTransition.scale() + let removal = AnyTransition.scalepresentation(_:), Sheet, Modal, PresentationLink
Added improved presentation modifiers: sheet(isPresented:onDismiss:content:), actionSheet(isPresented:content:), and alert(isPresented:content:) — along with isPresented in the environment — replace the existing presentation(_:), Sheet, Modal, and PresentationLink types. (52075730)
presentation(_:), Sheet, Modal, PresentationLinkがdeprecatedになりました。
併せて、これらの機能に相当する機能がViewのメソッドとして追加されています。
- sheet(isPresented:onDismiss:content:)
- actionSheet(isPresented:content:)
- alert(isPresented:content:)
SwiftUI Tutorialsでは
PresentationLinkが利用されていましたが、sheet(isPresented:onDismiss:content:)に変更されていました。Landmarks/Home.swift
+ @State var showingProfile = false + + var profileButton: some View { + Button(action: { self.showingProfile.toggle() }) { + Image(systemName: "person.crop.circle") + .imageScale(.large) + .accessibility(label: Text("User Profile")) + .padding() + } + } + var body: some View { NavigationView { List { FeaturedLandmarks(landmarks: featured) .scaledToFill() .frame(height: 200) .clipped() .listRowInsets(EdgeInsets()) ForEach(categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: self.categories[key]!) } .listRowInsets(EdgeInsets()) NavigationLink(destination: LandmarkList()) { Text("See All") } } .navigationBarTitle(Text("Featured")) - .navigationBarItems(trailing: - PresentationLink(destination: ProfileHost()) { - Image(systemName: "person.crop.circle") - .imageScale(.large) - .accessibility(label: Text("User Profile")) - .padding() - } - ) + .navigationBarItems(trailing: profileButton) + .sheet(isPresented: $showingProfile) { + ProfileHost() + } } }Animationの仕様変更
Updated the APIs for creating animations. The basic animations are now named after the curve type — such as linear and easeInOut. The interpolation-based spring(mass:stiffness:damping:initialVelocity:) animation is now interpolatingSpring(mass:stiffness:damping:initialVelocity:), and fluidSpring(stiffness:dampingFraction:blendDuration:timestep:idleThreshold:) is now spring(response:dampingFraction:blendDuration:) or interactiveSpring(response:dampingFraction:blendDuration:), depending on whether or not the animation is driven interactively. (50280375)
spring(mass:stiffness:damping:initialVelocity:), fluidSpring(stiffness:dampingFraction:blendDuration:timestep:idleThreshold:)がdeprecatedになりました。
併せて、これらのメソッドに相当するメソッドが新たに追加されています。
- interpolatingSpring(mass:stiffness:damping:initialVelocity:)
- spring(response:dampingFraction:blendDuration:)
- interactiveSpring(response:dampingFraction:blendDuration:)
Landmarks/Supporting View/GraphCapsule.swift
var animation: Animation { - Animation.spring(initialVelocity: 5) + Animation.spring(dampingFraction: 0.5) .speed(2) .delay(0.03 * Double(index)) }その他の変更点
- リファクタリング
- LandmarkListへの遷移元(Home.swift)でNavigationViewが生成されているため、LandmarkListではNavigationViewが削除されました。
Landmarks/LandmarkList.swift
var body: some View { - NavigationView { - List { - Toggle(isOn: $userData.showFavoritesOnly) { - Text("Show Favorites Only") - } - - ForEach(userData.landmarks) { landmark in - if !self.userData.showFavoritesOnly || landmark.isFavorite { - NavigationLink( - destination: LandmarkDetail(landmark: landmark) - .environmentObject(self.userData) - ) { - LandmarkRow(landmark: landmark) - } + List { + Toggle(isOn: $userData.showFavoritesOnly) { + Text("Show Favorites Only") + } + + ForEach(userData.landmarks) { landmark in + if !self.userData.showFavoritesOnly || landmark.isFavorite { + NavigationLink( + destination: LandmarkDetail(landmark: landmark) + ) { + LandmarkRow(landmark: landmark) } } } - .navigationBarTitle(Text("Landmarks"), displayMode: .large) } + .navigationBarTitle(Text("Landmarks"), displayMode: .large) }beta 2 → beta 3 での変更点
画面遷移に関する構造体の仕様変更
構造体名が
XXXButtonからXXXLinkに変更されました。
SwiftUI Tutorials では以下の2つが利用されており、名前が変更されています。
beta 2 beta 3 NavigationButton NavigationLink PresentationButton PresentationLink Landmarks/Home.swift
- NavigationButton(destination: LandmarkList()) { + NavigationLink(destination: LandmarkList()) { Text("See All") } } .navigationBarTitle(Text("Featured")) .navigationBarItems(trailing: - PresentationButton(destination: ProfileHost()) { + PresentationLink(destination: ProfileHost()) { Image(systemName: "person.crop.circle") .imageScale(.large) .accessibility(label: Text("User Profile"))ScrollViewの仕様変更
beta 3ではAxis.SetとshowsIndicatorsの組合せで振る舞いを指定するように仕様変更されているのですが、beta 2では指定できていたBounceの指定や細かい組合せが実現できないように見えます。
このため、beta 4でまた仕様の追加・変更があるかもしれません。
beta 2のScrollViewのinit仕様public init(isScrollEnabled: Bool = true, alwaysBounceHorizontal: Bool = false, alwaysBounceVertical: Bool = false, showsHorizontalIndicator: Bool = true, showsVerticalIndicator: Bool = true, content: () -> Content)
beta 3のScrollViewのinit仕様public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, content: () -> Content)Landmarks/CategoryRow.swift
- ScrollView(showsHorizontalIndicator: false) { + ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 0) { ForEach(self.items.identified(by: \.name)) { landmark inTextFieldの仕様変更
beta 2での初期化方法がdeprecatedに変更され、beta 3では新たな初期化方法が追加されました。Landmarks/Profiles/ProfileEditor.swift
HStack { Text("Username").bold() Divider() - TextField($profile.username) + TextField("Username", text: $profile.username) }UIWindowの仕様変更
SwiftUI Tutorialsで利用されていた初期化方法が、
beta2とbeta3では異なりました。
beta 2では、UIViewから継承したinitが利用されていましたが、beta3ではUIWindowとして定義されているinitが利用されており、より良い方法に変更されたようです。Landmarks/SceneDelegate.swift
- let window = UIWindow(frame: UIScreen.main.bounds) - window.rootViewController = UIHostingController(rootView: CategoryHome().environmentObject(UserData())) - self.window = window - window.makeKeyAndVisible() + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: CategoryHome().environmentObject(UserData())) + self.window = window + window.makeKeyAndVisible() + }最後に
上記内容は、SwiftUI Tutorialsの変更点を元に調査を行い変更点をまとめました。
最新のbeta(現時点ではXcode 11.0 beta3 - Xcode 11.0 beta 4)の詳細な変更点につきましては、以下のApple公式サイトで確認することが可能ですので、興味がある方は確認してみてください。
https://developer.apple.com/documentation/swiftui?changes=latest_beta
- 投稿日:2019-07-19T08:21:17+09:00
SwiftUI - Xcode 11 beta 4 での変更点
はじめに
Xcode 11 beta 4が公開されたので、SwiftUI Tutorialsで確認できたSwiftUIのbeta 3→beta 4での変更点について、以下にまとめていきます。
(文末にbeta 2→beta 3での変更点も記載しています。)beta 3 → beta 4 での変更点
本記事内で触れていない変更点などもありますので、詳細を知りたい場合は公式のリリースノートを参照してみてください。
- iOS & iPadOS 13 Beta 4 Release NotesBindableObjectの仕様変更
The BindableObject protocol’s requirement is now willChange instead of didChange, and should now be sent before the object changes rather than after it changes. This change allows for improved coalescing of change notifications. (51580731)
didChangeがwillChangeに変更されました。
仕様自体もオブジェクトが変更された後ではなく、変更される前に送信されるようになり、変更通知の統合が改善されたようです。Landmarks/Models/UserData.swift
final class UserData: BindableObject { - let didChange = PassthroughSubject<UserData, Never>() + let willChange = PassthroughSubject<Void, Never>() var showFavoritesOnly = false { - didSet { - didChange.send(self) + willSet { + willChange.send() } } var landmarks = landmarkData { - didSet { - didChange.send(self) + willSet { + willChange.send() } } }Textの仕様変更
The color(:) modifier for Text is renamed foregroundColor(:) for consistency with the more general foregroundColor(_:) view modifier. (50391847)
colorがforegroundColorに変名されました。Landmarks/CategoryRow.swift
Text(landmark.name) - .color(.primary) + .foregroundColor(.primary) .font(.caption)Collectionの仕様変更
The identified(by:) method on the Collection protocol is deprecated in favor of dedicated init(
selection:rowContent:) and init(
content:) initializers. (52976883)
identified(by:)メソッドがdeprecatedに変更され、ForEach(_:id:)またはList(_:id:)の利用が推奨となりました。Landmarks/Home.swift
- ForEach(categories.keys.sorted().identified(by: \.self)) { key in + ForEach(categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: self.categories[key]!) } .listRowInsets(EdgeInsets())DatePickerの仕様変更
'init(_:minimumDate:maximumDate:displayedComponents:)' is deprecated: DatePicker labels are now required
labelを引数に取らない初期化メソッドがdeprecatedになりました。
Landmarks/Profiles/ProfileEditor.swift
DatePicker( - $profile.goalDate, + "Goal Date", + selection: $profile.goalDate, minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate), maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate), displayedComponents: .date)なお、beta 4のSwiftUI Tutorialでは、仕様変更の対応以外に次のリファクタリングも行われていました。
+ var dateRange: ClosedRange<Date> { + let min = Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate)! + let max = Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate)! + return min...max + } DatePicker( - $profile.goalDate, - minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate), - maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate), + "Goal Date", + selection: $profile.goalDate, + in: dateRange, displayedComponents: .date)AnyTransitionの仕様変更
Cannot invoke 'scale' with no arguments
scaleがメソッドからプロパティに変更されました。Landmarks/HikeView.swift
- let removal = AnyTransition.scale() + let removal = AnyTransition.scalepresentation(_:), Sheet, Modal, PresentationLink
Added improved presentation modifiers: sheet(isPresented:onDismiss:content:), actionSheet(isPresented:content:), and alert(isPresented:content:) — along with isPresented in the environment — replace the existing presentation(_:), Sheet, Modal, and PresentationLink types. (52075730)
presentation(_:), Sheet, Modal, PresentationLinkがdeprecatedになりました。
併せて、これらの機能に相当する機能がViewのメソッドとして追加されています。
- sheet(isPresented:onDismiss:content:)
- actionSheet(isPresented:content:)
- alert(isPresented:content:)
SwiftUI Tutorialsでは
PresentationLinkが利用されていましたが、sheet(isPresented:onDismiss:content:)に変更されていました。Landmarks/Home.swift
+ @State var showingProfile = false + + var profileButton: some View { + Button(action: { self.showingProfile.toggle() }) { + Image(systemName: "person.crop.circle") + .imageScale(.large) + .accessibility(label: Text("User Profile")) + .padding() + } + } + var body: some View { NavigationView { List { FeaturedLandmarks(landmarks: featured) .scaledToFill() .frame(height: 200) .clipped() .listRowInsets(EdgeInsets()) ForEach(categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: self.categories[key]!) } .listRowInsets(EdgeInsets()) NavigationLink(destination: LandmarkList()) { Text("See All") } } .navigationBarTitle(Text("Featured")) - .navigationBarItems(trailing: - PresentationLink(destination: ProfileHost()) { - Image(systemName: "person.crop.circle") - .imageScale(.large) - .accessibility(label: Text("User Profile")) - .padding() - } - ) + .navigationBarItems(trailing: profileButton) + .sheet(isPresented: $showingProfile) { + ProfileHost() + } } }Animationの仕様変更
Updated the APIs for creating animations. The basic animations are now named after the curve type — such as linear and easeInOut. The interpolation-based spring(mass:stiffness:damping:initialVelocity:) animation is now interpolatingSpring(mass:stiffness:damping:initialVelocity:), and fluidSpring(stiffness:dampingFraction:blendDuration:timestep:idleThreshold:) is now spring(response:dampingFraction:blendDuration:) or interactiveSpring(response:dampingFraction:blendDuration:), depending on whether or not the animation is driven interactively. (50280375)
spring(mass:stiffness:damping:initialVelocity:), fluidSpring(stiffness:dampingFraction:blendDuration:timestep:idleThreshold:)がdeprecatedになりました。
併せて、これらのメソッドに相当するメソッドが新たに追加されています。
- interpolatingSpring(mass:stiffness:damping:initialVelocity:)
- spring(response:dampingFraction:blendDuration:)
- interactiveSpring(response:dampingFraction:blendDuration:)
Landmarks/Supporting View/GraphCapsule.swift
var animation: Animation { - Animation.spring(initialVelocity: 5) + Animation.spring(dampingFraction: 0.5) .speed(2) .delay(0.03 * Double(index)) }その他の変更点
- リファクタリング
- LandmarkListへの遷移元(Home.swift)でNavigationViewが生成されているため、LandmarkListではNavigationViewが削除されました。
Landmarks/LandmarkList.swift
var body: some View { - NavigationView { - List { - Toggle(isOn: $userData.showFavoritesOnly) { - Text("Show Favorites Only") - } - - ForEach(userData.landmarks) { landmark in - if !self.userData.showFavoritesOnly || landmark.isFavorite { - NavigationLink( - destination: LandmarkDetail(landmark: landmark) - .environmentObject(self.userData) - ) { - LandmarkRow(landmark: landmark) - } + List { + Toggle(isOn: $userData.showFavoritesOnly) { + Text("Show Favorites Only") + } + + ForEach(userData.landmarks) { landmark in + if !self.userData.showFavoritesOnly || landmark.isFavorite { + NavigationLink( + destination: LandmarkDetail(landmark: landmark) + ) { + LandmarkRow(landmark: landmark) } } } - .navigationBarTitle(Text("Landmarks"), displayMode: .large) } + .navigationBarTitle(Text("Landmarks"), displayMode: .large) }
beta 2 → beta 3 での変更点
画面遷移に関する構造体の仕様変更
構造体名が
XXXButtonからXXXLinkに変更されました。
SwiftUI Tutorials では以下の2つが利用されており、名前が変更されています。
beta 2 beta 3 NavigationButton NavigationLink PresentationButtonPresentationLink※ PresentationLinkは
beta4でdeprecatedになりました。Landmarks/Home.swift
- NavigationButton(destination: LandmarkList()) { + NavigationLink(destination: LandmarkList()) { Text("See All") } } .navigationBarTitle(Text("Featured")) .navigationBarItems(trailing: - PresentationButton(destination: ProfileHost()) { + PresentationLink(destination: ProfileHost()) { Image(systemName: "person.crop.circle") .imageScale(.large) .accessibility(label: Text("User Profile"))ScrollViewの仕様変更
beta 3ではAxis.SetとshowsIndicatorsの組合せで振る舞いを指定するように仕様変更されているのですが、beta 2では指定できていたBounceの指定や細かい組合せが実現できないように見えます。
beta 2のScrollViewのinit仕様public init(isScrollEnabled: Bool = true, alwaysBounceHorizontal: Bool = false, alwaysBounceVertical: Bool = false, showsHorizontalIndicator: Bool = true, showsVerticalIndicator: Bool = true, content: () -> Content)
beta 3のScrollViewのinit仕様public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, content: () -> Content)Landmarks/CategoryRow.swift
- ScrollView(showsHorizontalIndicator: false) { + ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 0) { ForEach(self.items.identified(by: \.name)) { landmark inTextFieldの仕様変更
beta 2での初期化方法がdeprecatedに変更され、beta 3では新たな初期化方法が追加されました。Landmarks/Profiles/ProfileEditor.swift
HStack { Text("Username").bold() Divider() - TextField($profile.username) + TextField("Username", text: $profile.username) }UIWindowの仕様変更
SwiftUI Tutorialsで利用されていた初期化方法が、
beta2とbeta3では異なりました。
beta 2では、UIViewから継承したinitが利用されていましたが、beta3ではUIWindowとして定義されているinitが利用されており、より良い方法に変更されたようです。Landmarks/SceneDelegate.swift
- let window = UIWindow(frame: UIScreen.main.bounds) - window.rootViewController = UIHostingController(rootView: CategoryHome().environmentObject(UserData())) - self.window = window - window.makeKeyAndVisible() + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: CategoryHome().environmentObject(UserData())) + self.window = window + window.makeKeyAndVisible() + }最後に
上記内容は、SwiftUI Tutorialsの変更点を元に調査を行い変更点をまとめました。
最新のbeta(現時点ではXcode 11.0 beta3 - Xcode 11.0 beta 4)の詳細な変更点につきましては、以下のApple公式サイトで確認することが可能ですので、興味がある方は確認してみてください。
https://developer.apple.com/documentation/swiftui?changes=latest_beta


