Swift に関する知見を書く場所です。 iOS関連になるかなと。 学びになったことをひたすら書いていきます。
SwiftUIのチュートリアルでできることが精一杯。まだまだできないことが多い。 「実用化までには、あと2年くらいはかかる」と勉強会で言われていた(2019/07/19)
一つのセルがあるとして、その中に複数の要素があるとする。 その時に、上下左右のマージンは 16 or 20 にする。 全ての外側のマージンの大きさは合わせる。
AutoLayoutは行列の計算で表される。 一つ一つの要素に一つの連立方程式があり、それを計算している。 つまり、labelなどの要素は高さや幅は決まっていた方が計算が早くなり、正確な値が出てくる。
labelの高さを決める時というのは、そのlabelが固定の文言であることや、長さが決まっている時である。 例えば、「名前」というラベルがあって、その下にユーザの名前のラベルがあるとする。 この時の「名前」のラベルは、不変のものなので、高さ、幅、マージンは指定する。 ユーザの名前のラベルは、もしかしたら、二行になる可能性があるので、高さと幅は決めることができない。名前がとても長い人がいたら、見えなくなってしまうからだ。
ほとんどの場合、値は決めて置くことがシステムのためになるが、可変になりそうなところだけはAutoLayoutに任せるのが吉だ。
条件式 ? trueの時: falseの時
isRead ? ReadCell.className:UnReadCell.className
! を使う。必ずアンラップが成功するときだけ使う
guard 文で先に離脱させたい時に使う
- 他に条件式があるとき
- そのスコープないだけでOKの時
if let や guard let を使うと冗長だと思った時
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Keyboard.configure()
Progress.configure()
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = TabBarController()
return true
}
selectedStyle = .none
↓↓↓↓↓↓こいつ使えない子かも。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// これを入れると、セレクトした後に、背景色が元に戻る。
tableView.deselectRow(at: indexPath, animated: true)
}
self.tableView.allowsSelection = false
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.row == 1 {
// セルの選択不可にする
cell.selectionStyle = .none
} else {
// セルの選択を許可
cell.selectionStyle = .default
}
return cell
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
switch indexPath.row {
case 0:
return indexPath
// 選択不可にしたい場合は"nil"を返す
case 1:
return nil
default:
return indexPath
}
}
https://teratail.com/questions/46828 右辺を自分の好きな形にしよう!
let distanceFromViewTop: CGFloat = 200
view.frame.height - distanceFromViewTop
tableHeight.constant = tableView.contentSize.height
extension TimelineViewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
navigationController?.setNavigationBarHidden(true, animated: true)
} else {
navigationController?.setNavigationBarHidden(false, animated: true)
}
}
}
extension RegistAuthUserViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController is TermOfServiceViewController {
viewController.dismiss(animated: true, completion: nil)
}
}
}
navigationController!.navigationBar.topItem!.title = " "
navigationController?.setNavigationBarHidden(false, animated: false)
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
let cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
let cell = UITableViewCell(style: .value2, reuseIdentifier: "cell")
cell.textLabel?.text = "東京都"
cell.detailTextLabel?.text = "ここが詳細テキストラベルです"
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
cell.imageView?.image = UIImage(named: "eyecatch-image")
guard let cell = collectionView.cellForItem(at: IndexPath(row: getNowWatchingCellIndex(collectionView), section: 0)) as? MovieCell else {
return
}
func getNowWatchingCellIndex(_ scrollView: UIScrollView) -> Int {
let nowVisiblePoint = scrollView.contentOffset.y
return Int(nowVisiblePoint / view.bounds.height)
}
https://stackoverflow.com/questions/48101233/dont-use-safearea-inset-on-iphone-x-for-first-uitableviewcell
Marking the "Content insets" under Size Inspector to "Never" worked for me.
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(didSwipe(sender:)))
leftSwipe.direction = .right
collectionView.addGestureRecognizer(leftSwipe)
leftSwipe.delegate = collectionView
// ここで他のジェスチャーも受け入れるかを判断。 delegateを CollectionViewに入れるのだ!
extension UICollectionView: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)
-> Bool {
return true
}
}
アイコンの文字を消す時は、baritem → image inset → bottom を −10, -12 にするとよい。
tabBarController?.tabBar.isHidden = true
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(test)))
protocol CommentInputViewDelegate: class {
func sendButtonTapped(text: String)
}
class CommentInputView: UIView {
@IBOutlet weak var commentField: UITextField!
@IBOutlet weak var sendButton: UIButton!
weak var delegate: CommentInputViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.setFromXib()
sendButton.isEnabled = false
commentField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setFromXib()
}
func setFromXib() {
let nib = UINib.init(nibName: self.className, bundle: nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
self.addSubview(view)
// カスタムViewのサイズを自分自身と同じサイズにする
view.translatesAutoresizingMaskIntoConstraints = false
let bindings = ["view": view]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|",
options:NSLayoutConstraint.FormatOptions(rawValue: 0),
metrics:nil,
views: bindings))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|",
options:NSLayoutConstraint.FormatOptions(rawValue: 0),
metrics:nil,
views: bindings))
}
var className: String {
get {
return type(of: self).className
}
}
@IBAction func sendButtonTapped(_ sender: Any) {
guard let text = commentField.text else {
return
}
if text.isEmpty {
return
}
commentField.resignFirstResponder()
commentField.text = String()
delegate?.sendButtonTapped(text: text)
sendButton.isEnabled = false
}
}
textField.attributedPlaceholder = NSAttributedString(string: "名前を入力してください。", attributes: [NSAttributedString.Key.foregroundColor : UIColor.red])
UITextField.appearance().tintColor = .white
ポイントは view.addSubView(toolBar) は viewWillAppear で行うこと! https://qiita.com/kamesoft/items/09386431c94eb922ee4c
1、WKWebViewはStoryboardからは貼り付けられない
2、view = webView
ではなく、addSubviewで画面にのせる
3、Storyboardでおいたものはすでに生成されているので、webView = WKWebView(frame: .zero, configuration: webConfiguration)
でもう一度生成すると二つwebViewができてしまう
なのでとりあえず、
storyboardのwebviewは消して、コードで生成したwkwebviewを使うか、もしくはその逆で行くかですね!
stackViewが表示されないのは、
view = webView
でstackViewが置かれているview自身をwebViewに変更してしまってるため、表示されていないということですね!
AudioServicesPlaySystemSound(SystemSoundID("1519")!)
tableView の return 〇〇.countの時。
〇〇がオプショナルの場合、
if let or guard let を使うけど、省略したい場合、
return 〇〇?.count ?? 0
にすると省略できる。
// グループを作って
let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "queue", attributes: .concurrent)
snapShot.documents.forEach({ (document) in
// 一つ目をグループに入れて、
dispatchGroup.enter()
dispatchQueue.async {
let id = document.documentID
let isEntried = [String](entriedOffers.keys).contains(document.documentID)
var isConfirmed: Bool = false
if isEntried {
isConfirmed = entriedOffers[document.documentID] ?? false
}
var entryStatus: Offer.EntryStatus
if isEntried && isConfirmed {
entryStatus = .confirmed
} else if isEntried {
entryStatus = .entried
} else {
entryStatus = .notEntried
}
let offer = Offer(id: id, document: document.data(), entryStatus: entryStatus)
offers.append(offer)
dispatchGroup.leave()
}
})
dispatchGroup.notify(queue: .main) {
completion(offers, nil)
}
// 重たい処理
DispatchQueue.global().async {
}
// UIを更新する処理
DispatchQueue.main.async {
weakSelf.tableView.reloadData()
}
初期化のタイミングをずらすことができる。 アクセスした時に値が決定する。と思う。
### lazyなし
var defaultPrice = 100
class Item {
var price: Int = defaultPrice
}
let item = Item()
defaultPrice = 200
print(item.price) // 100
### lazyあり
var defaultPrice = 100
class Item {
lazy var price: Int = defaultPrice
}
let item = Item()
defaultPrice = 200
print(item.price) // 200
defaultPrice = 300
print(item.price) // 200
/// または、 /** * */ で記述する ドキュメントコメントではMarkdownが使用できる。
https://scior.hatenablog.com/entry/2018/12/29/100000
formatter.dateFormat = "yyyy/MM/dd HH:mm"
hhだと24時間表記にならない mmだと、表示がおかしくなる
enum databaseError: Error {
case entry
case def
case out
}
func test() -> Result<String,Error> {
if let id = messages[0].messageId {
return .success(id)
}
return .failure(databaseError.entry)
}
参照: https://qiita.com/s_emoto/items/deda5abcb0adc2217e86
String, Int, Double, Data, Date, URL
Array, Dictionary, Optional → 中身がCodableの場合 struct → Codableなプロパティのみから構成されている場合
User.swift
struct Friend: Codable { // -> プロパティが全てCodableなのでCodable
name: String // -> Codable
age: Int // -> Codable
}
struct User: Codable {
name: String
age: Int
friends: [Friend] // -> 中身がCodableなのでCodable
startDate: Date
role: String
}
上記のように記述すればCodableとして使えるけど、EncodeとDecodeでキー名が異なる時に一対一対応させる必要がある。その時に使うのが「CodingKeys」。
struct User: Codable {
name: String
age: Int
friends: [Friend]
startDate: Date
role: String
enum CodingKeys: String, CodingKey {
case name
case age
case friends
case startDate = "start_date" // ← これ
case role
}
}
【実装する時の決まり事】 ・enumの名前は「CodingKeys」にする ・case名をプロパティ名、rawValueをエンコード結果のフィールド名として定義する ・case自体を省略するとエンコード・デコードされない。この時、Decodableにするにはdefault valueが必要。
例で示した@「SnakeCase ↔︎ CamelCase」の場合は Swift4.1以降では、DecoderのkeyDecodingStrategyを使うと省略できるみたいです。
参考:https://www.chrisjmendez.com/2017/05/02/modify-systemversion-plist-within-macos-sierra/
リカバリーモードへ。 コマンド+R (ちゃんとコマンドのところ。Capslockはダメ) リカバリーモードのターミナル開く→自分のアカウントから入る csrutil disable 再起動
コピー取っとく
sudo cp /System/Library/CoreServices/SystemVersion.plist ~/SystemVersion.plist.bak
書き換える
sudo nano -W /System/Library/CoreServices/SystemVersion.plist
戻す リカバリーモードのターミナル
csrutil enable
Podfile と Target の iOS バージョンが異なっている。全てのTarget のバージョンを合わせる。 https://teratail.com/questions/99422
Jump to Definision でUIKitの主要な部品を見ていく。
UIContorolを継承している UIButton > UIControl > UIView > UIResponder > NSObject >
// .Selected
let mySelectedAttributedTitle =
NSAttributedString(string: "selected button",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.green,
])
button.setAttributedTitle(mySelectedAttributedTitle, for: .selected)
// .Normal
let myNormalAttributedTitle =
NSAttributedString(string: "Click Here",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.blue,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 24)])
button.setAttributedTitle(myNormalAttributedTitle, for: .normal)
@IBAction func buttonTapped(_ sender: UIButton, forEvent event: UIEvent) {
print(event)
button.isSelected = !button.isSelected
}
UIButtonの気づき isSelectedを変更することで、ボタンの表示を変えたりすることができる。 押された時にisSelectedを button.isSelected = !button.isSelected とする。 押された時にeventを取得できる
プロダクトのUIButton
Animations Changes to several view properties can be animated—that is, changing the property creates an animation starting at the current value and ending at the new value that you specify. The following properties of the UIView class are animatable:
- frame
- bounds
- center
- transform
- alpha
- backgroundColor
フォント。UIFontを指定
文字色。UIColorを指定
文字背景色。UIColorを指定
文字間隔。floatを指定。2.0など
NSUnderlineStyleのrawValueを指定 NSUnderlineStyle.styleSingle.rawValueなど
縁取りの色。UIColorを指定
縁取りの幅。floatを指定。 正の値のときは縁取りの内側はclearColorになる。.foregroundの指定は無視される 負の値のときは縁取りの内側は.foregroundColorになる(通常はこちらを使うと思う)
文字の影。NSShadowオブジェクトを指定
// アラートが出る
alart = UIAlertController(title: "よっ", message: "こんにちは", preferredStyle: .alert)
// 下にシートが出る
alart = UIAlertController(title: "よっ", message: "こんにちは", preferredStyle: .actionSheet)
// 広告ID設定
#if DEBUG
// テスト用
admobView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
#else
// 本番用
admobView.adUnitID = "realID"
#endif
以下のコードがマクロと呼ばれるもの。 Swiftのコードとは違って、#で始まる。 アプリの全体に関わるコード。自分は主に、リリースやデバッグモードを変更する時にしか使わない。
#if DEBUG
#else
リリースしたアプリのdSYMをFirebase/CrashlyticsにアップロードするTips
dSYMのアップロードはBitrise/fastlaneで自動化しとくといいぞ
Firebase CrashlyticsへdSYMをアップロードする
fastlane ドキュメント https://docs.fastlane.tools/actions/download_dsyms/ https://docs.fastlane.tools/actions/upload_symbols_to_crashlytics/ https://docs.fastlane.tools/actions/clean_build_artifacts/