iOS Advent Calendar 2020
It's the 17th day. : tada:
I've summarized the moments when I've been developing iOS apps for about two and a half years. (Although there is learning that is not limited to iOS or swift.) I aimed for an article that I could read without trying hard, but somehow it would be useful for studying. The one closer to the title is the rudimentary one. I would be grateful if you could take a look if you have time.
Before
var hoge: Bool {
if fugaBool {
return true
} else {
return false
}
}
After
var hoge: Bool {
return fugaBool
}
It's natural when I think about it now. You probably didn't realize that XX itself is a boolean value because you had to return true when XX was true.
Before
if fugaBool {
hogeLabel.text = "fuga is true"
} else {
hogeLabel.text = "fuga is fake"
}
After
hogeLabel.text = fugaBool ? "fuga is true" : "fuga is fake"
Ternary operator.
var + =
can be a calculated property.Before
func presentHogeAlert() {
var message = ""
if validationA {
message.append("A was ok")
} else if validationB {
message.append("B was ok")
} else {
message.append("It was awesome")
}
let alert = UIAlertController(title: "error", message: message, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default))
present(alert, animated: true)
}
After
func presentHogeAlert() {
var message: String {
if validationA {
return "A was ok"
} else if validationB {
return "B was ok"
} else {
return "It was awesome"
}
}
let alert = UIAlertController(title: "error", message: message, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default))
present(alert, animated: true)
}
The calculation type makes the return value easier to understand, isn't it?
Before
func hoge() {
if yagi {
//Process A
if let mogu = mogu {
//Process B
} else {
if fuga {
//Process C
} else {
//Process D
}
}
} else {
//Process E
}
}
After
func hoge() {
guard yagi else {
//Process E
return
}
//Process A
guard let mogu = mogu else {
if fuga {
//Process C
} else {
//Process D
}
return
}
//Process B
}
I wanted to prepare a better example. There are many nests and it's hard to read just because it's long.
Before
guard !fugaBool else {
// do something
return
}
After
if fugaBool {
// do something
return
}
Some projects dare to prefer double negation in order to explicitly express return with guard.
I still know if there is only one truth value, but this is!fugaBool || mogu > 1 && yagi.isEmpty
If the conditions are like this, you will go crazy.
.init
Before
view.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
After
view.frame = .init(
origin: .init(
x: 0,
y: 0
),
size: .init(
width: 0,
height: 0
)
)
I like it because I can omit long model names. Note that if you use it too much, it will be difficult to read.
trailing closure
can be omitted from the argumentBefore
func hogeChanged(fugaBool: Bool, hogeCompletionHandler: @escaping ((String) -> Void)) {
guard fugaBool else { return }
let hoge = getChangedHogeValue
hogeCompletionHandler(hoge)
}
hogeChanged(fugaBool: true, hogeCompletionHandler: @escaping { [weak self] hoge in
guard let self = self else { return }
self.hogeLabel.text = hoge
})
After
func hogeChanged(fugaBool: Bool, hogeCompletionHandler: @escaping ((String) -> Void)) {
guard fugaBool else { return }
let hoge = getChangedHogeValue
hogeCompletionHandler(hoge)
}
hogeChanged(fugaBool: true) { [weak self] hoge in
guard let self = self else { return }
self.hogeLabel.text = hoge
})
Closure argument names are usually long, which is helpful. It can be difficult to read.
For example, set the tab type of TabBarController to enum. Before
override func viewDidLoad() {
super.viewDidLoad()
var viewControllers: [UIViewController] = []
let hogeVC = HogeViewController())
hogeVC.tabBarItem = UITabBarItem(title: "hoge", image: nil, tag: 1)
viewControllers.append(hogeVC)
let fugaVC = FugaViewController()
fugaVC.tabBarItem = UITabBarItem(title: "fuga", image: nil, tag: 2)
viewControllers.append(fugaVC)
setViewControllers(viewControllers, animated: false)
}
After
enum TabType: Int, CaseIterable {
case hoge = 0
case fuga = 1
private var baseViewController: UIViewController {
switch self {
case .hoge:
return HogeViewController()
case .fuge:
return FugaViewController()
}
}
private var title: String {
switch self {
case .hoge:
return "hoge"
case .fuga:
return "fuga"
}
}
var tabItem: UITabBarItem {
.init(title: title, image: nil, tag: self.rawValue)
}
var viewController: UIViewController {
let viewController = baseViewController
viewController.tabBarItem = tabItem
return viewController
}
}
//When to use
override func viewDidLoad() {
super.viewDidLoad()
setViewControllers(TabType.allCases.map(\.viewController), animated: false)
}
If you add a case, you will need to add other setting values, so you can prevent leakage.
Before When there are several flags and you want to express a combination of true and false.
let hogeFlag: Bool
let fugaFlag: Bool
if hogeFlag && fugaFlag {
// true true
} else if !hogeFlag && fugaFlag {
// false true
} else if hogeFlag && !fugaFlag {
// true flase
} else {
// false false
}
After
switch (hogeFlag, fugaFlag) {
case (true, true): break
case (false, true): break
case (true, false): break
case (false, false): break
}
The visibility of the switch is abnormal.
var + for
is usually rewritten by higher-order functionsBefore
var titles: [String] = []
for i in 0...10 {
let title = "Apples\(i)There are one."
titles.append(title)
}
titles.joined(separator: "\n")
return titles
After
(0...10).map {
"Apples\($0)There are one."
}
.joined(separator: "\n")
https://qiita.com/shtnkgm/items/600009917d8e572e6780 This article is detailed.
Before
struct Hoge {
let aaa: String
let iii: String
let uuu: String
}
//There is an array of Hoge.
let hogeList = [Hoge]()
After
//Extract only specific properties
let aaaList = hogeList.map(\.aaa)
//Mapping to another type
struct Fuga {
let aaa: String
let eee: String
let ooo: String
}
let fugaList = hogeList.map { Fuga(aaa: $0.aaa, eee: "", ooo: "") }
It is absolutely necessary in the process of tapping the API, processing the data and passing it to View.
for + where
.Suppose you have these animals.
protocol Animal {
var name: String { get }
}
protocol Runable: Animal {
func run()
}
extension Runable {
func run() {
print("run!!!")
}
}
class Cat: Animal, Runable {
var name: String = "Cat"
}
class Dog: Animal, Runable {
var name: String = "dog"
}
class Penguin: Animal {
var name: String = "Penguins"
}
let animals: [Animal] = [Cat(), Dog(), Penguin()]
Before
for animal in animals {
if animal is Runable {
print("This animal can run!")
}
if let runableAnimal = animal as? Runable {
runableAnimal.run()
}
}
After
for animal in animals where animal is Runable {
print("This animal can run!")
if let runableAnimal = animal as? Runable {
runableAnimal.run()
}
}
If you master where, you will feel that you can do something.
for + where
can be rewritten to filter + forEach
.From the previous example Before
for animal in animals where animal is Runable {
print("\(animal.name)Can run!")
if let runableAnimal = animal as? Runable {
runableAnimal.run()
}
}
After
animals.filter { $0 is Runable }.forEach {
print("\($0.name)Can run!")
if let runableAnimal = $0 as? Runable {
runableAnimal.run()
}
}
Higher-order function. In this example, it may be faster to cast and apply to compactMap.
animals.compactMap { $0 as? Runable }.forEach {
print("\($0.name)Can run!")
$0.run()
}
This is so-called passing by reference.
Before
var fuga = "This is fuga."
print(fuga) //This is fuga.
func addHogeString() {
fuga.append("\n")
fuga.append("add hoge")
}
addHogeString()
print(fuga) //This is fuga.\n Add hoge.
After
func addHogeString(strings: inout String) {
strings.append("\n")
strings.append("add hoge")
}
var fuga = "This is fuga."
print(fuga) //This is fuga.
addHogeString(strings: &fuga)
print(fuga) //This is fuga.\n Add hoge.
This expression of passing by reference using & is also used in Combine's assign.
filter + first or last
can be written withfirst (where :) or last (where :)
.Before
animals.filter { !($0 is Runable) }.first // Penguin
After
animals.first { !($0 is Runable) } // Punguin
Somehow, using first is better for performance.
Suppose you want to prepare such a View. (The color is changed so that the frame is easy to see.)
Before
class ViewController: UIViewController {
private lazy var hogeLabel: UILabel = {
let label = UILabel()
label.text = "hogehoge"
return label
}()
private lazy var fugaLabel: UILabel = {
let label = UILabel()
label.text = "fugafuga"
return label
}()
private lazy var moguLabel: UILabel = {
let label = UILabel()
label.text = "mogumogu"
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
}
private func setupLayout() {
[hogeLabel, fugaLabel, moguLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .gray
view.addSubview($0)
}
NSLayoutConstraint.activate([
hogeLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 80),
hogeLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
hogeLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
fugaLabel.topAnchor.constraint(equalTo: hogeLabel.bottomAnchor, constant: 16),
fugaLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
fugaLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
moguLabel.topAnchor.constraint(equalTo: fugaLabel.bottomAnchor, constant: 16),
moguLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
moguLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
])
}
}
After
class ViewController: UIViewController {
...
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.spacing = 16
stackView.axis = .vertical
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
}
private func setupLayout() {
view.backgroundColor = .lightGray
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
[hogeLabel, fugaLabel, moguLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .gray
stackView.addArrangedSubview($0)
}
NSLayoutConstraint.activate([
hogeLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 80),
hogeLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
hogeLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
])
}
}
StackView a little. Please note that some people say that if you use it too much, the performance will drop.
StackView + ScrollView
, you can easily build a scrollable layout that is easy to expand.UIScrollView is used to create a screen that you want to scroll like this, but ScrollView determines whether scrolling occurs depending on the size of the View inside, so you have to prepare a View inside. If you make that View a StackView, the size will change just by addingArreangedSubView to the stackView, so scrolling will occur easily without having to calculate the height of the View.
After that, just add View to stackView and scrolling will occur when the size of StackView becomes larger than the screen size.
No scrolling | With scrolling |
---|---|
hittest
when you want to get the touch event of the View behind the visible ViewSuppose you have a screen with these ScrollViews overlapping.
The image is the screen of the app that records the position of the rock currently under development.https://github.com/kawano108/RockMap
Before If it is left as it is, the vertical ScrollView on the front absorbs the touch event, and the horizontal ScrollView on the back does not respond.
After So hittest. You can ignore the touch event by overriding it to return nil.
final class HeaderIgnorableScrollView: UIScrollView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if view == self,
point.x < UIScreen.main.bounds.width && point.y < UIScreen.main.bounds.height * (9/16) {
return nil
}
return view
}
}
You can also touch the horizontal Scroll View on the back side.
I was impressed.
PropertyWrapper
to annotate the same process and omit it.For example, set and get processing to Keychain. Before Suppose you get and set like this.
final class KeychainManager {
///Key
struct Key {
static let accessToken = "accessToken"
}
///Keychain instance
static let keychain = Keychain(service: Bundle.main.bundleIdentifier ?? "")
static var accessToken: String {
get { get(key: Key.accessToken) ?? "" }
set { set(key: Key.accessToken, value: newValue) }
}
private static func get<T>(key: String) -> T? {
do {
return try KeychainManager.keychain.get(key) as? T
} catch {
print(error.localizedDescription)
assertionFailure("Failed to get the data from Keychain.")
return nil
}
}
private static func set(key: String, value: String) {
do {
return try KeychainManager.keychain.set(value, key: key)
} catch {
print(error.localizedDescription)
assertionFailure("Failed to save data to Keychain. ..")
}
}
}
//When using
KeychainManager.accessToken = accessToken // set
let accessToken = KeychainManager.accessToken
After If you use propertyWrapper, you don't have to write set or get.
import KeychainAccess
@propertyWrapper
class KeychainStorage<T: LosslessStringConvertible> {
private let key: String
var keychain: Keychain {
guard let identifier = Bundle.main.object(forInfoDictionaryKey: UUID().uuidString) as? String else {
return Keychain(service: "")
}
return Keychain(service: identifier)
}
init(key: String) {
self.key = key
}
var wrappedValue: T? {
get {
do {
guard let result = try keychain.get(key) else { return nil }
return T(result)
} catch {
print(error.localizedDescription)
return nil
}
}
set {
do {
guard let new = newValue else {
try keychain.remove(key)
return
}
try keychain.set(String(new), key: key)
} catch {
print(error.localizedDescription)
}
}
}
}
final class KeychainDataHolder {
private enum Key: String {
case uid = "_accessToken"
}
static let shared: KeychainDataHolder = KeychainDataHolder()
private init() {}
@KeychainStorage(key: Key.accessToken.rawValue)
var accessToken: String?
}
//When using
KeychainDataHolder.shared.accessToken = accessToken // set
let accessToken = KeychainDataHolder.shared.accessToken //get
If you increase the key and properties, you don't have to write set and get.
Thank you for reading! I think there are more, but when I try to put it out, it doesn't come out. Tomorrow is @takashico!
Recommended Posts