Изображение в качестве цвета

Наряду с привычными способами, цвета UIColor могут создаваться на основе изображений UIImage. Такое бывает полезно, например, если нужно закрасить фон паттерном.

import UIKit

var view: UIView
var image: UIImage
var context: CGContext

// Draw pattern image using Core Graphics functions
UIGraphicsBeginImageContextWithOptions(CGSize(width: 20, height: 20), false, 0)

context = UIGraphicsGetCurrentContext()!

CGContextSetStrokeColorWithColor(context, UIColor(white: 0.5, alpha: 1).CGColor)
CGContextStrokeRectWithWidth(context, CGRect(x: 0, y: 0, width: 20, height: 20), 0.5)

image = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

view = UIView(frame: CGRect(x: 0, y: 0, width: 120, height: 120))

// Set view background as image color
view.backgroundColor = UIColor(patternImage: image)
sample

Закругление углов UIView

Изменение радиуса углов (cornerRadius), толщины (borderWidth) и цвета (borderColor) рамки объектов UIView доступно через свойство layer.

import UIKit

var view = UIView(frame: CGRect(x: 0, y: 0, width: 120, height: 120))

view.layer.borderWidth = 1
view.layer.cornerRadius = 10
view.layer.borderColor = UIColor.redColor().CGColor
sample-1
view.layer.cornerRadius = view.bounds.width / 2
view.layer.borderColor = UIColor.blueColor().CGColor
sample-2
view.layer.borderWidth = 0
view.layer.cornerRadius = view.bounds.width
view.backgroundColor = UIColor.blackColor()
sample-3

Протоколы

Одной из особенностей языка Swift являются протоколы. По сути — это интерфейсы, определяющие список свойств и методов, которые должны быть реализованы принимающими классами, структурам или перечислениями. Для выстраивания иерархии, протоколы поддерживают наследование, в том числе множественное. Также их разрешается использовать как типы; отличительной чертой служит возможность создавать типы-композиции через protocol<Protocol1, Protocol2, ..., ProtocolN>.

import UIKit

protocol Selectable {
    var selected: Bool { get set }
    
    func select()
    func deselect()
}

protocol Colorable {
    func paint()
}

// Shape protocol inherits Selectable and Colorable protocols
protocol Shape: Selectable, Colorable {
    var type: String { get }
}

protocol Rounded {
    var radius: Double { get set }
}

// Oval inherits UIView class and adopts protocols Rounded and Shape
class Oval: UIView, Rounded, Shape {
    let type = "oval"
    var radius = 10.0
    var selected = false

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func select() {
        selected = true
    }

    func deselect() {
        selected = false
    }

    func paint() {
        backgroundColor = UIColor.redColor()
    }
}

class Squircle: UIView, Rounded, Shape {
    let type = "squircle"
    var radius = 3.0
    var selected = false

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func select() {
        selected = true
    }

    func deselect() {
        selected = false
    }

    func paint() {
        backgroundColor = UIColor.greenColor()
    }
}

// Define composition protocol type
typealias RoundedShape = protocol<Rounded, Shape>

// Array initialization as [protocol<Rounded, Shape>]() doesn't work
var roundedShapes = [RoundedShape]()

roundedShapes.append(Oval())
roundedShapes.append(Squircle())

С выходом Swift 2.0 протоколы обзавелись расширениями. Они позволяют добавлять реализацию по умолчанию методов и свойств (новых или уже существующих). Если необходимо, область действия расширений ограничивается только нужными типами, делается это конструкцией where.

import UIKit

// Make Selectable protocol adoptable only by classes (not structs, enums)
protocol Selectable: class {
    var selected: Bool { get set }

    func select()
    func deselect()
}

/*
Add default implementations of select() and deselect() protocol methods
and new toggleSelection() method only for UIView subclasses that adopt Selectable protocol
*/
extension Selectable where Self: UIView {
    func select() {
        selected = true
    }

    func deselect() {
        selected = false
    }

    func toggleSelection() {
        selected = !selected
    }
}

class Shape: UIView, Selectable {
    var selected = false
}

Встроенные протоколы тоже можно расширять.

import Foundation

extension BooleanType {
    var trueReverse: Bool {
        if self {
            return !Bool(self)
        } else {
            return Bool(self)
        }
    }
}

print(true.trueReverse)
// Output: false

print(false.trueReverse)
// Output: false

Дополнительные материалы:

Загрузка данных — NSURLSession

До выхода iOS 7.0 единственным интерфейсом для загрузки данных по сети служил класс NSURLConnection, затем был добавлен NSURLSession, который со временем стал полноценным преемником, обладающим расширенными и более гибкими возможностями. Начиная с iOS 9.0 NSURLConnection более не рекомендуется использовать.

NSURLSession поддерживает основные протоколы передачи информации, в том числе защищенные. Работа осуществляется как с обычными файлами, так и объектами NSData.

Для простых задач у класса есть метод NSURLSession.sharedSession(), возвращающий преднастроенный синглтон-экземпляр. Настройки по умолчанию достаточно оптимальны, единственным серьезным минусом является невозможность задать делегата, отслеживающего все события процесса, а не только завершение загрузки посредством функции-обработчика.

import Foundation
import XCPlayground

// Playground configuration
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

let url = NSURL(string: "http://valery.bashkatov.org/favicon.png")!
let session = NSURLSession.sharedSession()

let task = session.dataTaskWithURL(url,
                                   completionHandler: 
                                   // This function is called when the task is completed
                                   {
                                       (data: NSData?,
                                        response: NSURLResponse?,
                                        error: NSError?) -> Void in
                                       if error == nil {
                                           print("Downloading completed")
                                       } else {
                                           print(error)
                                       }
                                   })

// Run task
task.resume()

Если же требования выше, NSURLSession конфигурируется через объект NSURLSessionConfiguration. Сначала необходимо создать одну из базовых конфигураций вызовом соответствующего метода класса (возвращается новый экземпляр, а не синглтон):

Затем возможно изменить неустраивающие значения свойств.

Во время инициализации NSURLSession разрешается также указать делегата, получающего контроль над процессом загрузки через реализацию протоколов:

import UIKit

class ViewController: UIViewController, NSURLSessionDataDelegate {
    var data: NSMutableData!
    var dataURL: NSURL!
    var session: NSURLSession!
    var sessionConfiguration: NSURLSessionConfiguration!
    var sessionTask: NSURLSessionDataTask!

    override func viewDidLoad() {
        super.viewDidLoad()

        data = NSMutableData()
        dataURL = NSURL(string: "http://valery.bashkatov.org/favicon.png")!
        
        // Used ephemeral session for disable disk-based cache
        sessionConfiguration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
        sessionConfiguration.timeoutIntervalForRequest = 10
        session = NSURLSession(configuration: sessionConfiguration,
                               delegate: self,
                               delegateQueue: nil)

        sessionTask = session.dataTaskWithURL(dataURL)
        sessionTask.resume()
    }

    // Method defined in NSURLSessionDataDelegate protocol
    func URLSession(session: NSURLSession,
                    dataTask: NSURLSessionDataTask,
                    didReceiveResponse response: NSURLResponse,
                    completionHandler: (NSURLSessionResponseDisposition) -> Void) {
        print("Received server response")
        print("Expected file size: \(response.expectedContentLength) bytes")

        completionHandler(NSURLSessionResponseDisposition.Allow)
        print("Downloading started")
    }

    // Method defined in NSURLSessionDataDelegate protocol
    func URLSession(session: NSURLSession,
                    dataTask: NSURLSessionDataTask,
                    didReceiveData data: NSData) {
        print("  Received: \(data.length) bytes")

        // Collect full data, chunk by chunk
        data.enumerateByteRangesUsingBlock
        {
            [unowned self] (bytes: UnsafePointer<Void>,
                            byteRange: NSRange,
                            stop: UnsafeMutablePointer<ObjCBool>) in
            self.data.appendBytes(bytes, length: byteRange.length)
        }
    }

    // Method defined in NSURLSessionTaskDelegate protocol
    func URLSession(session: NSURLSession,
                    task: NSURLSessionTask,
                    didCompleteWithError error: NSError?) {
        guard error == nil else {
            print(error)
            return
        }
        print("Downloading completed")
    }
}

Посимвольная работа со строками

Как и другие современные языки, Swift работает со строками в Юникоде, поэтому соответствие один символ — один байт не всегда верно.

import Foundation

var string = "Hello"

print(string.characters.count)
// Output: 5

print(string.utf8.count)
// Output: 5

string = "Héllo"

print(string.characters.count)
// Output: 5

print(string.utf8.count)
// Output: 6

Чтобы получить список именно символов, а не отдельных скаляров, есть свойство characters (типа String.CharacterView). Кроме прочих возможностей, оно поддерживает посимвольные циклы for-in и индексное обращение через [].

Особенностью является специальный тип индексов — String.CharacterView.Index (он же String.Index). Причем сами значения индексов задаются не абсолютно, а вычисляются относительно позиционных маркеров в начале и конце строки путем сдвига от них на нужное количество символов.

import Foundation

var string = "Héllo"

for character in string.characters {
    print(character)
}
/*
Output: 
H
é
l
l
o
*/

print(string.characters[0])
// Output: Cannot subscript a value of type 'String.CharacterView' with an index of type 'Int'

print(string.characters[string.startIndex])
// Output: H

string.replaceRange(string.startIndex.successor()...string.endIndex.advancedBy(-4), with: "a")
print(string)
// Output: Hallo