SwiftやiOSの便利だけど忘れそうな小ネタ集

俺得なので誰の参考にもならない
swift書いてて毎度書き方を忘れたり仕様を忘れたり存在自体を忘れたりしてるやつらのまとめ

# SceneDelegate.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) {
  }
}

```

#実行時間計測

##実装
```swift
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)")
  }
}
```

##使い方
計測したい処理をクロージャに入れる

```swift
Benchmark.measure("append") {
  var appendList: [String] = []
  for v in 1...1000 {
    appendList.append(v.description)
  }
}
```
```swift
Benchmark.measure("map") {
  let _ = Array(1...1000).map { $0.description }
}
```

#Xcode_previews

## UIView,UIViewControllerと連携

### extension

```View_ViewController+Representable.swift
#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


```

### 使う側

モデル

```PreviewModel.swift
struct PreviewModel {
  var texts: [String] = []
}
```

Viewで使う

```TestPreviewView.swift

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で使う

```TestPreviewViewController.swift
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

```

# シミュレータ

## 一覧を表示
```sh
xcrun simctl list devicetypes
```


# アノテーション

## discardableResult
[参考](https://tech-blog.sgr-ksmt.org/2016/09/24/discardable_result/)
デフォルトで関数の戻り値を使わない時に警告が出ないようにする

```test.swift
func test() -> Int { 1 }
test() // Result of call to 'test()' is unused の警告が出る
```

```test.swift
@discardableResult
func test() -> Int { 1 }
test() // 警告が出ない
```

# プリプロセッサマクロ

## swiftのバージョンで切り替え
```swift
#if swift(>=4.2)
#else
#endif
```

# クラス名を取得

```
self.classForCoder
```

```swift
Class Test {
  static var className: String {
    String(describing: Test.self)
  }
}
```
# Optionalにextension

String?ならこんな感じ

```swift
extension Optional where Wrapped == String {
    var isEmpty: Bool { self == nil || self!.isEmpty }
}
```

# 制約

##コードでautolayout
いつもsnapkitを使うから標準のやり方を忘れる

```swift

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なんだが

```swift
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を取得

```swift
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
[参考](https://qiita.com/happy_ryo/items/72b68859ed8ace9f5fb4)というか全て解説されてる

## 配列の要素に安全にアクセス

```swift

extension Collection {
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
```

```swift
let arr = [1, 2, 3, 4, 5]
arr[safe: 0] // 1
arr[safe: 5] // nil
arr[5] // crash
```

## 配列の要素から安全に抜き出す

```swift
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 }
    }
}

```

```swift
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を使うとインスタンスを強参照として持ってしまうので

```swift
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
[ここに書いてる通り](https://qiita.com/shtnkgm/items/3a00117c9b918616d116)だけど
`struct`, `enum`, `extension`で自身の値を変更するfuncの場合にmutatingキーワードを書く

# unowned
[ここに詳しく](https://qiita.com/susieyy/items/39efd4d34cf4c1f9d3ef#%E5%8F%82%E8%80%83%E8%B3%87%E6%96%99)
`nil`にならない`weak`
`unowned`か`weak`どっち使うか迷うぐらいなら`weak`にしとけ


# Enum

## Asociated Valueの取り出し方

[ここに詳しく](https://qiita.com/y_koh/items/204f04ab11677bd73444)

本来は`switch`でいいけど`if`で取り出したいこともある

```.swift
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の操作

## 矩形で切り抜き(クロッピング)

```UIImage+crop.swift
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の修正
```UIImage+fixOrientation.swift
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

[参考](https://qiita.com/ruwatana/items/01a9c43057f71560580f)

```TappableSlider.swift
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の方向
```pan+extension.swift
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)
  }
}
```


# 自作オペレータ
[参考](https://qiita.com/shimesaba/items/cb36c0120a0a9f509da7)

```swift
// これはオペレータですよと定義
infix operator =~

//そのオペレータの実装
func =~(lhs: String, rhs: String) -> Bool {
/*
何かしらの処理
*/
  return true
}
```

# 配列

## 初期化

[参考](https://qiita.com/Kyomesuke3/items/2e296dbf6bca29e15c54)

```swift
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]
```

## ランダム

```swift
["あいうえお", "かきくけこ"].randomElement() // "あいうえお" or "かきくけこ" がランダムで
```

## 配列から毎回違う要素をランダムで選択
```swift
let arr = ["あいうえお", "かきくけこ", "さしすせそ"]
var selectedStrInArr:String=""
selectedStrInArr = arr.filter({$0 != selectedStrInArr}).shuffled().first
```

## 結合

```swift
  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が変わる
```

## ユニークにする

```swift
extension Array where Element: Equatable {
  var unique: [Element] {
    return reduce([Element](), { (result, sequence) in
      result.contains(sequence) ? result : result + [sequence]
    })
  }
}
```

## 大文字だけか判定

```swift
str.components(separatedBy: CharacterSet.uppercaseLetters.inverted).joined() == str
//もしくは
str.allSatisfy(\.isUppercase)
```

# String
## String -> Int

```swift
let _ = Int("12") // -> 12
let _ = Int("012") // -> 12
let _ = Int("+12") // -> 12
let _ = Int("-12") // -> -12
let _ = Int("12.3") // -> nil
```

## 指定の文字列の数を数える

**20200105更新**
もっとシンプルな書き方があった

```swift
//改行コードの数を数えてみる
let a = "あいうえお\nかきく\nけこ"
let c = a..components(separatedBy: "\n").count - 1 // 2
```


[参考](https://ja.stackoverflow.com/questions/29675/%E6%96%87%E5%AD%97%E5%88%97%E3%81%AB%E6%8C%87%E5%AE%9A%E3%81%AE%E3%83%AF%E3%83%BC%E3%83%89%E3%81%8C%E4%BD%95%E5%80%8B%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8B%E3%81%8B%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%97%E3%81%9F%E3%81%84)
これもう古い

```swift
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

```

## 半角数値(またはそれ以外)の数を数える
全角数値があると数が狂う

```swift
let a = "あいうえお10かきく225けこ" //全部で15文字
//半角数値の数
a.components(separatedBy: CharacterSet.decimalDigits).count - 1 //5
//半角数値以外の数
a.components(separatedBy: CharacterSet.decimalDigits.inverted).count - 1 //10
```

## 指定した回数だけ同じ文字列を繰り返す

```swift
let times = 3
let orgStr = "ABCDEFG"
(0...times).reduce("") { (str, _) -> String in  orgStr + "・" + str }
// -> ABCDEFG・ABCDEFG・ABCDEFG・
```

## 指定した長さと文字のランダム文字列を生成

```swift
//charactersの中の文字からランダムに選ばれる
func randomString(length: Int, characters:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") -> String {
    return Array(1...length).reduce(into: "", { (str, _) in
      return str.append(characters.randomElement()!)
    })
  }
```

# CGRectの操作

```swift

// 元の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

## コードで制約を貼る

ビューの真ん中にボタンを置く例

```constraint.swift
    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にしとく

[![Screenshot from Gyazo](https://gyazo.com/6cd4e73c51abc049ddaaf5c459e34c0d/raw)](https://gyazo.com/6cd4e73c51abc049ddaaf5c459e34c0d)


# Grand Central Dispatch(GCD)
[参考](https://qiita.com/shiz/items/693241f41344a9df6d6f)
[参考](https://blog.ymyzk.com/2015/08/gcd-grand-central-dispatch-semaphore/)

## GCDクラス
```gcd.swift
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
  }
}
```

## 直列で処理

```swift
GCD.inMainQueueOnly()
```
<img src="https://media.giphy.com/media/2isjMAMikf155aUNHR/giphy.gif" width=50%>

## 並列で処理

```swift
GCD.inConcurrentQueue()
```
<img src="https://media.giphy.com/media/1xVbXqM9DlFIJ79yuo/giphy.gif" width=50%>


## 並列処理した後に処理を実行する

```swift
GCD.doMoreAfterConcurretQueuesUsingDispatchGroup()
```
<img src="https://media.giphy.com/media/e7OsV3p6U0SN5iooXu/giphy.gif" width=50%>

## 直列処理した後に値を返す

```swift
  let result = GCD.returnsValueAfterAQueueUsingSemaphore()
  print(result)
```
<img src="https://media.giphy.com/media/BcPbUyYyB4cf1FSnMl/giphy.gif" width=50%>


## 並列処理した後に値を返す

```swift
  let result = GCD.returnsValueAfterConcurrentQueuesUsingSemaphore()
  print(result)
```
<img src="https://media.giphy.com/media/1vZ8VEQV0uOJOGsDwo/giphy.gif" width=50%>


# ふたつのTableViewのスクロールを同期させる

`RxSwift`ありきの実装

```swift
    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でやる

```sh
grep -i -r --include=*.storyboard <検索文字列> <検索パス>
```

## 複数のXcodeがあるときのCLI対応

### 現在のXcodeのパスを確認

```.sh
xcode-select -print-path
```

### Xcodeの切り替え

```.sh
sudo xcode-select -switch /Applications/<指定のXcode>/Contents/Developer
```