- 投稿日:2020-03-01T22:15:00+09:00
UserDefaultsをもっとシンプルに
はじめに
今回はUserDefaultsをもっと簡単に管理しやすくするためのコードを紹介していきたいと思います。是非参考にしていただけると幸いです☺️
※ジェネリクスとenumを利用しています。環境
- macOS Catalina(10.15.2)
- xcode(11.3)
- Swift5.1
コード
※UserDefaultsはあちこちのファイルで定義すると管理が大変になるので、このように1つのファイルで管理するといいかもです。
UserDefaults.swiftenum UserDefaultsKey: String { case title = "title" func get<T>() -> T? { return UserDefaults.standard.object(forKey: self.rawValue) as? T } func set<T>(value: T) { UserDefaults.standard.set(value, forKey: self.rawValue) } func remove() { UserDefaults.standard.removeObject(forKey: self.rawValue) } }利用するときはこんな感じです。
UserDefaultsKey.title.set(value: "氷菓") UserDefaultsKey.title.get() ?? String() UserDefaultsKey.title.remove()解説
enumを利用しenumのメソッドに登録、取得、削除の処理を定義しています。caseの部分にKeyを定義しています。UserDefaultsはどの型でも扱うことができるので、メソッドにジェネリクスを利用しています。
流れとしては、caseでキーを指定しそれに対応する値を各メソットでどう取り扱うかという感じです。おまけ(UserDefaultsのファイルを見る!)
思ったより早く説明しきってしまったので、おまけコーナーを設けました笑。知っている方もいるかと思いますが、知らなかった方は是非参考にしてみてください。
2.ダウンロードしたファイルを「パッケージの内容を表示」を選択して開いてください。
3.次のパス(AppData/Library/Preferences/任意のファイル名.plist)にあるファイルを開くとUserDefaultsの中身を確認することができます。
- 投稿日:2020-03-01T22:14:44+09:00
SwiftUIで作るドラえもん【顔編】
はじめに
日曜日で暇なので, SwiftUIを使ってドラえもんを作ってみました.
この記事で完成するドラえもんは以下の画像です.
口を開けているドラえもんは難しそうなので断念しました.
かなり本物に近くないですか?笑さぁ作って行きましょう.
土台づくり
まず,以下の画像のような土台となる青の円とその中にある円から作って行きましょう.
ドラえもんって意識してませんでしたが,地味に楕円なんだと感じました.
確かによくよく見たら楕円なんですよね.土台の色のみ,Asset Catalogで色を管理しています.
(鼻も管理した方が良さそう...)以下コードです.
ContentView.swiftstruct base: View { var body: some View { ZStack { // 土台 Ellipse() .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) .frame(width: 300, height: 290) .foregroundColor(Color("DoraColor")) // 顔 Ellipse() .frame(width: 270 , height: 250) .foregroundColor(Color.white) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 100 } ) } } }目
次に目です.
ContentView.swiftstruct eyes: View { var body: some View { HStack(spacing:0) { // 左目 ZStack { Ellipse() .frame(width: 60, height: 80) .foregroundColor(Color.white) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) ZStack { Ellipse() .frame(width: 20, height: 30) .foregroundColor(Color.black) Ellipse() .frame(width:5, height: 10) .foregroundColor(Color.white) } .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -5 }) } ZStack { // 右目 Ellipse() .frame(width: 60, height: 80) .foregroundColor(Color.white) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) ZStack { Ellipse() .frame(width: 20, height: 30) .foregroundColor(Color.black) Ellipse() .frame(width:5, height: 10) .foregroundColor(Color.white) } .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 25 } ) } } } }鼻
続いては鼻ですね.
ContentView.swiftstruct nose: View { var body: some View { Group { // 鼻 ZStack{ Circle() .frame(width: 50, height: 50) .foregroundColor(Color.red) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) Circle() .frame(width: 20, height: 10) .foregroundColor(Color.white) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 20}) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 15}) } .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 60 } ) // 人中? Rectangle() .foregroundColor(Color.black) .frame(width: 110, height: 2) .rotationEffect(Angle(degrees: 90)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -45 } ) } } }髭
ここまでくるとそれっぽくなってきました.
ContentView.swiftstruct CatBeard: View { var body: some View { Group { // 左髭 Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 120 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 0)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -20 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 120 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: -15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -50 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 120 } ) // 右髭 Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: -15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -50 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 0)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -20 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -50 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -50 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -50 } ) } } }口
最後に口です.
ContentView.swiftstruct Mouth: View { var body: some View { Circle() .trim(from: 0.05, to: 0.45) .stroke(Color.black,lineWidth: 3) .frame(width: 200, height: 200) } }完成
全文
ContentView.swiftimport SwiftUI struct base: View { var body: some View { ZStack { // 土台 Ellipse() .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) .frame(width: 300, height: 290) .foregroundColor(Color("DoraColor")) // 顔 Ellipse() .frame(width: 270 , height: 250) .foregroundColor(Color.white) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) .alignmentGuide(.leading, computeValue: { _ in 10 } ) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 100 } ) } } } struct eyes: View { var body: some View { HStack(spacing:0) { // 左目 ZStack { Ellipse() .frame(width: 60, height: 80) .foregroundColor(Color.white) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) ZStack { Ellipse() .frame(width: 20, height: 30) .foregroundColor(Color.black) Ellipse() .frame(width:5, height: 10) .foregroundColor(Color.white) } .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -5 }) } ZStack { // 右目 Ellipse() .frame(width: 60, height: 80) .foregroundColor(Color.white) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) ZStack { Ellipse() .frame(width: 20, height: 30) .foregroundColor(Color.black) Ellipse() .frame(width:5, height: 10) .foregroundColor(Color.white) } .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 25 } ) } } } } struct nose: View { var body: some View { Group { // 鼻 ZStack{ Circle() .frame(width: 50, height: 50) .foregroundColor(Color.red) .overlay( Ellipse() .stroke(Color.black,lineWidth: 2) ) Circle() .frame(width: 20, height: 10) .foregroundColor(Color.white) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 20}) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 15}) } .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 60 } ) // 人中? Rectangle() .foregroundColor(Color.black) .frame(width: 110, height: 2) .rotationEffect(Angle(degrees: 90)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -45 } ) } } } struct CatBeard: View { var body: some View { Group { // 左髭 Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 120 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 0)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -20 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 120 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: -15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -50 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in 120 } ) // 右髭 Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: -15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 10 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -50 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 0)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -20 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -50 } ) Rectangle() .foregroundColor(Color.black) .frame(width: 70, height: 2) .rotationEffect(Angle(degrees: 15)) .alignmentGuide(VerticalAlignment.center, computeValue: { _ in -50 } ) .alignmentGuide(HorizontalAlignment.center, computeValue: { _ in -50 } ) } } } struct Mouth: View { var body: some View { Circle() .trim(from: 0.05, to: 0.45) .stroke(Color.black,lineWidth: 3) .frame(width: 200, height: 200) } } struct DoraFace: View { var body: some View { ZStack{ // 土台 base() // 目 eyes() .alignmentGuide(VerticalAlignment.center, computeValue: { _ in 130}) // 鼻 nose() // 髭 CatBeard() // 口 Mouth() } } } struct ContentView: View { var body: some View { VStack{ Text("ドラえもん") .font(.largeTitle) DoraFace() } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }終わりに
あまり綺麗ではありませんが,なんとか完成させることができました.
次の暇な日曜日にでも綺麗なコードに修正していこうと思います.ちなみに,今回のタイトルに,【顔編】とありますが,【体編】を作る予定はありません.笑
SwiftUI初心者なのでもっと良い書き方があればご教示ください.
- 投稿日:2020-03-01T20:08:44+09:00
【Swift】ミュータブルな要素を操作をせずに配列をDictionaryに変換する
はじめに
列挙可能なデータからDictionaryをつくりたくなることはないでしょうか。
私はありました。
私はC#を以前に学んでおり、C#にはIEnumerable.ToDictionary()という拡張メソッドがあったので、
Swiftにも同じ機能を持った関数がないか調べていました。StackOverflowで見つかったサンプル
https://stackoverflow.com/a/38455935
let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in var dict = dict dict[person.position] = person.name return dict }なるほど、
reduce()の初期値に空のDictionaryのインスタンスを与えて、
配列の要素順にDictionaryインスタンスに要素を追加していけば完成する。
reduce()は畳み込んでデータの次元を落とす処理をするイメージだったので、
こんなこともできるのかと目から鱗でした。しかし気になることが一つ。
var dict = dict
キミの存在だ。せっかくの関数型要素を強くもつ言語なのだから
イミュータブルなデータの操作のみでできないものだろうか。 というのが以降書いている内容の動機です。
(標準ライブラリの中は除くとして)ついでにC#にあるようなtoDictionary()拡張を用意することで、
簡単に書けるようにしてしまおうという目論みです。そして毎度おなじみ。
もっといいものが世の中にはあるらしい。
自己流の答えを書く前に世の中で正しいとされている方法を先に紹介です。
新しいreduceによる解決策users.reduce(into: [Int: User]()) { $0[$1.id] = $1 }intoラベルを付けた場合、ミュータブルな状態の参照渡しで累積値が扱えるようになっているようです。
ついでに、でやりたかったことはこれで解決してしまったうえ、参照渡しなので
コピーオンライトである配列やDictionaryを扱うときのパフォーマンスも申し分ないのですが……。とはいえ作ってみたもの
なんたって主目的はミュータブル操作の排除なので、作ったものは載せときます。
オーバーロードとか用意したら少し長くなっちゃったので
中身のコードはオンラインエディタで。
(リンク消える可能性を考慮して下部にも記載しておきます)メインの関数だけ抜き出しfunc toDictionary<TElement,TKey,TValue>( _ keyFunc: (TElement) throws -> TKey, _ valueFunc: (TElement) throws -> TValue, uniquingKeysWith: (TValue, TValue) throws -> TValue )rethrows -> Dictionary<TKey,TValue> where TElement == Element { let mappedItems = try self .lazy .map { (key: try keyFunc($0), value: try valueFunc($0)) } // 1. return try Dictionary(mappedItems, uniquingKeysWith: uniquingKeysWith) // 2. }やってることは
- mapping関数を引数にとって、任意の配列を
(key:TKey, value:TValue)のシーケンスに変換- Dictionary.init(_:uniquingKeysWith:)で任意のKeyVaklueタプルのシーケンスからDictionaryインスタンスを生成。
(キー重複が起こりえるのでその場合の処理を明示化)使い方let keyValues = [1:"a", 2:"b", 3:"c", 4:"a",] let keyStrSet = keyValues.keys .toDictionary({$0}, {String($0)}, ToDictionaryUnique.adoptLast) print(keyStrSet) // [1: "1", 2: "2", 3: "3", 4: "4"] let keyValueSwapLast = keyValues .toDictionary({$0.value}, {$0.key}, ToDictionaryUnique.adoptLast) print(keyValueSwapLast) // ["c": 3, "a": 4, "b": 2] let keyValueSwapFirst = keyValues .toDictionary({$0.value}, {$0.key}, ToDictionaryUnique.adoptFirst) print(keyValueSwapFirst) // ["c": 3, "a": 1, "b": 2] let uniqueError = try keyValues .toDictionary({$0.value}, {$0.key}) // Fatal error: Error raised at top level: ToDictionaryError.duplicateKeys print(uniqueError)どこかで見たことあるような……?
C#やってたので、C#にあったIEnumerable.ToDictionary()の使い勝手をそのまま移植した感じ。
Swiftは例外発生時の動作を漏れなく扱えるのがいいですね。個人的な発見とか思い
rethrows…throwを含む関数を引数に取った時だけtryを必要とし、含まない場合はtry記述を必要としない。- 最初は
throwsする関数としない関数の2種類作る必要があるんじゃないかと思っていたので、関心した。// 引数に取った関数で値を変換する func myMap<T,TResult>(_ target:T, _ mapFunc: (T) throws -> TResult) rethrows -> TResult { return try mapFunc(target) } enum MyError:Error{case Err} let unwrap = { (x:Int?) -> Int in guard let y = x else { throw MyError.Err } return y } print( myMap(Optional(1), {x in x!}) ) // 1 print(try myMap(Optional(1), unwrap ) ) // 1 print(try myMap(Int(""), unwrap ) ) // Fatal error: Error raised at top level: MyError.Err:
- 結局のところ正解例の参照渡しと、今回作ったコードでどれくらいパフォーマンス(速度、メモリ使用量)に差が出るのか。
- swiftのメモリ使用量ってどうやってみるんだろう。
- そもそもXCodeは使ってなくて、WindowsのWSLで動かしてるからステップ実行とか、変数内参照とかすらまだ環境が出来てない…。
往々にしてよくある悲しみ
- 動くものを作り、文章を書いてる段になって、書いてることの正しさ確認のために資料読んでて正解を見つける。
- やってたことは一体なんだったの感。
- まぁ理解した内容が無駄になるわけじゃないし、目的はmutableな要素を扱わないこととしたので、意味はあるのか…?
リンク切れのときのための中身
enum ToDictionaryError:Error{ case duplicateKeys } /// キー重複時の標準的な操作を提供 enum ToDictionaryUnique { /// シーケンスで後に登場した要素を採用 case adoptLast /// シーケンスで後に登場した要素を採用 case adoptFirst func getUniqueKeyFunc<TValue>() -> (TValue,TValue)->TValue { switch self { case .adoptFirst: return {(old:TValue, new:TValue) in old } case .adoptLast: return {(old:TValue, new:TValue) in new } } } } extension Sequence { /// SequenceからDictionaryに変換する。 /// - Parameters: /// - keyFunc: DictionaryのKeyとなる値を返す関数 /// - valueFunc: DictionaryのValueとなる値を返す関数 /// - uniquingKeysWith: キー重複時採用する値を返す関数 func toDictionary<TElement,TKey,TValue>( _ keyFunc: (TElement) throws -> TKey, _ valueFunc: (TElement) throws -> TValue, uniquingKeysWith: (TValue, TValue) throws -> TValue )rethrows -> Dictionary<TKey,TValue> where TElement == Element { let mappedItems = try self .lazy .map { (key: try keyFunc($0), value: try valueFunc($0)) } return try Dictionary(mappedItems, uniquingKeysWith: uniquingKeysWith) } /// SequenceからDictionaryに変換する。 /// - Parameters: /// - keyFunc: DictionaryのKeyとなる値を返す関数 /// - valueFunc: DictionaryのValueとなる値を返す関数 /// - uniqueCase: キー重複時の動作 func toDictionary<TElement,TKey,TValue>( _ keyFunc: (TElement) throws -> TKey, _ valueFunc: (TElement) throws -> TValue, _ uniqueCase: ToDictionaryUnique ) rethrows -> Dictionary<TKey,TValue> where TElement == Element { return try toDictionary(keyFunc, valueFunc, uniquingKeysWith: uniqueCase.getUniqueKeyFunc()) } /// SequenceからDictionaryに変換する。 /// キー重複時は例外を発生する。 func toDictionary<TElement,TKey,TValue>( _ keyFunc: (TElement) throws -> TKey, _ valueFunc: (TElement) throws -> TValue ) throws -> Dictionary<TKey,TValue> where TElement == Element { let throwDuplicateKey = { (_:TValue, _:TValue) -> TValue in throw ToDictionaryError.duplicateKeys } return try toDictionary(keyFunc, valueFunc, uniquingKeysWith: throwDuplicateKey) } } let keyValues = [1:"a", 2:"b", 3:"c", 4:"a",] let keyStrSet = keyValues.keys .toDictionary({$0}, {String($0)}, ToDictionaryUnique.adoptLast) print("keyStrSet") print(keyStrSet) // [1: "1", 2: "2", 3: "3", 4: "4"] let keyValueSwapLast = keyValues .toDictionary({$0.value}, {$0.key}, ToDictionaryUnique.adoptLast) print("keyValueSwapLast") print(keyValueSwapLast) // ["c": 3, "a": 4, "b": 2] let keyValueSwapFirst = keyValues .toDictionary({$0.value}, {$0.key}, ToDictionaryUnique.adoptFirst) print("keyValueSwapFirst") print(keyValueSwapFirst) // ["c": 3, "a": 1, "b": 2] //let uniqueError = try keyValues // .toDictionary({$0.value}, // {$0.key}) // Fatal error: Error raised at top level: ToDictionaryError.duplicateKeys //print(uniqueError)
- 投稿日:2020-03-01T17:18:31+09:00
UIPopoverPresentationController 背景が透明で奥のレイヤーが見えるポップオーバーにする
目的
下記のようなアプリがある場合にポップオーバーを表示するとこうなりますよね。
このように緑色の
UIViewを透かしたい場合があるのですが、
うっすら見えますけども、透明ではないですよね。
手順
❶
UIPopoverBackgroundViewのサブクラスを追加する。
❷UIPopoverPresentationControllerのプロパティ.popoverBackgroundViewClassに❶のクラスを指定する。実装(Objective-C)
###ViewController.m #import "ViewController.h" @interface ViewController ()<UIPopoverPresentationControllerDelegate> @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. } // ボタン押下後に呼び出される。 - (IBAction)tapButton:(id)sender { TableViewController *vc = [[TableViewController alloc]init]; [self presentPopOverWithViewController:vc sourceView:sender]; } - (void)presentPopOverWithViewController:(UIViewController *)viewController sourceView:(UIView *)sourceView { viewController.modalPresentationStyle = UIModalPresentationPopover; viewController.preferredContentSize = CGSizeMake(300.0, 44.0 * 5); UIPopoverPresentationController *presentationController = viewController.popoverPresentationController; //下記記述が重要 presentationController.popoverBackgroundViewClass = [PopoverBackgroundView class]; presentationController.delegate = self; presentationController.permittedArrowDirections = UIPopoverArrowDirectionUp; presentationController.sourceView = sourceView; presentationController.sourceRect = sourceView.bounds; [self presentViewController:viewController animated:YES completion:NULL]; } //iPhoneでの描画に大きく関係するのでしっかり追加しておく - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller { return UIModalPresentationNone; } @endPopoverBackgroundView.h
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface PopoverBackgroundView : UIPopoverBackgroundView @end NS_ASSUME_NONNULL_END @endPopoverBackgroundView.m
// PopoverBackgroundView.m #import "PopoverBackgroundView.h" @interface PopoverBackgroundView () { CGFloat _arrowOffset; UIPopoverArrowDirection _arrowDirection; } @end @implementation PopoverBackgroundView + (CGFloat)arrowHeight { return 0.0f; } + (UIEdgeInsets)contentViewInsets { return UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f); } - (UIPopoverArrowDirection)arrowDirection { return _arrowDirection; } - (void)setArrowDirection:(UIPopoverArrowDirection)arrowDirection { _arrowDirection = arrowDirection; } - (CGFloat)arrowOffset { return _arrowOffset; } - (void)setArrowOffset:(CGFloat)arrowOffset { _arrowOffset = arrowOffset; } - (void)layoutSubviews { [super layoutSubviews]; self.layer.shadowOpacity = 0.0f; } @end実際に開発現場でどのように使われてくるか
デザイナーさんが「ポップオーバーを押下したレイヤーの上に透明に重なるように」という意図を持って、透過がある画像ファイルを作成してくださった際に、ただポップオーバー上に表示するだけでは
レイヤーの上に透明に重なるようには実装できません。なので
ポップオーバーの背景自体を透明色を適用する必要があります。謝辞
下記記事を引用しております。
Xcode11、またUIPopoverPresentationControllerと明記していなかったのでこちらの記事を出させていただきました。
UIPopoverControllerの背景色を透明にする
- 投稿日:2020-03-01T16:02:31+09:00
Swiftをコマンドラインから実行(メモ)
環境設定
Mac OS Catalina(10.15.3)、xcodeは5.1.3が入っています。
$ sudo xcode-select -s /Applications/Xcode.app $ sudo xcode-select --installxcode-select: note: install requested for command line developer toolsここでデベロッパーツールを追加でインストール。完了後はswiftを打つと通るようになりました。
$ swiftWelcome to Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15). Type :help for assistance. 1>コンパイル
collatz.swiftvar n = 100 repeat { print("\(n)") if n % 2 == 0{ n /= 2 }else{ n = n * 3 + 1 } }while n > 1 print(n)これを保存しておきます。
$ swiftc collatz.swiftこのコマンドでコンパイルできます。
すると実行ファイルが作成されます。
これを実行します。
$ ./collatz
実行結果
100 50 25 76 38 19 58 29 88 44 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
コンパイルせずに解釈実行
上のファイルを解釈実行させます。
swift collatz.swift参考文献
- 投稿日:2020-03-01T15:53:56+09:00
Google Maps iOS Utils で Marker をまとめて表示する
Google Maps SDK を使用していてマップ上にマーカーを立てることはよくあると思います
ただ、マップ上にマーカーを建てすぎると、広域表示にした際にマーカーが密集しちゃって見辛くなってしまうってこともよくあると思いますそんな時マーカーを1つにまとめたいなーと思ったので、その実装方法を残しておきたいと追います
開発環境
Xcode:11.1(11A1027)
Swift 5
iOS:13.1
CocoaPods: 1.8.0実装
ライブラリのインストール
Google Maps iOS Utils をインストールするために CocoaPods を使用します
以下のように Podfile を編集し、pod installを実行してくださいtarget 'YOUR_APPLICATION_TARGET_NAME_HERE' do use_frameworks! pod 'GoogleMaps' pod 'Google-Maps-iOS-Utils' endこちらのセットアップガイドにも記載されていますが、
CocoaPods のバージョンが 1.8.1 以上はサポート対象外のようです1.6.1 が推奨されているようですが、Xcode11 では 1.6.1 を使用した場合にビルドが通らなかったため、今回は 1.8.0 を使用しています
CocoaPods のバージョンを下げるにはこちらを参考にしてください
マーカーをまとめて表示する用の class を作成
普段は
GMSMarkerclass を使用してマーカーを表示していますが、
マーカーをまとめて表示するにはGMUClusterItemprotocol に準拠した class を作成し、その class を使用してマーカーを表示する必要がありますimport GoogleMapsUtils class POIItem: NSObject, GMUClusterItem { var position: CLLocationCoordinate2D init(position: CLLocationCoordinate2D) { self.position = position } }Map 上にマーカーを配置
先ほど作成した独自 class
POIItemを使用してマーカーを Google Map 上に表示していきますimport UIKit import GoogleMaps import GoogleMapsUtils class ViewController: UIViewController { // デフォルトの位置情報(仮で東京駅付近にしています) let defaultPositionLat = 35.681223 let defaultPositionLng = 139.767059 private var mapView: GMSMapView! /// Map 上に表示するマーカーを管理するためのプロパティ private var clusterManager: GMUClusterManager! override func viewDidLoad() { super.viewDidLoad() // GoogleMapの初期位置 let camera = GMSCameraPosition.camera(withLatitude: defaultPositionLat, longitude: defaultPositionLng, zoom: 17.0) mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera) view = mapView let iconGenerator = GMUDefaultClusterIconGenerator() let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm() let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator) clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer) // マーカーをランダムに生成して Map 上に表示 generateClusterItems() } private func generateClusterItems() { let extent = 0.01 for _ in 1...100 { let lat = defaultPositionLat + extent * randomScale() let lng = defaultPositionLng + extent * randomScale() let item = POIItem(position: CLLocationCoordinate2DMake(lat, lng)) clusterManager.add(item) } // Map にマーカーを描画 clusterManager.cluster() } /// ランダムな位置にマーカーを表示するための乱数を生成 private func randomScale() -> Double { return Double(arc4random()) / Double(UINT32_MAX) * 2.0 - 1.0 } }上記のコードを実行すると、最初に載せた gif のような挙動になるかと思います
Delegate とか
マーカーをタップした際のアクションを設定したい場合、以下のように
GMUClusterManagerDelegateとGMSMapViewDelegateを設定してあげるとタップ時のアクションが設定可能な脳ですclass ViewController: UIViewController, GMUClusterManagerDelegate, GMSMapViewDelegate { private var mapView: GMSMapView! private var clusterManager: GMUClusterManager! override func viewDidLoad() { super.viewDidLoad() // ... Rest of code omitted for easy reading. // Register self to listen to both GMUClusterManagerDelegate and // GMSMapViewDelegate events. clusterManager.setDelegate(self, mapDelegate: self) } // MARK: - GMUClusterManagerDelegate func clusterManager(clusterManager: GMUClusterManager, didTapCluster cluster: GMUCluster) { let newCamera = GMSCameraPosition.cameraWithTarget(cluster.position, zoom: mapView.camera.zoom + 1) let update = GMSCameraUpdate.setCamera(newCamera) mapView.moveCamera(update) } // MARK: - GMUMapViewDelegate func mapView(mapView: GMSMapView, didTapMarker marker: GMSMarker) -> Bool { if let poiItem = marker.userData as? POIItem { NSLog("Did tap marker for cluster item \(poiItem.name)") } else { NSLog("Did tap a normal marker") } return false } }終わりに
Google Maps iOS Utils というライブラリはマーカーをまとめるだけでなく、
マーカーの画像をカスタマイズしたり KML や GeoJSON をレンダリングできたりと他にも色々な機能が使用できるようなので、機会があれば触ってみようかと思います参考
- 投稿日:2020-03-01T15:53:56+09:00
Google Maps iOS Utils でマーカーをまとめて表示する
Google Maps SDK を使用していてマップ上にマーカーを立てることはよくあると思います
ただ、マップ上にマーカーをたてすぎると、広域表示にした際にマーカーが密集しちゃって見辛くなってしまうってこともよくあると思いますそんな時マーカーを1つにまとめたいなーと思ったので、その実装方法を残しておきたいと追います
開発環境
Xcode:11.1(11A1027)
Swift 5
iOS:13.1
CocoaPods: 1.8.0実装
ライブラリのインストール
Google Maps iOS Utils をインストールするために CocoaPods を使用します
以下のように Podfile を編集し、pod installを実行してくださいtarget 'YOUR_APPLICATION_TARGET_NAME_HERE' do use_frameworks! pod 'GoogleMaps' pod 'Google-Maps-iOS-Utils' endこちらのセットアップガイドにも記載されていますが、
CocoaPods のバージョンが 1.8.1 以上はサポート対象外のようです1.6.1 が推奨されているようですが、Xcode11 では 1.6.1 を使用した場合にビルドが通らなかったため、今回は 1.8.0 を使用しています
CocoaPods のバージョンを下げるにはこちらを参考にしてください
マーカーをまとめて表示する用の class を作成
普段は
GMSMarkerclass を使用してマーカーを表示していますが、
マーカーをまとめて表示するにはGMUClusterItemprotocol に準拠した class を作成し、その class を使用してマーカーを表示する必要がありますimport GoogleMapsUtils class POIItem: NSObject, GMUClusterItem { var position: CLLocationCoordinate2D init(position: CLLocationCoordinate2D) { self.position = position } }Map 上にマーカーを配置
先ほど作成した独自 class
POIItemを使用してマーカーを Google Map 上に表示していきますimport UIKit import GoogleMaps import GoogleMapsUtils class ViewController: UIViewController { // デフォルトの位置情報(仮で東京駅付近にしています) let defaultPositionLat = 35.681223 let defaultPositionLng = 139.767059 private var mapView: GMSMapView! /// Map 上に表示するマーカーを管理するためのプロパティ private var clusterManager: GMUClusterManager! override func viewDidLoad() { super.viewDidLoad() // GoogleMapの初期位置 let camera = GMSCameraPosition.camera(withLatitude: defaultPositionLat, longitude: defaultPositionLng, zoom: 17.0) mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera) view = mapView let iconGenerator = GMUDefaultClusterIconGenerator() let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm() let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator) clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer) // マーカーをランダムに生成して Map 上に表示 generateClusterItems() } private func generateClusterItems() { let extent = 0.01 for _ in 1...100 { let lat = defaultPositionLat + extent * randomScale() let lng = defaultPositionLng + extent * randomScale() let item = POIItem(position: CLLocationCoordinate2DMake(lat, lng)) clusterManager.add(item) } // Map にマーカーを描画 clusterManager.cluster() } /// ランダムな位置にマーカーを表示するための乱数を生成 private func randomScale() -> Double { return Double(arc4random()) / Double(UINT32_MAX) * 2.0 - 1.0 } }上記のコードを実行すると、最初に載せた gif のような挙動になるかと思います
Delegate とか
マーカーをタップした際のアクションを設定したい場合、以下のように
GMUClusterManagerDelegateとGMSMapViewDelegateを設定してあげるとタップ時のアクションが設定可能なようですclass ViewController: UIViewController, GMUClusterManagerDelegate, GMSMapViewDelegate { private var mapView: GMSMapView! private var clusterManager: GMUClusterManager! override func viewDidLoad() { super.viewDidLoad() // ... Rest of code omitted for easy reading. // Register self to listen to both GMUClusterManagerDelegate and // GMSMapViewDelegate events. clusterManager.setDelegate(self, mapDelegate: self) } // MARK: - GMUClusterManagerDelegate func clusterManager(clusterManager: GMUClusterManager, didTapCluster cluster: GMUCluster) { let newCamera = GMSCameraPosition.cameraWithTarget(cluster.position, zoom: mapView.camera.zoom + 1) let update = GMSCameraUpdate.setCamera(newCamera) mapView.moveCamera(update) } // MARK: - GMUMapViewDelegate func mapView(mapView: GMSMapView, didTapMarker marker: GMSMarker) -> Bool { if let poiItem = marker.userData as? POIItem { NSLog("Did tap marker for cluster item \(poiItem.name)") } else { NSLog("Did tap a normal marker") } return false } }終わりに
Google Maps iOS Utils というライブラリはマーカーをまとめるだけでなく、
マーカーの画像をカスタマイズしたり KML や GeoJSON をレンダリングできたりと他にも色々な機能が使用できるようなので、機会があれば触ってみようかと思います参考
- 投稿日:2020-03-01T14:42:52+09:00
【Xcode11対応】Prefix.pchの設定のやり方
記事の背景
いつもプロジェクトを新規作成する際はいつも指示を出して追加していましたが、
改めて調べた所、Xcode6から自動追加されないようになり、その頃とやり方が大きく異なるので最新のやり方についてまとめておきます。手順
❶プロジェクトファイルの直下に
「PrefixHeader.pch」を追加する。
❷対象TARGETのBulid SettingsのPrefixHeaderで上記ファイルのパスを指定する。プロジェクトファイルの直下に
「PrefixHeader.pch」を追加する。
Command+nで新規ファイルを追加して「Other」>「PCH FILE」を選択。
名前は変更しても構いませんが本記事はデフォルトネームを取り扱いますのでご了承ください。
上記のようにプロジェクトファイル直下になれば問題ございません。
(後にパスを指定する都合上こちらの方が都合が良かった)対象TARGETの
Bulid SettingsのPrefixHeaderで上記ファイルのパスを指定する。上記
「Prefix Header」を押下して$(SRCROOT)/$(PROJECT)/PrefixHeader.pchと設定する。
これで
「PrefixHeader.pch」を適用できておりますので、
よく使うクラスなどは#import "hogeViewController.h"など書いてください。PrefixHeader.pch
#ifndef PrefixHeader_pch #define PrefixHeader_pch // importしたいクラスを記載する。 #import "HogeViewController.h" // Include any system framework and library headers here that should be included in all compilation units. // You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. #endif /* PrefixHeader_pch */また以前記事書きましたが下記の色の定義の宣言はかなり便利なのでオススメです。活用してみてください。
#define RGB(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1] #define RGBA(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)]余談:
$(PROJECT)など確認する方法これは完全な脱線です。ただ他にはどんなコマンドがあるのだ?など疑問ある人だけ進めてください。
❶TAGERT > BuildPhases > 「+」 > New Run Script Phase
❷RunScriptにenv > env.txtを登録してBuild(Command+R)する
❸プロジェクトフォルダに env.txt というファイルが生成されるので中身を確認する。こんな感じで実行したプロジェクト内に格納されているかと。
これをテキストエディタにて開いて、希望のコマンド探してみてください。
- 投稿日:2020-03-01T14:17:40+09:00
【Swift】 配列要素の合計を求める拡張
はじめに
数値計算を総称的に扱うためのNumeric Protocolみたいなのないの?
→Swift5からあるらしいで。
それを知らなくて自分で考えてた話です。
- 当初似たようなことやってる人がいるだろうと思って探してみるも
見つけたものには、以下のような制約があった。- それの制約を取り払えないか? というところからのスタート。
A
Swift5の標準に基づく実装と自前実装
Swift5の標準に基づく実装
まずは正しい(と思っている)答え
standardextension Sequence where Element:AdditiveArithmetic { func sum() -> Element { return reduce(.zero, +) } }AdditiveArithmeticとは…
基本の数値型(Int、Doubleなど)で実装済みのプロトコル。(Swift5から)
zero(初期値)と加減算オペレータが定義されているstandardprotocol AdditiveArithmetic { static var zero: Self static func + (Self, Self) -> Self static func += (Self, Self) static func - (Self, Self) -> Self static func -= (Self, Self) }
標準実装があるのを知らなくて自分で定義したプロトコルと実装。
要らないんですが考えの過程を残すために載せておきます。
my_implementationprotocol Summable{ init() static func +(lhs: Self, rhs: Self) -> Self } extension Int:Summable{} extension Int64:Summable{} extension String:Summable{} // any more extension Sequence where Element:Summable { func sum<T:Summable>() -> Element where Element==T { return reduce(T.init(), +) } } // use eg print( [1,2,3].sum() ) // …6 print( ["1","2","3"].sum() ) // …"123"実装にあたり悩んだところ。
- protocolはclassとは異なる要領で総称型に対する処理を実現している。
- class …
Class<T>の要領で総称型(ジェネリクス)を定義して利用 (struct、関数も同様)- protocol … プロパティで総称型として扱いたい型情報を管理(associatedtype)
- 扱いが異なる理由 … なぜSwiftのプロトコルはジェネリクスをサポートしないのか
- 「
extension ExtProtocol where AssoociatedType:MyProtoclのwhereは拡張対象を絞るだけであり、AssociateTypeを決定しているわけではない。protocolに対して汎用処理を実現したい場合、各関数に対してジェネリクス<T>をT == AssociateTypeだとして定義しなければ定まらない。」ことが最初理解できなかった。- 合計値計算の基準となる初期値(数値であれば、各型のゼロ)をどのように定義するか。
reduceの第一引数に型を指定た初期値を与える必要がある。(異なる型同士は加算できないため)- 利用時の記述を楽にしたい。※
init()で初期値が取得でき、楽ができた。※利用時にこんな風に{}内の記述したくないextension Double:Summable{ static var zero:Self { return Double(0)} } //protocol Summable{ // static func +(lhs: Self, rhs: Self) -> Self // static var zero:Self{get} //}
- 投稿日:2020-03-01T12:43:25+09:00
【Swift】同期的にHTTP通信する
ググったところ古い情報ばかり見つかったので、Swift5でも大丈夫だったよという記事
全2回のHTTPリクエストを行う際に、
2回目のリクエストは、1回目の通信結果を使用してパラメータを作るとかそういう用途で環境
Xcode 11.3.1
Swift 5方法
1.DispatchSemaphoreをvalue0で生成する
2.HTTP通信した直後、セマフォをwaitする
3.HTTP通信の完了イベント内でsignal()を呼び出すソースコード
/// セマフォ var semaphore : DispatchSemaphore! func syncHttpRequest() { semaphore = DispatchSemaphore(value: 0) // Httpリクエストの生成 var request = URLRequest(url: URL(string: "http://httpbin.org/get")!) request.httpMethod = "GET" // HTTPリクエスト実行 URLSession.shared.dataTask(with: request, completionHandler: requestCompleteHandler).resume() // requestCompleteHandler内でsemaphore.signal()が呼び出されるまで待機する semaphore.wait() print("request completed") } func requestCompleteHandler(data:Data?,res:URLResponse?,err:Error?) { print("response recieve") semaphore.signal() }実行結果
response recieve request completed
- 投稿日:2020-03-01T12:33:26+09:00
【Swift5】Realmでデータを管理する
はじめに
メモアプリのタイトルのみをUserDefaultsで管理していたのですが、タグや日付などの情報も管理したくなり、UserDefaultsで保持すると複雑になりそうだったので、Realmへの移行を試みました!
データの挿入まではわりとスムーズにできたのですが、autoIncrementや更新系の設定で、苦戦したので、その辺りを中心にまとめます!環境
- xcode 11.3.1
- swift 5.1.3
Realmにデータを挿入する
Realmにデータを挿入するには、Objectのモデルを作り、
addという関数を使って保存してあげれば、データが挿入されるようです。Memo.Swiftimport Foundation import RealmSwift class Memo: Object { @objc dynamic var title : String = "" @objc dynamic var content : String = "" @objc dynamic var tags : String = "" @objc dynamic var createdDate : Date = NSDate() as Date @objc dynamic var updatedDate : Date = NSDate() as Date }モデルは↑のように作りました!
データを挿入する際のコードはこんな感じです!MemoTableViewController.swift(一部抜粋)let memoModel = Memo() let realm = try! Realm() // オブジェクトに値をセットする memoModel.title = memo memoModel.content = memo memoModel.tags = "test1" // DBに書き込む try! realm.write { realm.add(memoModel) }データを挿入するところまではこちらの記事を参考にしていただければ、わかりやすいと思います!
私もこちらを参考に実装しました!
[Realm][Swift4対応 完全保存版] 3.モデルオブジェクトの作成と書き込みRealm内のデータを確認する
Realmのデータを確認する手順は次のようになります。
- Realm Browserをインストールする
- アプリを起動し、RealmのデータベースのPathを調べる
- ターミナルから
openコマンドで2のPathを開く動作確認する方法はこちらの記事が参考になると思います!
【Swift】Realm BrowserでRealm Mobile Databaseの中身を確認するidを自動連番(Auto Increment)で入れる
RealmはデフォルトでAuto Incrementするような機能がなさそう?だったので私はRObjectというクラスを作り、
そこにAuto Incrementを実装して、モデルで継承するように実装しました。Memo.Swiftimport Foundation import RealmSwift class Memo: RObject { // RObject←Objectに変更 @objc dynamic var title : String = "" @objc dynamic var content : String = "" @objc dynamic var tags : String = "" @objc dynamic var createdDate : Date = NSDate() as Date @objc dynamic var updatedDate : Date = NSDate() as Date }RObject.swiftimport Foundation import Realm import RealmSwift class RObject: Object { // ID @objc dynamic var id = 0 // データを保存。 func save() { let realm = try! Realm() if realm.isInWriteTransaction { if self.id == 0 { self.id = self.createNewId() } realm.add(self, update: true) } else { try! realm.write { if self.id == 0 { self.id = self.createNewId() } realm.add(self, update: true) } } } // 新しいIDを採番します。 private func createNewId() -> Int { let realm = try! Realm() return (realm.objects(type(of: self).self).sorted(byKeyPath: "id", ascending: false).first?.id ?? 0) + 1 } // プライマリーキーの設定 override static func primaryKey() -> String? { return "id" } }MemoTableViewController.swift(一部抜粋)if let selectedIndexPath = self.tableView.indexPathForSelectedRow{ self.memos[selectedIndexPath.row] = memo // realm let realm = try! Realm() let memoModel = realm.objects(Memo.self).filter("id == " + String(sourceVC.memoId!)).first ?? Memo() try! realm.write { memoModel.title = memo memoModel.save() // realm.addから↑実装した.saveに変更 } }else{ self.memos.append(memo) // realm let memoModel = Memo() let realm = try! Realm() try! realm.write { memoModel.title = memo memoModel.content = memo memoModel.tags = "test1" memoModel.save() }参考にした記事
- https://gist.github.com/syou007/801fa5ddbd11d1d57ac0255938f65b41
- https://qiita.com/syou007/items/267612c402fdd28de7a8
データの更新をする
ここまではわりとスムーズに行ったのですが、データ更新あたりから妻付き始めました。。
Realm().add(_:update:)を使ってデータを更新するという情報が多かったのですが、なぜかエラーに。。
色々調べた結果、次のように実装変えたらコンパイル通ったので参考にしてください。
おそらく、RealmSwiftの最新版だと
Realmのドキュメントrealm.add(self, update: true) // ↓ realm.add(self, update: .modified)元のテーブル(モデル)を削除する
実装自体のエラーはなくなって自動連番の機能や主キーのIDが入れられるようになりました。
が、今度は起動時にIDがテーブルに存在しないというエラーが出ました。。
調べたところ、テーブルの形式を変更したので、元々入っていたデータにIDがなく、エラーのようでした。。
対処方法は次の2つでした。
- 既存のテーブル(Object)の削除
- マイグレーション
まだ、不要なデータしかなかったのとマイグレーションが最初うまくできなかったので今回は、一旦既存テーブル削除を実施!
AppDelegate.swift// 既存テーブルの削除 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let realmURL = Realm.Configuration.defaultConfiguration.fileURL! let realmURLs = [ realmURL, realmURL.appendingPathExtension("lock"), realmURL.appendingPathExtension("note"), realmURL.appendingPathExtension("management") ] for URL in realmURLs { do { try FileManager.default.removeItem(at: URL) } catch { // handle error } } return true }このコードを実装した状態で、アプリケーション起動するとテーブルが削除されるので、新しいモデルが挿入できるようになると思います!
マイグレーションの方法に関しては、勉強して追記しようと思います?参考にした記事
まとめ
今回はRealmでデータ管理がするのに必要な実装をしてみました。
updateのあたりとかは特にBool型を引数に実装をしている記事が多かったので、要注意だと思います!
また、swift周りは技術の進化が早く、どんどんバージョンが入れ替わり、参考にした記事のコードが動かないということがザラにあったので、書かれた時期と環境のバージョンが大事だなと思いました!
あと余談ですがswiftの独特のコーディングにも少し慣れてきた気がしました(segueなど)!笑
- 投稿日:2020-03-01T09:42:57+09:00
[swift5]iOSアプリでローカル通知を実装
ローカル通知を実装する方法を紹介します!
動作環境
対象 バージョン iOS 13.3.1 macOS Catalina 10.15.3 Xcode 11.3.1 Swift 5.1.3 実装の方法
ソースコード
AppDelegate.swift
import UIKit import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. // 通知許可の取得 UNUserNotificationCenter.current().requestAuthorization( options: [.alert, .sound, .badge]){ (granted, _) in if granted{ UNUserNotificationCenter.current().delegate = self } } return true } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // アプリ起動時も通知を行う completionHandler([ .badge, .sound, .alert ]) } }ViewController.swift
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } /// ローカル通知ボタンを押下した際の処理 /// - Parameter sender: ローカル通知ボタン @IBAction func localPush(_ sender: Any) { let content = UNMutableNotificationContent() content.title = "お知らせ" content.body = "ボタンを押しました。" content.sound = UNNotificationSound.default // 直ぐに通知を表示 let request = UNNotificationRequest(identifier: "immediately", content: content, trigger: nil) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) } }
























