Hello, Habr. The translation was prepared as part of the online course "iOS Developer. Basic".
We invite everyone to a free two-day intensive “Creating a simple application without a single line of code”. On the first day we will find out:
1. What is Xcode?
2. How "screens are drawn"
3. Add buttons and input fields to the screens. Let's create an authorization screen.
4. Let's create a second screen of our application and add a transition to it from the authorization window.
You can register here.
Add your own flavor to Swift
Let's be honest. The Swift and Apple frameworks do not have all the functionality needed to create the best software for Apple devices. Fortunately, Swift supports extensions so that we can add the missing pieces we need for a better experience.
If you are new to Swift, please refer to the documentation to learn more about Extensions before proceeding.
, . , .
- , , .
, , -.
Xcode Playground, , GitHub.
10 , Livefront.
1. UIView —
UIView
import PlaygroundSupport
import UIKit
// Extension #1 - A helper method to add a view to another with top, left, bottom, and right constraints.
extension UIView {
/// Add a subview, constrained to the specified top, left, bottom and right margins.
///
/// - Parameters:
/// - view: The subview to add.
/// - top: Optional top margin constant.
/// - left: Optional left (leading) margin constant.
/// - bottom: Optional bottom margin constant.
/// - right: Optional right (trailing) margin constant.
///
func addConstrained(subview: UIView,
top: CGFloat? = 0,
left: CGFloat? = 0,
bottom: CGFloat? = 0,
right: CGFloat? = 0) {
subview.translatesAutoresizingMaskIntoConstraints = false
addSubview(subview)
if let top = top {
subview.topAnchor.constraint(equalTo: topAnchor, constant: top).isActive = true
}
if let left = left {
subview.leadingAnchor.constraint(equalTo: leadingAnchor, constant: left).isActive = true
}
if let bottom = bottom {
subview.bottomAnchor.constraint(equalTo: bottomAnchor, constant: bottom).isActive = true
}
if let right = right {
subview.trailingAnchor.constraint(equalTo: trailingAnchor, constant: right).isActive = true
}
}
}
// Implementation
class ViewController: UIViewController {
let newView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
newView.backgroundColor = .systemTeal
view.addConstrained(subview: newView, top: 50, left: 100, right: -100)
}
}
let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
, translatesAutoresizingMaskIntoConstraints
false
, , - . , , . , , . , .
2. - (UTC Date)
Date UTC
import Foundation
// Extension #2 - Create a date object from a date string with the UTC timezone.
//Inspired by: https://developer.apple.com/library/archive/qa/qa1480/_index.html
extension Date {
/// Returns a date from the provided string.
///
/// - Parameter utcString: The string used to create the date.
///
/// - Returns: A date from the provided string.
///
static func utcDate(from utcString: String) -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(abbreviation: "UTC")!
return formatter.date(from: utcString)
}
}
// Implementation
let utcDateString = "2021-04-03T14:00:00.000Z"
let utcDate = Date.utcDate(from: utcDateString) //Playgrounds will show this in the machine's timezone.
print(utcDate!)
API REST UTC. Date
. , , dateFormat
, .
3. String () — URL-
URL-
import Foundation
// Extension #3 - Retrieves valid URLs from a given string.
//Credit - Thanks to Paul Hudson for the core functionality on this extension.
//Source - https://www.hackingwithswift.com/example-code/strings/how-to-detect-a-url-in-a-string-using-nsdatadetector
extension String {
/// Searches through a string to find valid URLs.
/// - Returns: An array of found URLs.
func getURLs() -> [URL] {
var foundUrls = [URL]()
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return foundUrls
}
let matches = detector.matches(
in: self,
options: [],
range: NSRange(location: 0, length: self.utf16.count)
)
for match in matches {
guard let range = Range(match.range, in: self),
let retrievedURL = URL(string: String(self[range])) else { continue }
foundUrls.append(retrievedURL)
}
return foundUrls
}
}
// Implementation
let unfilteredString = "To get the best search results, go to https://www.google.com, www.duckduckgo.com, or www.bing.com"
let urls = unfilteredString.getURLs()
- , URL . -, , URL- JSON-.
4. UIStackView —
subviews UIStackView
import UIKit
// Extension #4 - Removes all views from a UIStackView.
extension UIStackView {
/// Removes all arranged subviews and their constraints from the view.
func removeAllArrangedSubviews() {
arrangedSubviews.forEach {
self.removeArrangedSubview($0)
NSLayoutConstraint.deactivate($0.constraints)
$0.removeFromSuperview()
}
}
}
// Implementation
let view1 = UIView()
let view2 = UIView()
let view3 = UIView()
let stackView = UIStackView()
//Add subviews to stackView
stackView.addArrangedSubview(view1)
stackView.addArrangedSubview(view2)
stackView.addArrangedSubview(view3)
//Confirm stackView contains 3 views
stackView.arrangedSubviews.count //3
//Remove views from stackView
stackView.removeAllArrangedSubviews()
//Confirm stackView doesn't contain any subviews now
stackView.arrangedSubviews.count //0
, UIStackView
, , , . - .
5. Bundle —
import Foundation
// Extension #5 - retrieve the app version # and build #.
//Inspired by https://stackoverflow.com/questions/25965239/how-do-i-get-the-app-version-and-build-number-using-swift
extension Bundle {
/// Retrieve the app version # from Bundle
var releaseVersionNumber: String? {
return infoDictionary?["CFBundleShortVersionString"] as? String
}
/// Retrieve the build version # from Bundle
var buildVersionNumber: String? {
return infoDictionary?["CFBundleVersion"] as? String
}
}
// Implementation
let releaseVersionNumber = Bundle.main.releaseVersionNumber
let buildVersionNumber = Bundle.main.buildVersionNumber
, Bundle
. , , . .
6. —
Integer
import Foundation
// Extension #6 - Get the prior year as an integer
extension Calendar {
/// Returns the prior year as an integer.
///
/// - Returns: Returns last year's year as an integer.
func priorYear() -> Int {
guard let priorYear = date(byAdding: .year, value: -1, to: Date()) else {
return component(.year, from: Date()) - 1
}
return component(.year, from: priorYear)
}
}
//Implementation
let priorYearAsNumber = Calendar.current.priorYear()
. Integer.
7. UIStackView — Init
Init ()
import PlaygroundSupport
import UIKit
// Extension #7 - Make UIStackView creation a lot easier.
extension UIStackView {
/// `UIStackView` convenience initializer for creating a stack view with arranged subviews, an
/// axis and spacing.
///
/// - Parameters:
/// - alignment: The alignment of the arranged subviews perpendicular to the stack view’s
/// axis.
/// - arrangedSubviews: The subviews to arrange in the `UIStackView`.
/// - axis: The axis that the subviews should be arranged around.
/// - distribution: The distribution of the arranged views along the stack view’s axis.
/// - spacing: The spacing to place between each arranged subview. Defaults to 0.
///
convenience init(alignment: UIStackView.Alignment = .fill,
arrangedSubviews: [UIView],
axis: NSLayoutConstraint.Axis,
distribution: UIStackView.Distribution = .fill,
spacing: CGFloat = 0) {
arrangedSubviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
self.init(arrangedSubviews: arrangedSubviews)
self.alignment = alignment
self.axis = axis
self.distribution = distribution
self.spacing = spacing
}
}
// Implementation
let view1 = UIView()
view1.backgroundColor = .systemPink
let view2 = UIView()
view2.backgroundColor = .systemOrange
let view3 = UIView()
view3.backgroundColor = .systemTeal
let stackView = UIStackView(alignment: .leading,
arrangedSubviews: [view1, view2, view3],
axis: .vertical,
distribution: .fill,
spacing: 20)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
view.backgroundColor = .systemBlue
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view1.heightAnchor.constraint(equalToConstant: 50),
view1.widthAnchor.constraint(equalToConstant: 150),
view2.heightAnchor.constraint(equalToConstant: 50),
view2.widthAnchor.constraint(equalToConstant: 150),
view3.heightAnchor.constraint(equalToConstant: 50),
view3.widthAnchor.constraint(equalToConstant: 150),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
PlaygroundPage.current.liveView = view
, UIStackView
, . . translatesAutoresizingMaskIntoConstraints
false
.
8. UIColor — Hex
Hex () UIColor
import UIKit
// Extension #8 - generates a string with the hex color value.
//Inspired by: https://stackoverflow.com/a/26341062
extension UIColor {
// MARK: - Helper Functions
/// Returns the hex string for this `UIColor`. For example: `#FFFFFF` or `#222222AB` if the alpha value is included.
///
/// - Parameter includeAlpha: A boolean indicating if the alpha value should be included in the returned hex string.
///
/// - Returns: The hex string for this `UIColor`. For example: `#FFFFFF` or
/// `#222222AB` if the alpha value is included.
///
func hexString(includeAlpha: Bool = false) -> String {
let components = cgColor.components
let red: CGFloat = components?[0] ?? 0.0
let green: CGFloat = components?[1] ?? 0.0
let blue: CGFloat = components?[2] ?? 0.0
let alpha: CGFloat = components?[3] ?? 0.0
let hexString = String.init(
format: "#%02lX%02lX%02lX%02lX",
lroundf(Float(red * 255)),
lroundf(Float(green * 255)),
lroundf(Float(blue * 255)),
lroundf(Float(alpha * 255))
)
return includeAlpha ? hexString : String(hexString.dropLast(2))
}
}
// Implementation
let whiteColor = UIColor(displayP3Red: 1, green: 1, blue: 1, alpha: 1)
let whiteHexString = whiteColor.hexString() //#FFFFFF
let blackColor = UIColor(displayP3Red: 0, green: 0, blue: 0, alpha: 1)
let blackHexString = blackColor.hexString() //#000000
UIColor
String
. , . , RGB.
9. UIViewController —
,
import UIKit
// Extension #9
extension UIViewController {
/// Gets a flag indicating whether or not the UI is in dark mode.
public var isDarkMode: Bool {
if #available(iOS 12.0, *) {
return traitCollection.userInterfaceStyle == .dark
}
return false
}
}
UIColors
, .label
, .systemBlue
.., , , , . , , .
10. UICollectionView — IndexPath
indexPath collectionView
import PlaygroundSupport
import UIKit
// Extension #10 - get the last valid indexPath in a UICollectionView.
extension UICollectionView {
/// Validates whether an `IndexPath` is a valid index path for an item in a collection view.
///
/// - Parameter indexPath: The index path to validate.
/// - Returns: `true` if the index path represents an item in the collection view or false
/// otherwise.
///
func isValid(_ indexPath: IndexPath) -> Bool {
guard indexPath.section < numberOfSections,
indexPath.item < numberOfItems(inSection: indexPath.section)
else {
return false
}
return true
}
/// Provides the last valid `indexPath` in the collection view.
/// - Parameter section: The section used to provide the last `indexPath`.
/// - Returns: the last valid `indexPath` in the collection view or nil if not a valid `indexPath`.
func lastIndexPath(in section: Int) -> IndexPath? {
let lastIndexPath = IndexPath(row: numberOfItems(inSection: section) - 1, section: section)
guard isValid(lastIndexPath) else { return nil }
return lastIndexPath
}
}
// Implementation
class CollectionViewController: UICollectionViewController {
let items = Array(1...100)
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .systemBlue
return cell
}
}
let collectionViewController = CollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
let lastIndexPath = collectionViewController.collectionView.lastIndexPath(in: 0)
lastIndexPath?.section //0
lastIndexPath?.row //99
PlaygroundPage.current.liveView = collectionViewController
, UICollectionView
, indexPath
. , , , UIKit. collectionView
; .
, . Swift . " Swift" , -.
() .
: