俺得なので誰の参考にもならない
swift書いてて毎度書き方を忘れたり仕様を忘れたり存在自体を忘れたりしてるやつらのまとめ
SceneDelegate.swiftから起動
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else {
return
}
let window = UIWindow(windowScene: scene)
self.window = window
let vc = UIViewController()
vc.view.backgroundColor = .systemRed
window.rootViewController = vc
window.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
}
func sceneWillResignActive(_ scene: UIScene) {
}
func sceneWillEnterForeground(_ scene: UIScene) {
}
func sceneDidEnterBackground(_ scene: UIScene) {
}
}
#実行時間計測
##実装
final class Benchmark {
static func measure(_ message: String = "", block: () -> Void) {
let startTime = Date()
block()
let elapsed = Date().timeIntervalSince(startTime) as Double
let formatedElapsed = String(format: "%.3f", elapsed)
print("Benchmark: \(message), 実行時間: \(formatedElapsed)(s)")
}
}
##使い方
計測したい処理をクロージャに入れる
Benchmark.measure("append") {
var appendList: [String] = []
for v in 1...1000 {
appendList.append(v.description)
}
}
Benchmark.measure("map") {
let _ = Array(1...1000).map { $0.description }
}
#Xcode_previews
UIView,UIViewControllerと連携
extension
#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13.0, *)
enum PreviewDeviceType: String {
case se1 = "iPhone SE (1st generation)"
case eight = "iPhone 8"
case x = "iPhone X"
case se2 = "iPhone SE (2nd generation)"
case eleven = "iPhone 11"
case elevenProMax = "iPhone 11 Pro Max"
var device: PreviewDevice { PreviewDevice(rawValue: self.rawValue) }
var displayName: String {
switch self {
case .se1: return "SE1"
case .eight: return "8"
case .x: return "X"
case .se2: return "SE2"
case .eleven: return "11"
case .elevenProMax: return "11 Pro Max"
}
}
}
@available(iOS 13.0, *)
extension UIView {
private struct Preview: UIViewRepresentable {
let view: UIView
func makeUIView(context: Context) -> UIView { view }
func updateUIView(_ uiView: UIView, context: Context) { }
}
func toPreview() -> some View {
Preview(view: self)
}
}
@available(iOS 13.0, *)
extension UIViewController {
private struct Preview: UIViewControllerRepresentable {
let viewController: UIViewController
func makeUIViewController(context: Context) -> UIViewController { viewController }
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
}
func toPreview() -> some View {
Preview(viewController: self)
}
}
#endif
使う側
モデル
struct PreviewModel {
var texts: [String] = []
}
Viewで使う
import SwiftUI
import UIKit
import SnapKit
final class TestPreviewView: UIView {
private let labels: [UILabel] = [.init(), .init(), .init(), .init()]
override init(frame: CGRect) {
super.init(frame: frame)
self.setupLayout()
}
func configure(model: PreviewModel) {
model.texts.enumerated().forEach { (index, text) in
labels[index].text = text
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private extension TestPreviewView {
func setupLayout() {
let stackView: UIStackView = .init()
self.addSubview(stackView)
stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .fill
let customSpaces: [CGFloat] = [30, 10, 40, 0]
for (idx, label) in labels.enumerated() {
label.setHuggingCompressionResitanceAll(priority: .required)
label.numberOfLines = 0
label.textColor = .black
stackView.addArrangedSubview(label)
stackView.setCustomSpacing(customSpaces[idx], after: label)
}
let space = UIView()
stackView.addArrangedSubview(space)
}
}
#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13.0, *)
struct TestPreviewViewRepresentable_Previews: PreviewProvider {
static var previews: some View {
let view1 = TestPreviewView()
view1.configure(model: PreviewModel(texts: ["label1aaaaa", "label2", "label3", "label4"]))
let view2 = TestPreviewView()
view2.configure(model: PreviewModel(texts: ["label1だってばよよおおおおお", "吾輩は猫である", "名前は", "まだないかもしれないんだあああああああああ"]))
return Group {
view1.toPreview()
.previewLayout(.fixed(width: 400, height: 200))
.previewDisplayName("iPhone X")
view2.toPreview()
.previewLayout(.fixed(width: 200, height: 200))
.previewDisplayName("iPhone SE1")
}
}
}
#endif
controllerで使う
import UIKit
final class TestPreviewViewController: UIViewController {
private let labels: [UILabel] = [.init(), .init(), .init(), .init()]
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(nibName: nil, bundle: nil)
self.view.backgroundColor = .systemPink
self.setupLayout()
}
func configure(model: PreviewModel) {
model.texts.enumerated().forEach {[weak self] (index, text) in
self?.labels[index].text = text
}
}
private func setupLayout() {
let stackView: UIStackView = .init()
self.view.addSubview(stackView)
stackView.snp.makeConstraints {
$0.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
$0.left.equalTo(self.view.safeAreaLayoutGuide.snp.left)
$0.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
$0.right.equalTo(self.view.safeAreaLayoutGuide.snp.right)
}
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .fill
let customSpaces: [CGFloat] = [30, 10, 40, 0]
for (idx, label) in labels.enumerated() {
label.setHuggingCompressionResitanceAll(priority: .required)
label.numberOfLines = 0
label.textColor = .black
stackView.addArrangedSubview(label)
stackView.setCustomSpacing(customSpaces[idx], after: label)
}
let space = UIView()
stackView.addArrangedSubview(space)
}
}
#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13, *)
struct PreviewTestViewControllerRepresentable_Previews: PreviewProvider {
static var previews: some View {
let vc1 = TestPreviewViewController()
vc1.configure(model: PreviewModel(texts: ["label1aaaaa", "label2", "label3", "label4"]))
let vc2 = TestPreviewViewController()
vc2.configure(model: PreviewModel(texts: ["label1だってばよよおおおおお", "吾輩は猫である", "名前は", "まだないかもしれないんだあああああああああ"]))
return Group {
vc1.toPreview()
.previewDevice(PreviewDeiceEnum.se2.device)
.previewDisplayName(PreviewDeiceEnum.se2.displayName)
vc2.toPreview()
.previewDevice(PreviewDeiceEnum.eleven.device)
.previewDisplayName(PreviewDeiceEnum.eleven.displayName)
}
}
}
#endif
シミュレータ
一覧を表示
xcrun simctl list devicetypes
アノテーション
discardableResult
参考
デフォルトで関数の戻り値を使わない時に警告が出ないようにする
func test() -> Int { 1 }
test() // Result of call to 'test()' is unused の警告が出る
@discardableResult
func test() -> Int { 1 }
test() // 警告が出ない
プリプロセッサマクロ
swiftのバージョンで切り替え
#if swift(>=4.2)
#else
#endif
クラス名を取得
self.classForCoder
Class Test {
static var className: String {
String(describing: Test.self)
}
}
Optionalにextension
String?ならこんな感じ
extension Optional where Wrapped == String {
var isEmpty: Bool { self == nil || self!.isEmpty }
}
制約
##コードでautolayout
いつもsnapkitを使うから標準のやり方を忘れる
class NormalViewController: UIViewController {
//xibファイルを読み込む
private let headerView: HeaderView = UINib.init(nibName: "Header", bundle: nil).instantiate(withOwner: nil, options: nil).first as! HeaderView
@IBOutlet weak var headerContainerView: UIView! {
didSet {
headerContainerView.addSubview(headerView)
//falseにする
headerView.translatesAutoresizingMaskIntoConstraints = false
//制約をつける
headerView.topAnchor.constraint(equalTo: headerContainerView.topAnchor, constant: 100).isActive = true
headerView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor).isActive = true
headerView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor).isActive = true
headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor).isActive = true
//こうやって制約をまとめて、最後にactiveにもできる
// let constraints = [
// headerView.topAnchor.constraint(equalTo: headerContainerView.topAnchor),
// headerView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor),
// headerView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor),
// headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor)
// ]
// NSLayoutConstraint.activate(constraints)
}
}
override func viewDidLoad() {
super.viewDidLoad()
headerView.configure()
}
}
コードで制約貼る時によく使う処理のextension
まあこれをたくさん用意したのがsnapkitなんだが
struct UIEdgePriorities {
var top: UILayoutPriority = .required
var leading: UILayoutPriority = .required
var trailing: UILayoutPriority = .required
var bottom: UILayoutPriority = .required
}
extension UIView {
func widthEqualTo(_ width: CGFloat) {
self.translatesAutoresizingMaskIntoConstraints = false
self.widthAnchor.constraint(equalToConstant: width).isActive = true
}
func heightEqualTo(_ height: CGFloat) {
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(equalToConstant: height).isActive = true
}
func sizeEqualTo(_ size: CGSize) {
self.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
self.widthAnchor.constraint(equalToConstant: size.width),
self.heightAnchor.constraint(equalToConstant: size.height)
]
NSLayoutConstraint.activate(constraints)
}
func edgesEqualToSuperView(margin: UIEdgeInsets = .zero, priorities: UIEdgePriorities = .init()) {
guard let _superview = superview else { return }
self.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
self.topAnchor.constraint(equalTo: _superview.topAnchor, constant: margin.top),
self.leadingAnchor.constraint(equalTo: _superview.leadingAnchor, constant: margin.left),
_superview.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: margin.right),
_superview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: margin.bottom)
]
constraints[0].priority = priorities.top
constraints[1].priority = priorities.leading
constraints[2].priority = priorities.trailing
constraints[3].priority = priorities.bottom
NSLayoutConstraint.activate(constraints)
}
func setHuggingCompressionResitanceAll(priority: UILayoutPriority = .defaultHigh) {
self.setContentHuggingPriority(priority, for: .horizontal)
self.setContentHuggingPriority(priority, for: .vertical)
self.setContentCompressionResistancePriority(priority, for: .horizontal)
self.setContentCompressionResistancePriority(priority, for: .vertical)
}
}
UICollectionView, UITableView
画面内のセルで一番上のセルのindxPathを取得
extension UICollectionView {
// cellHeightRatioはセルがどの程度隠れていたら見えていないセルと判断するか決めるパラメータ
func getTopVisibleIndexPath(cellHeightRatio: CGFloat = 0.0) -> IndexPath? {
let index = self.visibleCells.enumerated().filter { (args) -> Bool in
let (_, cell) = args
return self.contentOffset.y < (cell.frame.minY + cell.frame.size.height * cellHeightRatio)
}
.sorted { (lhs, rhs) -> Bool in
let lCell = lhs.element
let rCell = rhs.element
return lCell.frame.minY < rCell.frame.minY
}.map { $0.offset }.first
guard let _index = index else { return nil }
return self.indexPathsForVisibleItems[_index]
}
}
extension UITableView {
func getTopVisibleIndexPath(cellHeightRatio: CGFloat = 0.0) -> IndexPath? {
let index = self.visibleCells.enumerated().filter { (args) -> Bool in
let (_, cell) = args
return self.contentOffset.y < (cell.frame.minY + cell.frame.size.height * cellHeightRatio)
}
.sorted { (lhs, rhs) -> Bool in
let lCell = lhs.element
let rCell = rhs.element
return lCell.frame.minY < rCell.frame.minY
}.map { $0.offset }.first
guard let _index = index else { return nil }
return self.indexPathsForVisibleRows?[_index]
}
}
subscript
参考というか全て解説されてる
配列の要素に安全にアクセス
extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
let arr = [1, 2, 3, 4, 5]
arr[safe: 0] // 1
arr[safe: 5] // nil
arr[5] // crash
配列の要素から安全に抜き出す
extension Array {
subscript(safe range: ClosedRange<Int>) -> Self {
guard let first = range.first, let last = range.last,
self.count > first else {
return self
}
if self.count <= first + last {
let nRange = first...(self.count - 1)
return self[nRange].map { $0 }
}
return self[range].map { $0 }
}
subscript(safe range: Range<Int>) -> Self {
guard let first = range.first, let last = range.last,
self.count > first else {
return self
}
if self.count <= last {
let nRange = first...(self.count - 1)
return self[nRange].map { $0 }
}
return self[range].map { $0 }
}
}
let arr = [1,2,3,4,5]
arr[safe: 0...2] // [1, 2, 3]
arr[safe: 0..<2] // [1, 2]
arr[safe: 3...5] // [4, 5]
arr[safe: 3..<6] // [4, 5]
arr[3...5] // crash
arr[3..<6] // crash
weak
循環参照を避けるために使うと頭で分かっても定型文的にweak書いてなんでだっけと忘れてしまうのでメモ
クロージャ
インスタンスがクロージャを強参照として持ち、クロージャが内部でselfを使うとインスタンスを強参照として持ってしまうので
class A{
var count: Int = 0
init() {
debugPrint("\(String(describing: type(of:self))) \(#function)")
}
deinit {
debugPrint("\(String(describing: type(of:self))) \(#function)")
}
lazy var plusOneNoWeak: () -> Void = { self.count += 1 }
lazy var plusOneWeak: () -> Void = { [weak self] in self?.count += 1 }
}
//スコープ内で実行して開放するか確認
do{
let a = A()
a.plusOneWeak()
//こっち実行したら開放しなくなっちゃうよ
// a.plusOneNoWeak()
}
/*出力
"A init()"
"A deinit"
*/
mutating
ここに書いてる通りだけど
struct
, enum
, extension
で自身の値を変更するfuncの場合にmutatingキーワードを書く
unowned
ここに詳しく
nil
にならないweak
unowned
かweak
どっち使うか迷うぐらいならweak
にしとけ
Enum
Asociated Valueの取り出し方
本来はswitch
でいいけどif
で取り出したいこともある
import Foundation
enum Test {
case one(v: Int)
case two(v: Int)
}
let test = Test.one(v: 20)
if case .one(let v) = test, v == 10{
print(v)
}
UIImageの操作
矩形で切り抜き(クロッピング)
extension UIImage{
func cropped(to rect: CGRect) -> UIImage? {
let scaleRect = CGRect(x: rect.minX * self.scale, y: rect.minY * self.scale, width: rect.width * self.scale, height: rect.height * self.scale)
guard
let imgRef = self.cgImage?.cropping(to: scaleRect) else {
return nil
}
return UIImage(cgImage: imgRef, scale: scale, orientation: imageOrientation)
}
}
Orientaionの修正
extension UIImage{
func fixedOrientation() -> UIImage {
guard
imageOrientation != UIImage.Orientation.up,
let cgImage = self.cgImage,
let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
return self
}
var transform: CGAffineTransform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat.pi)
break
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat.pi / 2.0)
break
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat.pi / -2.0)
break
default:
break
}
switch imageOrientation {
case .upMirrored, .downMirrored:
transform.translatedBy(x: size.width, y: 0)
transform.scaledBy(x: -1, y: 1)
break
case .leftMirrored, .rightMirrored:
transform.translatedBy(x: size.height, y: 0)
transform.scaledBy(x: -1, y: 1)
default:
break
}
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
break
}
guard let newCGImage = ctx.makeImage() else { return self }
return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
}
}
どこでもタッチが反応するUISlider
class TappableSlider: UISlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
//タッチした位置にvalueを変更
let tapPoint = touch.location(in: self)
let tapValue = min(1.0, max(0.0, tapPoint.x / self.frame.size.width))
self.value = Float(tapValue)
// どんなtouchでもスライダー調節を行う
return true
}
}
PanGestureの方向
enum PanDirection{
case up
case down
case left
case right
}
extension UIPanGestureRecognizer{
func getPanDirection(inView:UIView)->PanDirection{
let velocity = self.velocity(in: inView)
return fabs(velocity.y) > fabs(velocity.x) ?
(velocity.y > 0 ? .up : .down) :
(velocity.x > 0 ? .right : .left)
}
}
自作オペレータ
// これはオペレータですよと定義
infix operator =~
//そのオペレータの実装
func =~(lhs: String, rhs: String) -> Bool {
/*
何かしらの処理
*/
return true
}
配列
初期化
var intArray = [Int](repeating: 5, count: 100) //[5, 5, 5,...,5]
var strArray = [String](repeating: "hello", count: 50) //["hello", "hello",...,"hello"]
var intArray2 = Array(1...100) //[1, 2, 3, ..., 98, 99, 100]
ランダム
["あいうえお", "かきくけこ"].randomElement() // "あいうえお" or "かきくけこ" がランダムで
配列から毎回違う要素をランダムで選択
let arr = ["あいうえお", "かきくけこ", "さしすせそ"]
var selectedStrInArr:String=""
selectedStrInArr = arr.filter({$0 != selectedStrInArr}).shuffled().first
結合
var a = [1,2,3,4,5]
var b = [10,11,12,13,14,15]
var c = a + b // [1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15] cに代入
a.append(contentsOf: b) //[1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15] aが変わる
var aa = [[1,2,3],[4,5,6]]
var bb = [[11,12,13],[14,15,16]]
var cc = aa + bb // [[1, 2, 3], [4, 5, 6], [11, 12, 13], [14, 15, 16]] ccに代入
aa.append(contentsOf: bb) // [[1, 2, 3], [4, 5, 6], [11, 12, 13], [14, 15, 16]] aaが変わる
bb.append(b) //[[11, 12, 13], [14, 15, 16], [10, 11, 12, 13, 14, 15]] bbが変わる
ユニークにする
extension Array where Element: Equatable {
var unique: [Element] {
return reduce([Element](), { (result, sequence) in
result.contains(sequence) ? result : result + [sequence]
})
}
}
大文字だけか判定
str.components(separatedBy: CharacterSet.uppercaseLetters.inverted).joined() == str
//もしくは
str.allSatisfy(\.isUppercase)
String
String -> Int
let _ = Int("12") // -> 12
let _ = Int("012") // -> 12
let _ = Int("+12") // -> 12
let _ = Int("-12") // -> -12
let _ = Int("12.3") // -> nil
指定の文字列の数を数える
20200105更新
もっとシンプルな書き方があった
//改行コードの数を数えてみる
let a = "あいうえお\nかきく\nけこ"
let c = a..components(separatedBy: "\n").count - 1 // 2
参考
これもう古い
extension String {
func numberOfOccurrences(of word: String) -> Int {
var count = 0
var nextRange = self.startIndex..<self.endIndex
while let range = self.range(of: word, options: .caseInsensitive, range: nextRange) {
count += 1
nextRange = range.upperBound..<self.endIndex
}
return count
}
}
//改行コードの数を数えてみる
let a = "あいうえお\nかきく\nけこ"
let c = a.numberOfOccurrences(of: "\n") // 2
半角数値(またはそれ以外)の数を数える
全角数値があると数が狂う
let a = "あいうえお10かきく225けこ" //全部で15文字
//半角数値の数
a.components(separatedBy: CharacterSet.decimalDigits).count - 1 //5
//半角数値以外の数
a.components(separatedBy: CharacterSet.decimalDigits.inverted).count - 1 //10
指定した回数だけ同じ文字列を繰り返す
let times = 3
let orgStr = "ABCDEFG"
(0...times).reduce("") { (str, _) -> String in orgStr + "・" + str }
// -> ABCDEFG・ABCDEFG・ABCDEFG・
指定した長さと文字のランダム文字列を生成
//charactersの中の文字からランダムに選ばれる
func randomString(length: Int, characters:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") -> String {
return Array(1...length).reduce(into: "", { (str, _) in
return str.append(characters.randomElement()!)
})
}
CGRectの操作
// 元のCGRect
let f1 = CGRect(x: 10, y: 20, width: 30, height: 40) //{x 10 y 20 w 30 h 40}
//dx,dy分、f1の中に入るCGRect (originだけがずれるわけじゃないよ)
let f2 = f1.insetBy(dx: 5, dy: 5) //{x 15 y 25 w 20 h 30}
//insetByの細かく指定できる版
let f3 = f1.inset(by: UIEdgeInsets.init(top: 5, left: 5, bottom: 10, right: 10)) //(15.0, 25.0, 15.0, 25.0)
//指定した位置でframeを分割する
let f4 = f1.divided(atDistance: 5, from: CGRectEdge.minXEdge) //slice: (10.0, 20.0, 5.0, 40.0), remainder: (15.0, 20.0, 25.0, 40.0))
Autolayout
コードで制約を貼る
ビューの真ん中にボタンを置く例
let btn = UIButton.init()
btn.setTitle("ログイン", for: .normal)
btn.setTitleColor(.black, for: .normal)
self.view.addSubview(btn)
//AutoresizingMaskの設定をconstraintに反映させるかどうか (古い仕様との互換性のためで、今時意味はないので常にfalse
btn.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
btn.widthAnchor.constraint(greaterThanOrEqualToConstant: 100),//横幅100以上
btn.heightAnchor.constraint(equalToConstant: 100),//縦幅100
NSLayoutConstraint(item: btn, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0),//centerXをviewのcenterXに合わせる
NSLayoutConstraint(item: btn, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0),//centerYをviewのcenterYに合わせる
//なぜかこれは警告が出る
// btn.centerXAnchor.constraint(equalToSystemSpacingAfter: self.view.centerXAnchor, multiplier: 1.0),
// btn.centerYAnchor.constraint(equalToSystemSpacingBelow: self.view.centerYAnchor, multiplier: 1.0)
])
StackView in CollcectionViewCell
CollectionViewCellの中にStackViewを入れるときはひとつViewを挟む
セルの高さの制約はpriorityを750にしとく
Grand Central Dispatch(GCD)
GCDクラス
class GCD{
// 時間のかかる処理
private static func doSomething(number: Int) {
print("------- Task \(number): Start -------")
for i in 0 ... 5 {
usleep(500000)//0.5sec待ち
print("Task \(number): Running ... \(i * 20)%")
}
print("------- Task \(number): Completed -------")
}
// すべて直列で処理
static func inMainQueueOnly() {
for i in 1 ... 3 {
doSomething(number: i)
}
print("All tasks completed.")
}
// キューを並列で処理
static func inConcurrentQueue() {
let queue = DispatchQueue.global(qos: .default)
for i in 1 ... 3 {
queue.async {
self.doSomething(number: i)
}
}
print("ここはキューの処理完了を待たずに実行される")
}
// 複数のキューを並列処理した後に、さらに処理を行う
static func doMoreAfterConcurretQueuesUsingDispatchGroup() {
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .default)
for i in 1 ... 3 {
group.enter()
queue.async {
self.doSomething(number: i)
group.leave()
}
}
group.notify(queue: DispatchQueue.global(qos: .default)) {
print("すべてのキューの処理が完了しました")
}
}
// 何か処理した後に値を返す(セマフォを使用)
static func returnsValueAfterAQueueUsingSemaphore() -> String {
var string = "初期値"
let semaphore = DispatchSemaphore(value: 0)
let queue = DispatchQueue.global(qos: .default)
for i in 1 ... 3 {
queue.sync {
self.doSomething(number: i)
string += " => Task\(i)が終了"
semaphore.signal()
}
}
for _ in 1 ... 3 {
semaphore.wait()
}
return string
}
// キューを並列処理した後に値を返す(セマフォを使用)
static func returnsValueAfterConcurrentQueuesUsingSemaphore() -> String {
var string = "初期値"
let semaphore = DispatchSemaphore(value: 0)
let queue = DispatchQueue.global(qos: .default)
for i in 1 ... 3 {
queue.async {
self.doSomething(number: i)
string += " => Task\(i)が終了"
semaphore.signal()
}
}
for _ in 1 ... 3 {
semaphore.wait()
}
return string
}
}
直列で処理
GCD.inMainQueueOnly()
並列で処理
GCD.inConcurrentQueue()
並列処理した後に処理を実行する
GCD.doMoreAfterConcurretQueuesUsingDispatchGroup()
直列処理した後に値を返す
let result = GCD.returnsValueAfterAQueueUsingSemaphore()
print(result)
並列処理した後に値を返す
let result = GCD.returnsValueAfterConcurrentQueuesUsingSemaphore()
print(result)
ふたつのTableViewのスクロールを同期させる
RxSwift
ありきの実装
tableView1.rx.didScroll.asObservable()
.filter({(_) -> Bool in
self.tableView2.contentOffset.y != self.tableView1.contentOffset.y
})
.subscribe(onNext: {[weak self] (_) in
guard let wSelf = self else{return}
let x = wSelf.tableView2.contentOffset.x
let y = wSelf.tableView1.contentOffset.y
wSelf.tableView2.setContentOffset(CGPoint(x:x, y:y), animated: false)
}).disposed(by: disposeBag)
tableView2.rx.didScroll.asObservable()
.filter({ (_) -> Bool in
self.tableView2.contentOffset.y != self.tableView1.contentOffset.y
})
.subscribe(onNext: {[weak self] (_) in
guard let wSelf = self else{return}
let x = wSelf.tableView1.contentOffset.x
let y = wSelf.tableView2.contentOffset.y
wSelf.tableView1.setContentOffset(CGPoint(x:x, y:y), animated: false)
}).disposed(by: disposeBag)
Xcode
Storybaord内の文字列検索
Xcodeではヒットしないのでgrepでやる
grep -i -r --include=*.storyboard <検索文字列> <検索パス>
複数のXcodeがあるときのCLI対応
現在のXcodeのパスを確認
xcode-select -print-path
Xcodeの切り替え
sudo xcode-select -switch /Applications/<指定のXcode>/Contents/Developer