-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBaseViewController
More file actions
237 lines (216 loc) · 11.3 KB
/
BaseViewController
File metadata and controls
237 lines (216 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// MARK: ViewController/Base/BaseViewController.swift
/**
+ StoryboardのInitialViewControllerマークプロトコル
*/
protocol StoryboardInitialMark {
/// Storyboardのファイル名
/// 1. InitialViewControllerに定義されているファイル名
static var STORYBOARD_NAME: String { get }
}
/**
+ このプロジェクトの基底ViewController
*/
class BaseViewController: UIViewController {
// MARK : Properties
/// 監視するのテキストフィルド
var keyboardObserverTextFields: [UITextField]?
/// 全てのテキストフィルドを監視するか(例えばTabelViewにTextField動的表示する場合も監視します),デフォルトはfalse
var isObserverAllTextFields = false
/// ViewControllerにテキストフィルドが元々あるの場合監視るす(例えばStoryBoardに最初から入れたテキストフィルドがあるの場合)、デフォルトはtrue
var isObserverAllTextFieldsWhileHasAnyStaictTextField = true
/// このViewControllerは他のViewControllerの傘下にいるかどうか
var isEmbed = true
/// キーボードつい最近の範囲を持っているプロパティ
var lastKeyboardFrameEnd: CGRect?
/// テキストフィルドを入力する時、キーボードの高さによってViewを調整する監視は必要か
var needToObserverTextFields: Bool {
get {
if isObserverAllTextFields || keyboardObserverTextFields != nil {
return true
} else {
if isObserverAllTextFieldsWhileHasAnyStaictTextField && isAnyTypeViewInSubViews(subViews: view.subviews, type: UITextField.self) {
return true
}
return false
}
}
}
// MARK: Methods
/// 画面が描画された後に呼ばれるメソッド
override func viewDidLoad() {
super.viewDidLoad()
if needToObserverTextFields {
// キーボード表示範囲変化イベントを監視する
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(notification:)), name: .UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidChangeFrame(notification:)), name: .UIKeyboardDidChangeFrame, object: nil)
// キーボードの閉じる動きを監視する
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide(notification:)), name: .UIKeyboardDidHide, object: nil)
// テキストフィールドのフォーカス変更を監視する
NotificationCenter.default.addObserver(self, selector: #selector(textFieldTextDidBeginEditing(notification:)), name: .UITextFieldTextDidBeginEditing, object: nil)
}
}
/// キーボードの表示範囲が変わる際のイベントの処理
///
/// - Parameter notification: 通知内容
@objc func keyboardWillChangeFrame(notification: Notification) {
//キーボード目標の表示範囲を取る
if let keyboardFrameEnd = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue {
lastKeyboardFrameEnd = keyboardFrameEnd
adjustTextFieldFrameWithKeyboard(keyboardFrameEnd: keyboardFrameEnd, animationDuration: notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double)
}
}
/// キーボードの表示範囲が変した時のイベントの処理
///
/// - Parameter notification: 通知内容
@objc func keyboardDidChangeFrame(notification: Notification) {
if let keyboardFrameEnd = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue {
lastKeyboardFrameEnd = keyboardFrameEnd
adjustTextFieldFrameWithKeyboard(keyboardFrameEnd: keyboardFrameEnd, animationDuration: notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double)
}
}
/// キーボードを閉じる際のエベントの処理
///
/// - Parameter notification: 通知内容
@objc func keyboardDidHide(notification: Notification) {
lastKeyboardFrameEnd = nil
}
/// テキストフィールドのフォーカス変更イベントの処理
///
/// - Parameter notification: 通知内容
@objc func textFieldTextDidBeginEditing(notification: Notification) {
if let keyboardFrameEnd = lastKeyboardFrameEnd {
adjustTextFieldFrameWithKeyboard(keyboardFrameEnd: keyboardFrameEnd)
}
}
/// テキストフィールドの位置をキーボードの表示範囲にあわせて調節する
///
/// - Parameters:
/// - keyboardFrameEnd: キーボードの表示範囲
/// - animationDuration: 画面移動時のアニメション時間
func adjustTextFieldFrameWithKeyboard(keyboardFrameEnd: CGRect, animationDuration: Double? = nil) -> Void {
if let textField = findFirstResponder(self.view) as? UITextField {
// 入力中のテキストフィルドは指定の監視テキストフィルドの一つか?
if !needToObserverTextFields && !(keyboardObserverTextFields?.contains(textField))! {
return
}
// テキストフィルドはその親Windowの中のファレムを取得する
let textFieldFrame = textField.convert(textField.bounds, to: nil)
// ViewControllerの一番外のビューのY軸移動値
let transformY = textFieldFrame.origin.y + textField.frame.height - keyboardFrameEnd.origin.y - (self.view.frame.origin.y - (self.navigationController?.navigationBar.intrinsicContentSize.height ?? 0) - (self.isEmbed ? 0 : UIApplication.shared.statusBarFrame.size.height)) + 80
// キーボードの表示範囲がテキストフィールドより上の場合にはViewControllerの一番外のビューを移動する
let duration = animationDuration ?? 0.25
if !(keyboardFrameEnd.origin.y + keyboardFrameEnd.height < (self.view.window?.frame.height ?? 0)) && transformY > 0 {
UIView.animate(withDuration: duration, animations: {
self.view.frame.origin.y = -transformY
})
} else { // キーボードの表示範囲がテキストフィールドより下の場合にはViewControllerの一番外のビューを初期値に戻る
UIView.animate(withDuration: duration, animations: {
self.view.frame.origin.y = (self.isEmbed ? 0 : UIApplication.shared.statusBarFrame.size.height) + (self.navigationController?.navigationBar.intrinsicContentSize.height ?? 0)
})
}
}
}
/// 親ビューの中にキーボードが使用中のビューを取得する
///
/// - Parameter view: 親ビュー
/// - Returns: キーボードが使用中のビュー
func findFirstResponder(_ view: UIView) -> UIView? {
for subView in view.subviews {
if subView.isFirstResponder {
return subView
}
if let recursiveSubView = self.findFirstResponder(subView) {
return recursiveSubView
}
}
return nil
}
/// 指定されたタイプのビューはSubViewsの中にあるかどうか
///
/// - Parameters:
/// - subViews: 検索されたのSubViews
/// - type: ビューのタイプ
/// - Returns: 見つかった場合trueに戻ります
func isAnyTypeViewInSubViews<T : UIView>(subViews: [UIView], type: T.Type) -> Bool {
for subView in subViews {
if subView is T {
return true
}
if subView.subviews.count > 0 {
if isAnyTypeViewInSubViews(subViews: subView.subviews, type: type) {
return true
}
}
}
return false
}
/// ViewControllerにある編集中のテキストフィールドの編集状態を取り消し
func resignFirstResponderWithAllTextField() {
if let textField = findFirstResponder(self.view) as? UITextField {
textField.resignFirstResponder()
}
}
/// ViewControllerが破棄された時呼び出されるメソッド
deinit {
// ViewController deinitするとき、監視された対象が全て破棄します
NotificationCenter.default.removeObserver(self)
}
/// StoryBoardからViewControllerのインスタンスをとる
///
/// - Parameters:
/// - classType: 取得したいのViewControllerのクラスタイプ
/// - storyBoardId: ViewControllerを持つのStroyBoardId
/// - Returns: StoryBoardにあるViewControllerのインスタンス
private func instantiateViewController<T : UIViewController>(classType: T.Type) -> T {
var storyboardName = ""
if let storyboardInitialMark = classType.init() as? StoryboardInitialMark {
storyboardName = type(of: storyboardInitialMark).STORYBOARD_NAME
} else {
let viewControllers = self.navigationController?.viewControllers
let count = viewControllers?.count ?? 0
for i in (0 ..< count).reversed() {
if let storyboardInitialMark = viewControllers![i] as? StoryboardInitialMark {
storyboardName = type(of: storyboardInitialMark).STORYBOARD_NAME
break
}
}
}
return UIStoryboard(name: storyboardName, bundle: Bundle.main).instantiateViewController(withIdentifier: classType.className()) as! T
}
/// StoryBoardにあるViewControllerのインスタンスをpresentする
///
/// - Parameters:
/// - classType: ViewControllerのクラスタイプ
/// - storyBoardId: ViewControllerを持つのStroyBoardId
/// - prepare: pushしで前のViewControllerの処理
func presentViewController<T : UIViewController>(classType: T.Type, prepare: ((T) -> Void)? = nil) {
let viewController = instantiateViewController(classType: classType)
prepare?(viewController)
self.present(viewController, animated: true)
}
/// StoryBoardにあるViewControllerのインスタンスをPushする
///
/// - Parameters:
/// - classType: ViewControllerのクラスタイプ
/// - storyBoardId: ViewControllerを持つのStroyBoardId
/// - prepare: pushしで前のViewControllerの処理
func pushViewController<T : UIViewController>(classType: T.Type, prepare: ((T) -> Void)? = nil) {
let viewController = instantiateViewController(classType: classType)
prepare?(viewController)
self.navigationController?.pushViewController(viewController, animated: true)
}
/// 指定されたViewControllerまでに戻ります
///
/// - Parameter classType: 指定されたのViewControlerのクラスタイプ
func popToViewController<T : UIViewController>(classType: T.Type) {
guard let viewControllers = self.navigationController?.viewControllers else {
return
}
for viewController in viewControllers {
if viewController is T {
self.navigationController?.popToViewController(viewController, animated: true)
return
}
}
}
}