<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Boris Bugor on Medium]]></title>
        <description><![CDATA[Stories by Boris Bugor on Medium]]></description>
        <link>https://medium.com/@bugorbn?source=rss-53da94055385------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*MdcwPvCjYoN-9-seM_l67A.jpeg</url>
            <title>Stories by Boris Bugor on Medium</title>
            <link>https://medium.com/@bugorbn?source=rss-53da94055385------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 10:32:03 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@bugorbn/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Structure Oriented Programming VS Protocol Oriented Programming in Swift]]></title>
            <link>https://itnext.io/structure-oriented-programming-vs-protocol-oriented-programming-in-swift-023970d80c75?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/023970d80c75</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sun, 05 Nov 2023 21:01:50 GMT</pubDate>
            <atom:updated>2024-12-04T20:56:24.675Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*M_xmAOenIGpoltMp" /><figcaption>Photo by <a href="https://unsplash.com/@cadop?utm_source=medium&amp;utm_medium=referral">Mathew Schwartz</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In this article we will look at the main aspects of structure-oriented programming: how to write code according to the SOLID principles without using protocols and without losing abstraction.</p><p>The Structure Oriented Programming is a paradigm based on the fact that any protocol can be replaced by a structure.</p><p>The advantages of this approach is performance.<br>Since the structure-oriented approach is based on structures that use static dispatch, the dispatch speed will be significantly different from protocols with dynamic dispatch in the protocol-oriented approach.</p><h3>1. Replacing a protocol with an equivalent structure</h3><p>Let’s start by trying to replace the protocol with an equivalent structure.</p><p>In a protocol-oriented approach, the protocol itself allows abstraction from the implementation.</p><p>In the structure-oriented approach, generics and closures help to provide abstraction.</p><p>Consider, as an example, the composition of a protocol and a class, in which the protocol provides the square of a number for any object that will conform to this protocol:</p><pre>// Abstraction<br>protocol RegularProtocol {<br>    var sqrValue: Int { get }<br>}<br><br>// Object<br>class RegularClass {<br>    <br>    let value: Int<br>    <br>    init(value: Int) {<br>        self.value = value<br>    }<br>}<br><br>// Realization<br>extension RegularClass: RegularProtocol {<br>    var sqrValue: Int {<br>        value * value<br>    }<br>}<br><br>let onbject = RegularClass(value: 25)<br>print(onbject.sqrValue)</pre><p>Thus, the extension to the protocol adopt RegularProtocol.</p><p>For a structure-oriented approach, the above would look like this:</p><pre>// Abstraction<br>struct RegularStruct&lt;AdoptedObject&gt; {<br>    var sqrValue: (AdoptedObject) -&gt; Int<br>    <br>    init(sqrValue: @escaping (AdoptedObject) -&gt; Int) {<br>        self.sqrValue = sqrValue<br>    }<br>}<br><br>// Object<br>class RegularClass {<br>    <br>    let value: Int<br>    <br>    init(value: Int) {<br>        self.value = value<br>    }<br>}<br><br>// Realization<br>extension RegularStruct where AdoptedObject == RegularClass {<br>    init() {<br>        sqrValue = { object in object.value * object.value }<br>    }<br>}<br><br>let onbject = RegularStruct&lt;RegularClass&gt;()<br>print(onbject.sqrValue(.init(value: 25)))</pre><h3><strong>2. Covering all kinds of cases</strong></h3><p>To understand how to use structure-oriented approach in practice, let’s consider the most likely cases of using the protocol:</p><ol><li>Computed property</li><li>Property with getter and setter</li><li>Static property</li><li>Regular method</li><li>Static function</li><li>Function with associated value</li><li>Parent protocol function when inheriting protocols</li></ol><p>For protocol-oriented approach all these cases are presented below:</p><pre>protocol ProtocolExample: ParentProtocolExpample {<br>    // 1<br>    var getVariable: Int { get }<br>    <br>    // 2<br>    var getSetVariable: Int { get set }<br>    <br>    // 3<br>    static var staticVariable: Int { get }<br>    <br>    // 4<br>    func regularFunction(value: Int) -&gt; Bool<br>    <br>    // 5<br>    static func staticFunction(value: Int) -&gt; Bool<br>    <br>    // 6<br>    associatedtype Value<br>    func assosiatedFunction(value: Value) -&gt; Bool<br>}<br><br>protocol ParentProtocolExample {<br>    // 7<br>    func inheritedFunction() -&gt; String<br>}</pre><p>An equivalent composition for a structure-oriented approach looks like this:</p><pre>struct StructExample&lt;AdoptedObject, Value&gt; {<br>    // 1<br>    var getVariable: (_ object: AdoptedObject) -&gt; Int<br>    <br>    // 2<br>    var setVariable: (_ object: AdoptedObject, Int) -&gt; Void<br>    <br>    // 3<br>    var staticVariable: () -&gt; Int<br>    <br>    // 4<br>    var regularFunction: (_ object: AdoptedObject, _ value: Int) -&gt; Bool<br>    <br>    // 5<br>    var staticFunction: (_ value: Int) -&gt; Bool<br>    <br>    // 6<br>    var assosiatedFunction: (_ object: AdoptedObject, _ value: Value) -&gt; Bool<br>    <br>    // 7<br>    var parentStruct: ParentStructExample&lt;AdoptedObject&gt;<br>    var inheritedFunction: () -&gt; String<br>}<br><br>struct ParentStructExample&lt;AdoptedObject&gt; {<br>    var inheritedFunction: () -&gt; String<br>}</pre><h3>3. Performance benefits</h3><p>The effect of the implementation was tested with XCTest and the Optimization Level flags — Fastest, Smallest [-Os] and showed that dispatching is 3–4 times faster for the structure-oriented approach.</p><p>Protocol-oriented approach:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*8bx0fIEiK-nzevlEar13Jg.png" /></figure><p>Structure-oriented approach</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/476/1*ktoN31IRh8Eu460LxmAodg.png" /></figure><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=023970d80c75" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/structure-oriented-programming-vs-protocol-oriented-programming-in-swift-023970d80c75">Structure Oriented Programming VS Protocol Oriented Programming in Swift</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimizing work in iOS runtime]]></title>
            <link>https://itnext.io/optimizing-work-in-ios-runtime-b2afc10ec775?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/b2afc10ec775</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sun, 15 Oct 2023 16:25:23 GMT</pubDate>
            <atom:updated>2024-12-04T20:56:51.064Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*j24oRmZOgTu1i5wr" /><figcaption>Photo by <a href="https://unsplash.com/@billjelen?utm_source=medium&amp;utm_medium=referral">Bill Jelen</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>There are a sufficient number of ways to increase the speed of a project at runtime. Let’s look at the most popular of them:</p><h3>Limiting dynamic dispatch in classes</h3><p>Under the hood, any new class will use dynamic (table) dispatch. This is a price for the ability to be inherited, but if the class is not inherited, you can save a significant amount of resources. The final prefix will turn your class into a statically dispatched object.</p><p>Before optimization:</p><pre>// parent class with dynamic table dispatch<br>class A {}<br>// child class with dynamic table dispatch<br>class B: A {}</pre><p>After optimization:</p><pre>// child class with static dispatch<br>final class B: A {}</pre><p>Examples of speed boost:</p><ul><li>Parent class A:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/470/1*mPwMXahF7fTNDeIkf2HwkQ.png" /></figure><ul><li>Subclass B:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*1lz4ms4oDBUlrrldKeiAAA.png" /></figure><ul><li>Final class B:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/462/1*-KMXM06hV5loGcNB7tP2QA.png" /></figure><h3>Limitation of dynamic dispatch at methods</h3><p>A similar approach is used for class methods, but there are several options available:</p><ul><li>If your method is not used outside the class, feel free to use private.</li><li>If you are going to protect your method from the possibility of override, use final.</li></ul><p>Both prefixes will turn your method into a statically dispatched method, but with different scopes.</p><p>Before optimization:</p><pre>// method with dynamic table dispatch<br>func method() {}</pre><p>After optimization:</p><pre>// private method with static dispatch<br>private func method() {}<br><br>// method with static dispatch cannot be override<br>final func method() {}</pre><p>Examples of speed boost:</p><ul><li>Nonfinal menthod:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/468/1*Sm7TJ4hBxrXovtATm-FzTQ.png" /></figure><ul><li>Final or private method</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/458/1*5IDwJct4zUDd-7LyOD988w.png" /></figure><h3>Inline optimization</h3><p>One of the most curious and rarely used types of optimization. Inline optimization allows you to copy code calculations directly to the place where a given function is called without calling a method marked with inline, which reduces the number of method calls and works faster than static dispatch.</p><p>Among the pitfalls, in order to force the compiler to call a method with inline optimization — it is necessary to mark the method not only @inlineable but also @inline(__always)</p><p><strong>It is important</strong> not to mark methods that spend a large amount of resources on copying with these prefixes; there is a risk of filling the cache of all processor levels with calculations of these methods and, as a result, getting slower instead of speeding up.</p><p>Before optimization:</p><pre>// test method with multiple calling<br>func testMethod() {}</pre><p>After optimization:</p><pre>// inlinable test method with multiple calling<br>@inlinable<br>@inline(__always)<br>func testMethod() {}</pre><p>Examples of speed boost:</p><ul><li>regular calling:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/474/1*7HUgthT1le_BeQppYQtb9Q.png" /></figure><ul><li>inlined calling:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/476/1*BqkIF5D58hHYNdCZm-QFXg.png" /></figure><h3>Optimization in protocols</h3><p>Under the hood, protocols in Swift can be implemented for reference types and value types. Due to the fact that the memory management mechanism for reference types and value types are different, you can significantly speed up the compiler by limiting protocols adoption to reference types only.</p><p>Before optimization:</p><pre>// can be adopted to reference types and value types,<br>// but value types adoption is unnecessarily<br>protocol Implementable {}</pre><p>After optimization:</p><pre>// can be adopted to reference types only<br>protocol Implementable: AnyObject {}</pre><p>Examples of speed boost:</p><ul><li>creation array elements with protocol for reference and value type</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/478/1*HIiLZWseEFbWWLaPQxOnNA.png" /></figure><ul><li>creation array elements with protocol for reference type only</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/482/1*2YkR81bzSZMs930i90IsGQ.png" /></figure><h3><strong>Arrays</strong></h3><p>Array was originally intended not only as a collection type for Swift, allowing elements of the same type to be stored sequentially, but also as a collection type providing compatibility with NSArray.</p><p>In case the array is intended to store non-ObjC data types, it is recommended to use ContagiousArray for more efficient operation of the array.</p><p>Before optimization:</p><pre><br>// creation array with NSArray interop<br>let array: Array&lt;Int&gt; = [1, 2, 3]</pre><p>After optimization:</p><pre>// creation array without NSArray interop<br>let fastArray: ContiguousArray&lt;Int&gt; = [1, 2, 3]</pre><p>Examples of speed boost:</p><ul><li>creation Array:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/478/1*eyQKq0-WmidHnMJAFv7H3w.png" /></figure><ul><li>creation ContagiousArray:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/482/1*d8VIQrHXiJ7Qai_gWmfeNA.png" /></figure><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b2afc10ec775" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/optimizing-work-in-ios-runtime-b2afc10ec775">Optimizing work in iOS runtime</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OperationQueue + Asynchronous Code]]></title>
            <link>https://medium.com/better-programming/operationqueue-asynchronous-code-d383848e8ba4?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/d383848e8ba4</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-development]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sat, 23 Sep 2023 18:42:57 GMT</pubDate>
            <atom:updated>2024-12-04T20:57:20.313Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NaPuRZd4xMG10f6h" /><figcaption>Photo by <a href="https://unsplash.com/@austris_a?utm_source=medium&amp;utm_medium=referral">Austris Augusts</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In Swift using OperationQueue for asynchronous code may seem like pure hell because, under the hood, Operations are considered complete if the compilation of their synchronous code is completed.</p><p>In other words, compiling the example described below will output a broken execution order since, by the time the asynchronous code is executed, the Operation itself will have already been completed.</p><pre>let operationQueue = OperationQueue()<br>operationQueue.maxConcurrentOperationCount = 1<br><br>operationQueue.addOperation {<br>    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {<br>        print(&quot;First async operation complete&quot;)<br>    }<br>    print(&quot;First sync operation complete&quot;)<br>}<br><br>operationQueue.addOperation {<br>    DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {<br>        print(&quot;Second async operation complete&quot;)<br>    }<br>    print(&quot;Second sync operation complete&quot;)<br>}</pre><p>This will be printed:</p><pre>First sync operation complete<br>Second sync operation complete<br>First async operation complete<br>Second async operation complete</pre><p>However, there is a way to circumvent these restrictions. To understand how to solve the problem, you need to understand how Operation works under the hood.</p><p>The Operation itself has four flags by which you can track the life cycle of the operation:</p><ul><li>isReady — indicates whether the Operation can be performed at this time.</li><li>isExecuting —indicates whether an Operation is currently in progress.</li><li>isFinished —indicates whether the Operation is currently completed.</li><li>isCancelled —indicates whether the Operation was cancelled.</li></ul><p>In theory, the Operation enters the isFinished state before the Operation itself is executed asynchronously, so we need to develop a technique by which we will be able to manipulate the life cycle of the Operation.</p><p>This possibility can be solved by subclassing the Operation and also by redefining the start / cancel methods, as well as all the flags on which the operation&#39;s life cycle is built. Here’s the code:</p><pre>public class AsyncOperation: Operation {<br>    // MARK: Open<br><br>    override open var isAsynchronous: Bool {<br>        true<br>    }<br><br>    override open var isReady: Bool {<br>        super.isReady &amp;&amp; self.state == .ready<br>    }<br><br>    override open var isExecuting: Bool {<br>        self.state == .executing<br>    }<br><br>    override open var isFinished: Bool {<br>        self.state == .finished<br>    }<br><br>    override open func start() {<br>        if isCancelled {<br>            state = .finished<br>            return<br>        }<br>        main()<br>        state = .executing<br>    }<br><br>    override open func cancel() {<br>        super.cancel()<br>        state = .finished<br>    }<br><br>    // MARK: Public<br><br>    public enum State: String {<br>        case ready<br>        case executing<br>        case finished<br><br>        // MARK: Fileprivate<br><br>        fileprivate var keyPath: String {<br>            &quot;is&quot; + rawValue.capitalized<br>        }<br>    }<br><br>    public var state = State.ready {<br>        willSet {<br>            willChangeValue(forKey: newValue.keyPath)<br>            willChangeValue(forKey: state.keyPath)<br>        }<br>        didSet {<br>            didChangeValue(forKey: oldValue.keyPath)<br>            didChangeValue(forKey: state.keyPath)<br>        }<br>    }<br>}</pre><p>The subclass we received from the Operation is basic and allows us to forcefully complete it manually.</p><p>To work with completion blocks, you should create another subclass. However, this will not be a subclass of the Operation, but of AsyncOperation.</p><pre>public typealias VoidClosure = () -&gt; Void<br>public typealias Closure&lt;T&gt; = (T) -&gt; Void<br><br>public class CompletionOperation: AsyncOperation {<br>    // MARK: Lifecycle<br><br>    public init(completeBlock: Closure&lt;VoidClosure?&gt;?) {<br>        self.completeBlock = completeBlock<br>    }<br><br>    // MARK: Public<br><br>    override public func main() {<br>        DispatchQueue.main.async { [weak self] in<br>            self?.completeBlock? {<br>                DispatchQueue.main.async {<br>                    self?.state = .finished<br>                }<br>            }<br>        }<br>    }<br><br>    // MARK: Private<br><br>    private let completeBlock: Closure&lt;VoidClosure?&gt;?<br>}</pre><p>This subclass will allow us to pass a closure to the Operation, after which the Operation will be completed.</p><p>Let’s try this type of operation in practice:</p><pre>let operationQueue = OperationQueue()<br>operationQueue.maxConcurrentOperationCount = 1<br><br>operationQueue.addOperation(<br>    CompletionOperation { completion in<br>        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {<br>            print(&quot;First async operation complete&quot;)<br>            completion?()<br>        }<br>        print(&quot;First sync operation complete&quot;)<br>    }<br>)<br><br>operationQueue.addOperation(<br>    CompletionOperation { completion in<br>        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {<br>            print(&quot;Second async operation complete&quot;)<br>            completion?()<br>        }<br>        print(&quot;Second sync operation complete&quot;)<br>    }<br>)</pre><p>As a result, we were able to achieve synchronous execution of Operations:</p><pre>First sync operation complete<br>First async operation complete<br>Second sync operation complete<br>Second async operation complete</pre><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d383848e8ba4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/operationqueue-asynchronous-code-d383848e8ba4">OperationQueue + Asynchronous Code</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unsafe Memory Access in Swift]]></title>
            <link>https://bugorbn.medium.com/unsafe-memory-access-in-swift-a66ff9638a02?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/a66ff9638a02</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[ios-development]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Fri, 08 Sep 2023 14:12:18 GMT</pubDate>
            <atom:updated>2024-12-04T20:57:35.517Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nOmJqmDffpCvMl6V" /><figcaption>Photo by <a href="https://unsplash.com/@rioryan?utm_source=medium&amp;utm_medium=referral">Ryan</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Memory management in Swift is based on automatic reference counting (ARC), which means that an object exists in memory as long as there is at least one strong reference to it. After that, ARC initiates the object deallocation mechanism, depending on the number of existing weak and unowned object references, the deallocation mechanism will be different.</p><p>However, in addition to ARC, Swift also supports manual memory management. In this article, I will tell you what are the ways to work with memory that provide create / read / update / delete (CRUD) operations and much more.</p><h3>1.Pointers</h3><p>Manual memory management can be implemented based on pointers. Pointer types vary depending on the need for unsafe memory access.</p><p>The data type is:</p><ul><li>Pointers to a piece of memory without an explicit type. Returns the number of bytes. Usually contains Raw in the name;</li><li>Pointers with an explicit type specified during initialization as a generic parameter. <strong>Does not</strong> contain Raw in the name;</li></ul><p>Variability is distinguished by:</p><ul><li>Pointers to a memory area with the possibility of changing it. The name contains Mutable;</li><li>Pointers to a piece of memory without the possibility of changing it. The name <strong>does not</strong> contain Mutable;</li></ul><p>The number of elements is distinguished by:</p><ul><li>Pointers that operate on an array of elements. The name contains Buffer;</li><li>Pointers that operate on a single element. The name <strong>does not</strong> contain Buffer;</li></ul><p>In total, all possible combinations of pointers look like this:</p><ul><li>UnsafePointer&lt;T&gt;;</li><li>UnsafeMutablePointer&lt;T&gt;;</li><li>UnsafeBufferPointer&lt;T&gt;;</li><li>UnsafeMutableBufferPointer&lt;T&gt;;</li><li>UnsafeRawPointer;</li><li>UnsafeMutableRawPointer;</li><li>UnsafeRawBufferPointer;</li><li>UnsafeMutableRawBufferPointer;</li></ul><h3>2. Creating objects</h3><p>Let’s consider the creation of objects on the example of UnsafePointer.</p><pre>var x: Int = 10<br>let unsafePointer = UnsafePointer&lt;Int&gt;(&amp;x)</pre><p>Because UnsafePointer is an immutable pointer, it can only be initialized by passing it an already initialized object directly.</p><p>You can get information about the memory area stored at a given pointer as follows:</p><pre>unsafePointer.pointee // printed 10</pre><p>Consider the creation of objects on the example of UnsafeMutablePointer. Unlike UnsafePointer, this pointer can be initialized before information is written to the memory area.</p><pre>let size = MemoryLayout&lt;Int&gt;.size<br><br>let unsafeMutablePointer = UnsafeMutablePointer&lt;Int&gt;.allocate(capacity: size)<br>unsafeMutablePointer.pointee = 5</pre><p>Now, through the pointee property, you can read and write the allocated memory area. Since we are working with the Int data type, the capacity area was chosen taking into account the required size of the MemoryLayout.</p><p>You can deallocate a memory area and a pointer to it as follows:</p><pre>unsafeMutablePointer.deallocate()<br>unsafeMutablePointer.deinitialize(count: 1)</pre><p>Consider the creation of elements on the example of UnsafeMutableRawPointer. Since this pointer is mutable and without explicit typing, it is enough to allocate a memory area for an object, and then write data to it. In this case, all operations for this pointer occur byte by byte, without a specific data type.</p><pre>let unsafeMutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment) // 6000006E44F0<br>unsafeMutableRawPointer.storeBytes(of: 32, as: Int.self) // 6000006E44F0<br>unsafeMutableRawPointer.load(as: Int.self) // printed 32</pre><p>The load method allows you to read a memory area with a given data type. The storeBytes method allows you to write to the allocated memory area. At the same time, because of the lack of binding to a specific data type, you can easily put and read data with a completely different type into the allocated area:</p><pre>unsafeMutableRawPointer.initializeMemory(as: String.self, to: &quot;123&quot;) // 6000006E44F0<br>unsafeMutableRawPointer.load(as: String.self) // printed “123”<br>unsafeMutableRawPointer.deallocate()</pre><p>The address 6000006E44F0 was the number 32 with the data type Int, but we rewrote it to the string &quot;123&quot;, hello Python.</p><h3>3. Copying</h3><p>In addition to standard CRUD operations, pointers also support copying memory from one address to another.</p><pre>let size = MemoryLayout&lt;Int&gt;.size<br>let alignment = MemoryLayout&lt;Int&gt;.alignment<br><br>let unsafeMutableRawPointer1 = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)<br>unsafeMutableRawPointer1.storeBytes(of: 32, as: Int.self) // 32<br><br>let unsafeMutableRawPointer2 = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)<br>unsafeMutableRawPointer2.storeBytes(of: 40, as: Int.self) // 40<br><br>unsafeMutableRawPointer2.copyMemory(from: unsafeMutableRawPointer1, byteCount: size)<br>unsafeMutableRawPointer2.load(as: Int.self) // 32</pre><p>Two pointers were created to different memory locations containing the numbers 32 and 40. Thanks to copyMemory, we were able to copy information from one memory location to another. At the same time, the use of the copyMemory method allows one-time copying, preserving the further independence of memory sections with different addresses.</p><h3>4. Binding</h3><p>It is also possible to bind two different pointers:</p><pre>let unsafeMutableRawPointer3 = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)<br>unsafeMutableRawPointer3.storeBytes(of: 32, as: Int.self)<br><br>let unsafeMutableRawPointer4 = unsafeMutableRawPointer3.bindMemory(to: Int.self, capacity: size)<br>unsafeMutableRawPointer4.pointee // 32<br><br>unsafeMutableRawPointer3.storeBytes(of: 40, as: Int.self)<br><br>unsafeMutableRawPointer4.pointee // 40</pre><p>Thanks to bindMemory, both pointers point to the same memory location and will catch all changes, regardless of which pointer they are written to.</p><h3>5. Collections</h3><p>To allocate an area of memory for an array using UnsafeMutableBufferPointer , the area for each element of the array will be allocated first:</p><pre>let array: [Int] = [5, 6, 7, 8, 9]<br>let elementPointer = UnsafeMutablePointer&lt;Int&gt;.allocate(capacity: array.count)<br>let arrayPointer = UnsafeMutableBufferPointer(start: elementPointer, count: array.count)</pre><p>Let’s fill in the previously allocated area. Offset between memory locations of different elements will be provided by the advanced(by: Int) method.</p><pre>for (index, value) in array.enumerated() {<br>    elementPointer.advanced(by: index).pointee = value<br>}</pre><p>In addition to writing, the advanced(by: Int) method also helps when reading a specific array element by ordinal index:</p><pre>elementPointer.advanced(by: 4).pointee // 9</pre><p>UnsafeMutableBufferPointer supports subscript[index] to read and write array elements by ordinal index:</p><pre>arrayPointer[4] = 5<br>elementPointer.advanced(by: 4).pointee // 5<br>arrayPointer.deallocate()</pre><p>On this, the article comes to an end. I will talk about other types of manual memory management, such as Unmanaged objects, in the next article.</p><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a66ff9638a02" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scrollable lists using Protocol-Oriented Programming and UICollectionViewCompositionalLayout]]></title>
            <link>https://bugorbn.medium.com/scrollable-lists-using-protocol-oriented-programming-and-uicollectionviewcompositionallayout-51793eb5aa8b?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/51793eb5aa8b</guid>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sat, 26 Aug 2023 19:28:52 GMT</pubDate>
            <atom:updated>2024-12-04T20:57:49.623Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ReAxbYhDFjzwaX9A" /><figcaption>Photo by <a href="https://unsplash.com/@mbaumi?utm_source=medium&amp;utm_medium=referral">Mika Baumeister</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I <a href="https://medium.com/@bugorbn">continue</a> the series of articles devoted to the use of a protocol-oriented approach when scaling projects with a large code base.</p><p>If you have not read the <a href="https://bugorbn.medium.com/scrollable-lists-using-protocol-oriented-programming-and-uicollectionviewflowlayout-6e5225661cc4">previous</a> article, then I strongly recommend that you familiarize yourself with the approaches and conclusions that were made in it. Briefly, a case was considered with the creation of a universal class that would allow creating a constructor for using scrolling lists based on UICollectionViewFlowLayout.</p><p>The motivation for this approach is very simple, we want to reduce the amount of boilerplate code by creating universal tools that will reduce the amount of routine and at the same time not lose flexibility.</p><p>In this article, we will continue to consider a similar task using <a href="https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout?language=objc">UICollectionViewCompositionalLayout</a>, which is supported from iOS 13+, and see what nuances this framework brings.</p><p>As in the previous problem, we will solve this problem in 4 stages.</p><ol><li>Writing an abstraction of the data type of scrolling elements;</li><li>Writing a base class for scrollable elements;</li><li>Writing an Implementation for Lists;</li><li>Use cases</li></ol><h3>1. Abstract scrolling elements</h3><p>The creation of abstraction — is undoubtedly the most important stage for design. To lay the foundation for a system open to scaling, it is necessary to abstract from the qualitative and quantitative characteristics of scrolling elements. It is only important to comply with the requirements for the same type of layout.</p><p>Let us introduce such a notion — as a section. A section is one or more elements with the same layout. We use the section as an abstraction over the scrollable elements:</p><p>Using a section as an abstraction over scrollable elements:</p><pre>protocol BaseSection {<br>    var numberOfElements: Int { get }<br><br>    func registrate(collectionView: UICollectionView)<br>    func cell(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionViewCell<br>    func header(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView<br>    func footer(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView<br>    func section() -&gt; NSCollectionLayoutSection<br>    func select(row: Int)<br>}</pre><p>We will transfer the responsibility for configuring the layout to the section. The presence of supplementary views, such as a header or footer, will also be determined there.</p><h3>2. Scrolling list</h3><p>The base class will be used as the scrollable list. The task of the base class is to take the abstract data of the BaseSection and render it. In our case, UICollectionView and UICollectionViewCompositionalFlowLayout will be used as a visualization tool:</p><pre>class SectionView: UIView {<br>    override init(frame: CGRect) {<br>        super.init(frame: frame)<br>        <br>        commonInit()<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>        <br>        commonInit()<br>    }<br>    <br>    private func commonInit() {<br>        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]<br>        collectionView.frame = bounds<br>        addSubview(collectionView)<br>    }<br>    <br>    private(set) lazy var flowLayout: UICollectionViewCompositionalLayout = {<br>        let layout = UICollectionViewCompositionalLayout { [weak self] index, env in<br>            self?.sections[index].section()<br>        }<br>        return layout<br>    }()<br>    <br>    private(set) lazy var collectionView: UICollectionView = {<br>        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)<br>        collectionView.backgroundColor = .clear<br>        collectionView.delegate = self<br>        collectionView.dataSource = self<br>        return collectionView<br>    }()<br>    <br>    private var sections: [BaseSection] = []<br>    <br>    public func set(sections: [BaseSection], append: Bool) {<br>        sections.forEach {<br>            $0.registrate(collectionView: collectionView)<br>        }<br>        <br>        if append {<br>            self.sections.append(contentsOf: sections)<br>        } else {<br>            self.sections = sections<br>        }<br>        <br>        collectionView.reloadData()<br>    }<br>    <br>    public func set(contentInset: UIEdgeInsets) {<br>        collectionView.contentInset = contentInset<br>    }<br>}<br><br>extension SectionView: UICollectionViewDataSource {<br>    func numberOfSections(in collectionView: UICollectionView) -&gt; Int {<br>        sections.count<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {<br>        sections[section].numberOfElements<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&gt; UICollectionViewCell {<br>        sections[indexPath.section].cell(for: collectionView, indexPath: indexPath)<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -&gt; UICollectionReusableView {<br>        kind == UICollectionView.elementKindSectionHeader<br>            ? sections[indexPath.section].header(for: collectionView, indexPath: indexPath)<br>            : sections[indexPath.section].footer(for: collectionView, indexPath: indexPath)<br>    }<br>}<br><br>extension SectionView: UICollectionViewDelegate {<br>    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {<br>        sections[indexPath.section].select(row: indexPath.row)<br>    }<br>}</pre><p>UICollectionViewCompositionalLayout, compared to using UICollectionViewFlowLayout, allows you to transfer cell / header / footer layout configuration from delegate methods to the layout body.</p><h3>3. Implementing Scrollable Elements</h3><p>Based on the fact that the section, which includes the ability to show the footer and header, was taken as an abstraction, it is also necessary to take this into account in the implementation class.</p><p>In this case, the requirements for any cell will look like this:</p><pre>protocol SectionCell: UICollectionViewCell {<br>    associatedtype CellData: SectionCellData<br><br>    func setup(with data: CellData) -&gt; Self<br>    static func groupSize() -&gt; NSCollectionLayoutGroup<br>}<br><br>protocol SectionCellData {<br>    var onSelect: VoidClosure? { get }<br>}<br><br>typealias VoidClosure = () -&gt; Void</pre><p>We move the configuration of the cell size to the area of responsibility of the cell, we also take into account the possibility of receiving an action by tapping on any cell.</p><p>The header and footer requirements will look like this:</p><pre>protocol SectionHeader: UICollectionReusableView {<br>    associatedtype HeaderData<br>    <br>    func setup(with data: HeaderData?) -&gt; Self<br>    static func headerItem() -&gt; NSCollectionLayoutBoundarySupplementaryItem?<br>}<br><br>protocol SectionFooter: UICollectionReusableView {<br>    associatedtype FooterData<br>    <br>    func setup(with data: FooterData?) -&gt; Self<br>    static func footerItem() -&gt; NSCollectionLayoutBoundarySupplementaryItem?<br>}</pre><p>Based on the requirements for scrolling elements, we can design the implementation of the section:</p><pre>class Section&lt;Cell: SectionCell, Header: SectionHeader, Footer: SectionFooter&gt;: BaseSection {<br>    init(items: [Cell.CellData], headerData: Header.HeaderData? = nil, footerData: Footer.FooterData? = nil) {<br>        self.items = items<br>        self.headerData = headerData<br>        self.footerData = footerData<br>    }<br>    <br>    private(set) var items: [Cell.CellData]<br>    private let headerData: Header.HeaderData?<br>    private let footerData: Footer.FooterData?<br>    <br>    var numberOfElements: Int {<br>        items.count<br>    }<br>    <br>    func registrate(collectionView: UICollectionView) {<br>        collectionView.register(Cell.self)<br>        collectionView.registerHeader(Header.self)<br>        collectionView.registerFooter(Footer.self)<br>    }<br>    <br>    func cell(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionViewCell {<br>        collectionView<br>            .dequeue(Cell.self, indexPath: indexPath)?<br>            .setup(with: items[indexPath.row])<br>        ?? UICollectionViewCell()<br>    }<br>    <br>    func header(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView {<br>        collectionView<br>            .dequeueHeader(Header.self, indexPath: indexPath)?<br>            .setup(with: headerData)<br>        ?? UICollectionReusableView()<br>    }<br>    <br>    func footer(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView {<br>        collectionView<br>            .dequeueFooter(Footer.self, indexPath: indexPath)?<br>            .setup(with: footerData)<br>        ?? UICollectionReusableView()<br>    }<br>    <br>    func section() -&gt; NSCollectionLayoutSection {<br>        let section = NSCollectionLayoutSection(group: Cell.groupSize())<br>        <br>        if let headerItem = Header.headerItem() {<br>            section.boundarySupplementaryItems.append(headerItem)<br>        }<br>        <br>        if let footerItem = Footer.footerItem() {<br>            section.boundarySupplementaryItems.append(footerItem)<br>        }<br><br>        return section<br>    }<br><br>    func select(row: Int) {<br>        items[row].onSelect?()<br>    }<br>}</pre><p>Generics that implement the requirements for them act as cell / header / footer types.</p><p>In general, the implementation is complete, but I would like to add a few helpers that further reduce the amount of boilerplate code. In particular, in practice it will not always be useful to have such a generic section, for the simple reason that the footer or header is not always used. Therefore, let’s add a section heir that takes into account similar cases:</p><pre>class SectionWithoutHeaderFooter&lt;Cell: SectionCell&gt;: Section&lt;Cell, EmptySectionHeader, EmptySectionFooter&gt; {}<br><br>class EmptySectionHeader: UICollectionReusableView, SectionHeader {<br>    func setup(with data: String?) -&gt; Self {<br>        self<br>    }<br>    <br>    static func headerItem() -&gt; NSCollectionLayoutBoundarySupplementaryItem? {<br>        nil<br>    }<br>}<br><br>class EmptySectionHeader: UICollectionReusableView, SectionHeader {<br>    func setup(with data: String?) -&gt; Self {<br>        self<br>    }<br>    <br>    static func headerItem() -&gt; NSCollectionLayoutBoundarySupplementaryItem? {<br>        nil<br>    }<br>}</pre><p>On this, the design can be considered complete, I propose to move on to the use cases themselves.</p><h3>4. Use cases</h3><p>Let’s create a section of cells with a fixed size and display it on the screen:</p><pre>class ColorCollectionCell: UICollectionViewCell, SectionCell {<br><br>    func setup(with data: ColorCollectionCellData) -&gt; Self {<br>        contentView.backgroundColor = data.color<br>        <br>        return self<br>    }<br><br>    static func groupSize() -&gt; NSCollectionLayoutGroup {<br>        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.5))<br>        let item = NSCollectionLayoutItem(layoutSize: itemSize)<br>        let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitem: item, count: 2)<br>        group.interItemSpacing = .fixed(16)<br>        return group<br>    }<br>}<br><br>class ColorCollectionCellData: SectionCellData {<br>    let onSelect: VoidClosure?<br>    let color: UIColor<br>    <br>    init(color: UIColor, onSelect: VoidClosure? = nil) {<br>        self.onSelect = onSelect<br>        self.color = color<br>    }<br>}</pre><p>Let’s create an implementation of the header and footer:</p><pre>class DefaultSectionHeader: UICollectionReusableView, SectionHeader {<br>    let textLabel: UILabel = {<br>        let label = UILabel()<br>        label.font = .systemFont(ofSize: 32, weight: .bold)<br>        return label<br>    }()<br>    <br>    override init(frame: CGRect) {<br>        super.init(frame: frame)<br>        <br>        commonInit()<br>    }<br><br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>        <br>        commonInit()<br>    }<br>    <br>    private func commonInit() {<br>        addSubview(textLabel)<br>        textLabel.numberOfLines = .zero<br>        textLabel.translatesAutoresizingMaskIntoConstraints = false<br>        <br>        NSLayoutConstraint.activate([<br>            textLabel.topAnchor.constraint(equalTo: topAnchor),<br>            textLabel.bottomAnchor.constraint(equalTo: bottomAnchor),<br>            textLabel.leftAnchor.constraint(equalTo: leftAnchor),<br>            textLabel.rightAnchor.constraint(equalTo: rightAnchor)<br>        ])<br>    }<br>    <br>    func setup(with data: String?) -&gt; Self {<br>        textLabel.text = data<br>        <br>        return self<br>    }<br><br>    static func headerItem() -&gt; NSCollectionLayoutBoundarySupplementaryItem? {<br>        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(20))<br>        let header = NSCollectionLayoutBoundarySupplementaryItem(<br>            layoutSize: headerSize,<br>            elementKind: UICollectionView.elementKindSectionHeader,<br>            alignment: .top,<br>            absoluteOffset: .zero<br>        )<br>        header.pinToVisibleBounds = true<br>        return header<br>    }<br>}<br><br>class DefaultSectionFooter: UICollectionReusableView, SectionFooter {<br>    let textLabel: UILabel = {<br>        let label = UILabel()<br>        label.font = .systemFont(ofSize: 12, weight: .light)<br>        return label<br>    }()<br>    <br>    override init(frame: CGRect) {<br>        super.init(frame: frame)<br>        <br>        commonInit()<br>    }<br><br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>        <br>        commonInit()<br>    }<br>    <br>    private func commonInit() {<br>        addSubview(textLabel)<br>        textLabel.numberOfLines = .zero<br>        textLabel.translatesAutoresizingMaskIntoConstraints = false<br>        <br>        NSLayoutConstraint.activate([<br>            textLabel.topAnchor.constraint(equalTo: topAnchor),<br>            textLabel.bottomAnchor.constraint(equalTo: bottomAnchor),<br>            textLabel.leftAnchor.constraint(equalTo: leftAnchor),<br>            textLabel.rightAnchor.constraint(equalTo: rightAnchor)<br>        ])<br>    }<br>    <br>    func setup(with data: String?) -&gt; Self {<br>        textLabel.text = data<br>        <br>        return self<br>    }<br><br>    static func footerItem() -&gt; NSCollectionLayoutBoundarySupplementaryItem? {<br>        let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(20))<br>        let footer = NSCollectionLayoutBoundarySupplementaryItem(<br>            layoutSize: footerSize,<br>            elementKind: UICollectionView.elementKindSectionFooter,<br>            alignment: .bottom,<br>            absoluteOffset: .zero<br>        )<br><br>        return footer<br>    }<br>}</pre><p>Let’s add a new section to the scrolling list:</p><pre>class ViewController: UIViewController {<br>    <br>    let sectionView = SectionView()<br>    <br>    override func loadView() {<br>        view = sectionView<br>    }<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        sectionView.backgroundColor = .white<br><br>        sectionView.set(<br>            sections: [<br>                Section&lt;ColorCollectionCell, DefaultSectionHeader, DefaultSectionFooter&gt;(<br>                    items: [<br>                        .init(color: .blue) {<br>                            print(#function)<br>                        },<br>                        .init(color: .red) {<br>                            print(#function)<br>                        },<br>                        .init(color: .yellow) {<br>                            print(#function)<br>                        },<br>                        .init(color: .green) {<br>                            print(#function)<br>                        },<br>                        .init(color: .blue) {<br>                            print(#function)<br>                        }<br>                    ],<br>                    headerData: &quot;COLOR SECTION&quot;,<br>                    footerData: &quot;footer text for color section&quot;<br>                )<br>            ],<br>            append: false<br>        )<br>    }<br>}</pre><p>In total, in just a few lines of code, we implemented a section of 5 multi-colored cells with a size proportional to the screen, a header and a footer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7luCbW040TGhcFtIJ6-4kQ.png" /></figure><p>Let’s try to use a similar approach for dynamically sized cells.</p><pre>class DynamicCollectionCell: UICollectionViewCell, SectionCell {<br>    let textLabel = UILabel()<br>    <br>    override init(frame: CGRect) {<br>        super.init(frame: frame)<br>        <br>        commonInit()<br>    }<br><br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>        <br>        commonInit()<br>    }<br>    <br>    private func commonInit() {<br>        contentView.addSubview(textLabel)<br>        textLabel.numberOfLines = .zero<br>        textLabel.translatesAutoresizingMaskIntoConstraints = false<br>        <br>        NSLayoutConstraint.activate([<br>            textLabel.topAnchor.constraint(equalTo: contentView.topAnchor),<br>            textLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),<br>            textLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor),<br>            textLabel.rightAnchor.constraint(equalTo: contentView.rightAnchor)<br>        ])<br>    }<br><br>    func setup(with data: DynamicCollectionCellData) -&gt; Self {<br>        textLabel.text = data.text<br>        <br>        return self<br>    }<br>    <br>    static func groupSize() -&gt; NSCollectionLayoutGroup {<br>        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(20))<br>        let item = NSCollectionLayoutItem(layoutSize: itemSize)<br>        let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item])<br>        return group<br>    }<br>}<br><br>class DynamicCollectionCellData: SectionCellData {<br>    let text: String<br>    var onSelect: VoidClosure?<br>    <br>    init(text: String) {<br>        self.text = text<br>    }<br>}<br><br><br>class ViewController: UIViewController {<br>    <br>    ...<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br><br>        ...<br>        sectionView.set(<br>            sections: [<br>                SectionWithoutHeaderFooter&lt;DynamicCollectionCell&gt;(<br>                    items: [<br>                        .init(text: &quot;Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s&quot;),<br>                        .init(text: &quot;when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.&quot;),<br>                        .init(text: &quot;It was popularised&quot;),<br>                        .init(text: &quot;the 1960s with the release of Letraset sheets containing&quot;),<br>                        .init(text: &quot;Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.&quot;)<br>                    ]<br>                ),<br>                ...<br>            ],<br>            append: false<br>        )<br>    }<br>}</pre><p>As a result, we got rid of writing boilerplate code when creating scrolling lists based on UICollectionViewCompositionalLayout.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N75s78U1NwNT3IA-U1pz5g.png" /></figure><p>The source code can be viewed <a href="https://github.com/BugorBN/BaseCollection-CompositionalLayout/tree/main">here</a>.</p><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=51793eb5aa8b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scrollable lists using Protocol-Oriented Programming and UICollectionViewFlowLayout]]></title>
            <link>https://bugorbn.medium.com/scrollable-lists-using-protocol-oriented-programming-and-uicollectionviewflowlayout-6e5225661cc4?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/6e5225661cc4</guid>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[protocol-oriented]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sat, 19 Aug 2023 16:39:11 GMT</pubDate>
            <atom:updated>2024-12-04T20:58:04.690Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*j65wY0S4PtmtlWnL" /><figcaption>Photo by <a href="https://unsplash.com/@npi?utm_source=medium&amp;utm_medium=referral">Pavel Neznanov</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Each developer in practice faced the need to make up a screen based on a scrolling list of elements. Or maybe not just one list, but several.</p><p>It can be horizontal lists, vertical lists, lists with a complex layout. These can be lists with a predetermined size, or with a dynamic size. And the developer always has a choice — to use UITableView or UICollectionView. (We will not consider UIScrollView, since its use is limited to a finite list of a predetermined number).</p><p>Each of the above options has strengths and weaknesses:</p><ul><li>UITableView, one of the advantages of which is the dynamic calculation of the content height out of the box, is used in vertical lists.</li><li>UICollectionView — always had more use cases chronologically, as they always covered more scroll direction. However, their biggest drawback prior to iOS 13 is the static size, which had to be calculated manually.</li></ul><p>And as every developer knows, after choosing one or the other, the next step is to write a tangible amount of accompanying code in order to make this list work.</p><ul><li>You have to register cells, headers, footers or a runtime crash is inevitable;</li><li>You have to monotonously create cells, configure their number in a section, declare the number of sections;</li><li>You have to fill in the cells with preset data;</li><li>Handle table or collection delegate methods, handle clicks inside a cell;</li><li>It is necessary to duplicate the code and wrap in cells, previously designed for a table, in cells for a collection if both classes are used in the project.</li></ul><p>Each of us spent a huge amount of time on all these things, and this is not normal. This problem is called the problem of writing boilerplate code, and it must be solved by writing universal tools that will reduce the amount of routine and at the same time not lose flexibility.</p><p>In this article, we will solve the problem of writing boilerplate code based on UICollectionView using a protocol-oriented approach for UICollectionViewFlowLayout. This approach is suitable for any project for all currently supported versions of iOS.</p><p>In the next article, I will show how to use this approach for UICollectionViewCompositionalFlowLayout, which is supported from iOS 13+.</p><p>We are faced with the issue of reducing the amount of boilerplate code and we will solve it in 4 stages.</p><ol><li>Writing an abstraction of the data type of scrolling elements;</li><li>Writing a base class for scrollable elements;</li><li>Writing an Implementation for Lists;</li><li>Use cases</li></ol><h3>1. Abstract scrolling elements</h3><p>Designing abstractions is one of the key points in creating a system that is well resistant to changes. It is important to understand that the requirements for abstraction must cover absolutely all possible cases of using objects, otherwise, when scaling the system, additional entities will have to be introduced, the value of the root abstraction will decrease, and the introduction of new features will become more complicated.</p><p>In the case of designing a scrolling list based on UICollectionView, we want to have a unit that would describe the behavior of the elements in the list, without being tied to their qualitative and quantitative characteristics.</p><p>In other words, it doesn’t matter how many objects we have, it doesn’t matter what exactly these objects contain — they should be able to get into the scrolling list if they meet the stated requirements.</p><p>Let us introduce such a notion — as a section.<br>A section is one or more elements with the same layout.<br>We use the section as an abstraction over the scrollable elements:</p><pre>protocol BaseSection {<br>    var numberOfElements: Int { get }<br><br>    func registrate(collectionView: UICollectionView)<br>    func cell(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionViewCell<br>    func cellSize(<br>        for collectionView: UICollectionView,<br>        layout: UICollectionViewFlowLayout,<br>        indexPath: IndexPath<br>    ) -&gt; CGSize<br>    func header(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView<br>    func headerSize(for collectionView: UICollectionView) -&gt; CGSize<br>    func footer(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView<br>    func footerSize(for collectionView: UICollectionView) -&gt; CGSize<br>    func select(row: Int)<br>}</pre><p>Since each section can have a header and footer, we will also provide their configuration in abstraction. In addition, the abstraction is supplemented by methods for calculating the size of cells / header / footer.</p><h3>2. Scrolling list</h3><p>The base class will be used as the scrollable list. The task of the base class is to take the abstract data of the BaseSection and render it. In our case, UICollectionView and UICollectionViewFlowLayout will be used as a visualization tool:</p><pre>class SectionView: UIView {<br>    override init(frame: CGRect) {<br>        super.init(frame: frame)<br>        <br>        commonInit()<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>        <br>        commonInit()<br>    }<br>    <br>    private func commonInit() {<br>        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]<br>        collectionView.frame = bounds<br>        addSubview(collectionView)<br>    }<br>    <br>    private(set) var flowLayout: UICollectionViewFlowLayout = {<br>        let layout = UICollectionViewFlowLayout()<br>        layout.minimumLineSpacing = .zero<br>        layout.minimumInteritemSpacing = .zero<br>        layout.scrollDirection = .vertical<br>        return layout<br>    }()<br>    <br>    private(set) lazy var collectionView: UICollectionView = {<br>        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)<br>        collectionView.backgroundColor = .clear<br>        collectionView.delegate = self<br>        collectionView.dataSource = self<br>        return collectionView<br>    }()<br>    <br>    private var sections: [BaseSection] = []<br>    <br>    public func set(sections: [BaseSection], append: Bool) {<br>        sections.forEach {<br>            $0.registrate(collectionView: collectionView)<br>        }<br>        <br>        if append {<br>            self.sections.append(contentsOf: sections)<br>        } else {<br>            self.sections = sections<br>        }<br>        <br>        collectionView.reloadData()<br>    }<br>    <br>    public func set(itemSpacing: CGFloat) {<br>        flowLayout.minimumInteritemSpacing = itemSpacing<br>    }<br>    <br>    public func set(lineSpacing: CGFloat) {<br>        flowLayout.minimumLineSpacing = lineSpacing<br>    }<br>    <br>    public func set(contentInset: UIEdgeInsets) {<br>        collectionView.contentInset = contentInset<br>    }<br>}<br><br>extension SectionView: UICollectionViewDataSource {<br>    func numberOfSections(in collectionView: UICollectionView) -&gt; Int {<br>        sections.count<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {<br>        sections[section].numberOfElements<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&gt; UICollectionViewCell {<br>        sections[indexPath.section].cell(for: collectionView, indexPath: indexPath)<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -&gt; UICollectionReusableView {<br>        kind == UICollectionView.elementKindSectionHeader<br>            ? sections[indexPath.section].header(for: collectionView, indexPath: indexPath)<br>            : sections[indexPath.section].footer(for: collectionView, indexPath: indexPath)<br>    }<br>}<br><br>extension SectionView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {<br>    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {<br>        sections[indexPath.section].select(row: indexPath.row)<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&gt; CGSize {<br>        sections[indexPath.section].cellSize(<br>            for: collectionView,<br>            layout: flowLayout,<br>            indexPath: indexPath<br>        )<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -&gt; CGSize {<br>        sections[section].headerSize(for: collectionView)<br>    }<br>    <br>    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -&gt; CGSize {<br>        sections[section].footerSize(for: collectionView)<br>    }<br>}</pre><p>In addition to the usual rendering of objects, the base class solves the problem of writing boilerplate code. Instead of writing UICollectionView delegate method handling for each of the screens where the list will be used, we have only one single base class in which the delegate method processing will be performed once.</p><h3>3. Implementing Scrollable Elements</h3><p>Based on the fact that the section was taken as an abstraction, including the ability to show the footer and header, it is also necessary to take this into account in the implementation class.</p><p>In this case, the requirements for any cell will look like this:</p><pre>protocol SectionCell: UICollectionViewCell {<br>    associatedtype CellData: SectionCellData<br><br>    func setup(with data: CellData) -&gt; Self<br>    static func size(for data: CellData, width: CGFloat) -&gt; CGSize<br>}<br><br>protocol SectionCellData {<br>    var onSelect: VoidClosure? { get }<br>}<br><br>typealias VoidClosure = () -&gt; Void</pre><p>We move the configuration of the cell size to the area of responsibility of the cell, we also take into account the possibility of receiving an action by tapping on any cell.</p><p>The header and footer requirements will look like this:</p><pre>protocol SectionHeader: UICollectionReusableView {<br>    associatedtype HeaderData<br>    <br>    func setup(with data: HeaderData?) -&gt; Self<br>    static func size(for data: HeaderData?, width: CGFloat) -&gt; CGSize<br>}<br><br>protocol SectionFooter: UICollectionReusableView {<br>    associatedtype FooterData<br>    <br>    func setup(with data: FooterData?) -&gt; Self<br>    static func size(for data: FooterData?, width: CGFloat) -&gt; CGSize<br>}</pre><p>Based on the requirements for scrolling elements, we can design the implementation of the section:</p><pre>class Section&lt;Cell: SectionCell, Header: SectionHeader, Footer: SectionFooter&gt;: BaseSection {<br>    <br>    init(items: [Cell.CellData], headerData: Header.HeaderData? = nil, footerData: Footer.FooterData? = nil) {<br>        self.items = items<br>        self.headerData = headerData<br>        self.footerData = footerData<br>    }<br>    <br>    private(set) var items: [Cell.CellData]<br>    private let headerData: Header.HeaderData?<br>    private let footerData: Footer.FooterData?<br>    <br>    var numberOfElements: Int {<br>        items.count<br>    }<br>    <br>    func registrate(collectionView: UICollectionView) {<br>        collectionView.register(Cell.self)<br>        collectionView.registerHeader(Header.self)<br>        collectionView.registerFooter(Footer.self)<br>    }<br>    <br>    func cell(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionViewCell {<br>        collectionView<br>            .dequeue(Cell.self, indexPath: indexPath)?<br>            .setup(with: items[indexPath.row])<br>        ?? UICollectionViewCell()<br>    }<br>    <br>    func header(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView {<br>        collectionView<br>            .dequeueHeader(Header.self, indexPath: indexPath)?<br>            .setup(with: headerData)<br>        ?? UICollectionReusableView()<br>    }<br>    <br>    func footer(for collectionView: UICollectionView, indexPath: IndexPath) -&gt; UICollectionReusableView {<br>        collectionView<br>            .dequeueFooter(Footer.self, indexPath: indexPath)?<br>            .setup(with: footerData)<br>        ?? UICollectionReusableView()<br>    }<br>    <br>    func cellSize(for collectionView: UICollectionView, layout: UICollectionViewFlowLayout, indexPath: IndexPath) -&gt; CGSize {<br>        Cell.size(<br>            for: items[indexPath.row],<br>            width: collectionView.frame.width - collectionView.contentInset.left - collectionView.contentInset.right - layout.minimumInteritemSpacing<br>        )<br>    }<br>    <br>    func headerSize(for collectionView: UICollectionView) -&gt; CGSize {<br>        Header.size(<br>            for: headerData,<br>            width: collectionView.frame.width - collectionView.contentInset.left - collectionView.contentInset.right<br>        )<br>    }<br>    <br>    func footerSize(for collectionView: UICollectionView) -&gt; CGSize {<br>        Footer.size(<br>            for: footerData,<br>            width: collectionView.frame.width - collectionView.contentInset.left - collectionView.contentInset.right<br>        )<br>    }<br>    <br>    func select(row: Int) {<br>        items[row].onSelect?()<br>    }<br>}</pre><p>Generics that implement the requirements for them act as cell / header / footer types.</p><p>In general, the implementation is complete, but I would like to add a few helpers that further reduce the amount of boilerplate code. In particular, in practice it will not always be useful to have such a generic section, for the simple reason that the footer or header is not always used. Therefore, let’s add a section heir that takes into account similar cases:</p><pre>class SectionWithoutHeaderFooter&lt;Cell: SectionCell&gt;: Section&lt;Cell, EmptySectionHeader, EmptySectionFooter&gt; {}<br><br>class EmptySectionHeader: UICollectionReusableView, SectionHeader {<br>    func setup(with data: String?) -&gt; Self {<br>        self<br>    }<br><br>    static func size(for data: String?, width: CGFloat) -&gt; CGSize {<br>        .zero<br>    }<br>}<br><br>class EmptySectionFooter: UICollectionReusableView, SectionFooter {<br>    func setup(with data: String?) -&gt; Self {<br>        self<br>    }<br><br>    static func size(for data: String?, width: CGFloat) -&gt; CGSize {<br>        .zero<br>    }<br>}</pre><p>On this, the design can be considered complete, I propose to move on to the use cases themselves.</p><h3>4. Use cases</h3><p>Let’s create a section of cells with a fixed size and display it on the screen:</p><pre>class ColorCollectionCell: UICollectionViewCell, SectionCell {<br>    func setup(with data: ColorCollectionCellData) -&gt; Self {<br>        contentView.backgroundColor = data.color<br>        <br>        return self<br>    }<br><br>    static func size(for data: ColorCollectionCellData, width: CGFloat) -&gt; CGSize {<br>        .init(width: width / 2, height: width / 2)<br>    }<br>}<br><br>class ColorCollectionCellData: SectionCellData {<br>    let onSelect: VoidClosure?<br>    let color: UIColor<br>    <br>    init(color: UIColor, onSelect: VoidClosure? = nil) {<br>        self.onSelect = onSelect<br>        self.color = color<br>    }<br>}<br><br>class ViewController: UIViewController {<br>    <br>    let sectionView = SectionView()<br>    <br>    override func loadView() {<br>        view = sectionView<br>    }<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        sectionView.backgroundColor = .white<br><br>        sectionView.set(<br>            sections: [<br>                SectionWithoutHeaderFooter&lt;ColorCollectionCell&gt;(<br>                    items: [<br>                        .init(color: .blue) {<br>                            print(#function)<br>                        },<br>                        .init(color: .red) {<br>                            print(#function)<br>                        },<br>                        .init(color: .yellow) {<br>                            print(#function)<br>                        },<br>                        .init(color: .green) {<br>                            print(#function)<br>                        },<br>                        .init(color: .blue) {<br>                            print(#function)<br>                        }<br>                    ]<br>                )<br>            ],<br>            append: false<br>        )<br>    }<br>}</pre><p>In total, in just a few lines of code, we implemented 5 multi-colored cells with a fixed size.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M5z6Fpb40X6jKRbLBBGGfg.png" /></figure><p>Let’s try to use a similar approach for dynamically sized cells. For this flight, we will write the successor of SectionCell:</p><pre>protocol DynamicSectionCell: SectionCell {<br>    <br>}<br><br>extension DynamicSectionCell {<br>    static func size(for data: CellData, width: CGFloat) -&gt; CGSize {<br>        let cell = Self().setup(with: data)<br>        let size = cell.contentView.systemLayoutSizeFitting(<br>            .init(width: width, height: .greatestFiniteMagnitude),<br>            withHorizontalFittingPriority: .required,<br>            verticalFittingPriority: .fittingSizeLevel<br>        )<br>        <br>        return .init(width: width, height: size.height)<br>    }<br>}</pre><p>The method used here is the Prototype design pattern. To calculate the cell size, we create a cell, fill it with data, and calculate its size based on the data.</p><p>Using this approach, the implementation of the cell will look like this:</p><pre>class DynamicCollectionCell: UICollectionViewCell, DynamicSectionCell {<br>    let textLabel = UILabel()<br>    <br>    override init(frame: CGRect) {<br>        super.init(frame: frame)<br>        <br>        commonInit()<br>    }<br><br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>        <br>        commonInit()<br>    }<br>    <br>    private func commonInit() {<br>        contentView.addSubview(textLabel)<br>        textLabel.numberOfLines = .zero<br>        textLabel.translatesAutoresizingMaskIntoConstraints = false<br>        <br>        NSLayoutConstraint.activate([<br>            textLabel.topAnchor.constraint(equalTo: contentView.topAnchor),<br>            textLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),<br>            textLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor),<br>            textLabel.rightAnchor.constraint(equalTo: contentView.rightAnchor)<br>        ])<br>    }<br><br>    func setup(with data: DynamicCollectionCellData) -&gt; Self {<br>        textLabel.text = data.text<br>        <br>        return self<br>    }<br>}<br><br>class DynamicCollectionCellData: SectionCellData {<br>    let text: String<br>    var onSelect: VoidClosure?<br>    <br>    init(text: String) {<br>        self.text = text<br>    }<br>}<br><br>class ViewController: UIViewController {<br>    <br>    let sectionView = SectionView()<br>    <br>    override func loadView() {<br>        view = sectionView<br>    }<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        sectionView.backgroundColor = .white<br>        sectionView.set(itemSpacing: 16)<br>        sectionView.set(lineSpacing: 16)<br>        sectionView.set(<br>            sections: [<br>                SectionWithoutHeaderFooter&lt;DynamicCollectionCell&gt;(<br>                    items: [<br>                        .init(text: &quot;Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s&quot;),<br>                        .init(text: &quot;when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.&quot;),<br>                        .init(text: &quot;It was popularised&quot;),<br>                        .init(text: &quot;the 1960s with the release of Letraset sheets containing&quot;),<br>                        .init(text: &quot;Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.&quot;)<br>                    ]<br>                ),<br>                SectionWithoutHeaderFooter&lt;ColorCollectionCell&gt;(<br>                    items: [<br>                        .init(color: .blue) {<br>                            print(#function)<br>                        },<br>                        .init(color: .red) {<br>                            print(#function)<br>                        },<br>                        .init(color: .yellow) {<br>                            print(#function)<br>                        },<br>                        .init(color: .green) {<br>                            print(#function)<br>                        },<br>                        .init(color: .blue) {<br>                            print(#function)<br>                        }<br>                    ]<br>                )<br>            ],<br>            append: false<br>        )<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gHkTV-H4emauE2ACcXB20Q.png" /></figure><p>Despite the need to calculate the size of the UICollectionView elements, we were able to get rid of the template code and taught how to calculate the size dynamically.</p><p>The source code can be viewed <a href="https://github.com/BugorBN/BaseCollection">here</a>, and in the next article I will show how to use a similar approach to work with UICollectionViewCompositionalFlowLayout.</p><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6e5225661cc4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Share data between devices without internet access. iOS Multipeer Connectivity]]></title>
            <link>https://bugorbn.medium.com/share-data-between-devices-without-internet-access-ios-multipeer-connectivity-7caaba7ff477?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/7caaba7ff477</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[multipeerconnectivity]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sat, 05 Aug 2023 21:24:47 GMT</pubDate>
            <atom:updated>2024-12-04T20:58:57.410Z</atom:updated>
            <content:encoded><![CDATA[<h3>Share data across devices without internet access. <strong>iOS Multipeer Connectivity</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CQBHFixQCZPbxsjQ" /><figcaption>Photo by <a href="https://unsplash.com/@jamesplewis?utm_source=medium&amp;utm_medium=referral">James Lewis</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Multipeer Connectivity is an alternative to the common data exchange format. Instead of exchanging data via Wi-Fi or Cellular Network through an intermediate broker, which is usually a backend server, Multipeer Connectivity provides the ability to exchange information between multiply nearby devices without intermediaries.</p><p>The technology itself for the iPhone and iPad is based on Wi-Fi and Bluetooth, while for the MacBook and Apple TV, a similar technology was organized via Wi-Fi and Ethernet.</p><p>From here, the pros and cons of this technology immediately follow. The advantages include decentralization and, accordingly, the ability to exchange information without intermediaries.</p><p>Disadvantages — sharing is limited to Wi-Fi and Bluetooth coverage for iPhone and iPad, or Wi-Fi and Ethernet for MacBook and Apple TV. In other words, the exchange of information can be carried out in the immediate vicinity of the devices.</p><p>Integration of Multipeer Connectivity is not complicated and consists of the following steps:</p><ol><li>Project preset</li><li>Setup visibility for other devices</li><li>Scan for visible devices in range</li><li>Creating a pair of devices for data exchange</li><li>Data exchange</li></ol><p>Let’s take a closer look at each of the above steps.</p><h3><strong>1. </strong>Project preset</h3><p>At this stage, the project must be prepared for the implementation of Multipeer Connectivity. To do this, you need to obtain additional permissions from the user to be able to scan:</p><ul><li>to do this, add <strong>Privacy — Local Network Usage Description</strong> to the Info.plist file with a description of the purpose of use;</li><li>In addition, for the possibility of information exchange, Info.plist also needs to be supplemented with the following lines:</li></ul><pre>&lt;key&gt;NSBonjourServices&lt;/key&gt;<br> &lt;array&gt;<br>  &lt;string&gt;_nearby-devices._tcp&lt;/string&gt;<br>  &lt;string&gt;_nearby-devices._upd&lt;/string&gt;<br> &lt;/array&gt;</pre><p>It is important to note that the nearby-devices substring is used as an example in this context. In your project, this key must meet the following requirements:</p><blockquote>1–15 characters long and valid characters include ASCII lowercase letters, numbers, and the hyphen, containing at least one letter and no adjacent hyphens.<br> <br> You can read more about the requirements <a href="https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyserviceadvertiser?language=objc">here</a>.</blockquote><p>As for communication protocols, the example uses tcp and upd (more reliable and less reliable one). If you do not know which protocol you need, you should enter both.</p><h3><strong>2. </strong>Setup visibility for other devices</h3><p>Organization of device visibility for multi-peer connection is implemented by MCNearbyServiceAdvertiser. Let’s create a class that will be responsible for detecting, displaying and sharing information between devices.</p><pre>import MultipeerConnectivity<br>import SwiftUI<br><br>class DeviceFinderViewModel: ObservableObject {<br>    private let advertiser: MCNearbyServiceAdvertiser<br>    private let session: MCSession<br>    private let serviceType = &quot;nearby-devices&quot;<br><br>    @Published var isAdvertised: Bool = false {<br>        didSet {<br>            isAdvertised ? advertiser.startAdvertisingPeer() : advertiser.stopAdvertisingPeer()<br>        }<br>    }<br>    <br>    init() {<br>        let peer = MCPeerID(displayName: UIDevice.current.name)<br>        session = MCSession(peer: peer)<br>        <br>        advertiser = MCNearbyServiceAdvertiser(<br>            peer: peer,<br>            discoveryInfo: nil,<br>            serviceType: serviceType<br>        )<br>    }<br>}</pre><p>The core of the multipeer is a MCSession, which will allow you to connect and exchange data between devices.</p><p>The serviceType is the key mentioned above, which was added to the Info.plist file along with the exchange protocols.</p><p>The isAdvertised property will allow you to switch the visibility of the device using Toggle.</p><h3><strong>3. </strong>Scan for visible devices in range</h3><p>Device visibility scanning for a multi-peer connection is performed by MCNearbyServiceBrowser:</p><pre>class DeviceFinderViewModel: NSObject, ObservableObject {<br>    ...<br>    private let browser: MCNearbyServiceBrowser<br>    ...<br><br>    @Published var peers: [PeerDevice] = []<br>    ...<br>    <br>    override init() {<br>        ...<br>        <br>        browser = MCNearbyServiceBrowser(peer: peer, serviceType: serviceType)<br>        <br>        super.init()<br>        <br>        browser.delegate = self<br>    }<br><br>    func startBrowsing() {<br>        browser.startBrowsingForPeers()<br>    }<br>    <br>    func finishBrowsing() {<br>        browser.stopBrowsingForPeers()<br>    }<br>}<br><br>extension DeviceFinderViewModel: MCNearbyServiceBrowserDelegate {<br>    func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {<br>        peers.append(PeerDevice(peerId: peerID))<br>    }<br><br>    func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {<br>        peers.removeAll(where: { $0.peerId == peerID })<br>    }<br>}  <br><br>struct PeerDevice: Identifiable, Hashable {<br>    let id = UUID()<br>    let peerId: MCPeerID<br>}</pre><p>A list of all visible devices will be stored in peers. TheMCNearbyServiceBrowser delegate methods will add or remove an MCPeerID when a peer is found or lost. <br>The startBrowsing and finishBrowsing methods will be used to start discovering visible devices when the screen appears, or stop searching after the screen disappears.</p><p>The following View will be used as the UI:</p><pre>struct ContentView: View {<br>    @StateObject var model = DeviceFinderViewModel()<br><br>    var body: some View {<br>        NavigationStack {<br>            List(model.peers) { peer in<br>                HStack {<br>                    Image(systemName: &quot;iphone.gen1&quot;)<br>                        .imageScale(.large)<br>                        .foregroundColor(.accentColor)<br><br>                    Text(peer.peerId.displayName)<br>                        .frame(maxWidth: .infinity, alignment: .leading)<br>                }<br>                .padding(.vertical, 5)<br>            }<br>            .onAppear {<br>                model.startBrowsing()<br>            }<br>            .onDisappear {<br>                model.finishBrowsing()<br>            }<br>            .toolbar {<br>                ToolbarItem(placement: .navigationBarTrailing) {<br>                    Toggle(&quot;Press to be discoverable&quot;, isOn: $model.isAdvertised)<br>                        .toggleStyle(.switch)<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Device visibility will be enabled/disabled by the Toggle.<br>As a result, at this stage, the detection and display of devices should work correctly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JaafnNF4Y7tdb_bY9LJe0w.gif" /></figure><h3>4. Creating a pair of devices for data exchange</h3><p>The delegate method MCNearbyServiceAdvertiser didReceiveInvitationFromPeer is responsible for sending an invitation between a pair of devices. Both of them must be capable of handling this request.</p><pre><br>class DeviceFinderViewModel: NSObject, ObservableObject {<br>    ...<br><br>    @Published var permissionRequest: PermitionRequest?<br>    <br>    @Published var selectedPeer: PeerDevice? {<br>        didSet {<br>            connect()<br>        }<br>    }<br><br>    ...<br>   <br>    @Published var joinedPeer: [PeerDevice] = []<br>    <br>    override init() {<br>        ...<br>               <br>        advertiser.delegate = self<br>    }<br><br>    func startBrowsing() {<br>        browser.startBrowsingForPeers()<br>    }<br>    <br>    func finishBrowsing() {<br>        browser.stopBrowsingForPeers()<br>    }<br>    <br>    func show(peerId: MCPeerID) {<br>        guard let first = peers.first(where: { $0.peerId == peerId }) else {<br>            return<br>        }<br>        <br>        joinedPeer.append(first)<br>    }<br>    <br>    private func connect() {<br>        guard let selectedPeer else {<br>            return<br>        }<br>        <br>        if session.connectedPeers.contains(selectedPeer.peerId) {<br>            joinedPeer.append(selectedPeer)<br>        } else {<br>            browser.invitePeer(selectedPeer.peerId, to: session, withContext: nil, timeout: 60)<br>        }<br>    }<br>}<br><br>extension DeviceFinderViewModel: MCNearbyServiceAdvertiserDelegate {<br>    func advertiser(<br>        _ advertiser: MCNearbyServiceAdvertiser,<br>        didReceiveInvitationFromPeer peerID: MCPeerID,<br>        withContext context: Data?,<br>        invitationHandler: @escaping (Bool, MCSession?) -&gt; Void<br>    ) {<br>        permissionRequest = PermitionRequest(<br>            peerId: peerID,<br>            onRequest: { [weak self] permission in<br>                invitationHandler(permission, permission ? self?.session : nil)<br>            }<br>        )<br>    }<br>}<br><br>struct PermitionRequest: Identifiable {<br>    let id = UUID()<br>    let peerId: MCPeerID<br>    let onRequest: (Bool) -&gt; Void<br>}</pre><p>When the selectedPeer is set, the connect method fires. If this peer is in the list of existing peers, it will be added to the joinedPeer array. In the future, this property will be processed by the UI.</p><p>In the absence of this peer in the session, the browser will invite this device to create a pair.</p><p>After that, the didReceiveInvitationFromPeer method will be processed for the invited device. In our case, after the start of didReceiveInvitationFromPeer, a permissionRequest is created with a delayed callback, which will be shown as an alert on the invited device:</p><pre>struct ContentView: View {<br>    @StateObject var model = DeviceFinderViewModel()<br><br>    var body: some View {<br>        NavigationStack {<br>            ...<br>            .alert(item: $model.permissionRequest, content: { request in<br>                Alert(<br>                    title: Text(&quot;Do you want to join \(request.peerId.displayName)&quot;),<br>                    primaryButton: .default(Text(&quot;Yes&quot;), action: {<br>                        request.onRequest(true)<br>                        model.show(peerId: request.peerId)<br>                    }),<br>                    secondaryButton: .cancel(Text(&quot;No&quot;), action: {<br>                        request.onRequest(false)<br>                    })<br>                )<br>            })<br>            ...<br>        }<br>    }<br>}</pre><p>In the case of an approve, didReceiveInvitationFromPeer will return the device sending the invitation, permission and session if permission was succeed.<br>As a result, after successfully accepting the invitation, a pair will be created:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XqdnwSEy_3uSs74lBfBt2Q.gif" /></figure><h3><strong>5. </strong>Data exchange</h3><p>After creating a pair, MCSession is responsible for the exchange of data:</p><pre>import MultipeerConnectivity<br>import Combine<br><br>class DeviceFinderViewModel: NSObject, ObservableObject {<br> ...<br><br>    @Published var messages: [String] = []<br>    let messagePublisher = PassthroughSubject&lt;String, Never&gt;()<br>    var subscriptions = Set&lt;AnyCancellable&gt;()<br><br>    func send(string: String) {<br>        guard let data = string.data(using: .utf8) else {<br>            return<br>        }<br>        <br>        try? session.send(data, toPeers: [joinedPeer.last!.peerId], with: .reliable)<br><br>        messagePublisher.send(string)<br>    }<br>    <br>    override init() {<br><br>        <br>        ...<br>        <br>        session.delegate = self<br>        <br>        messagePublisher<br>            .receive(on: DispatchQueue.main)<br>            .sink { [weak self] in<br>                self?.messages.append($0)<br>            }<br>            .store(in: &amp;subscriptions)<br>    }    <br>}<br><br><br>extension DeviceFinderViewModel: MCSessionDelegate {<br>    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {<br>        guard let last = joinedPeer.last, last.peerId == peerID, let message = String(data: data, encoding: .utf8) else {<br>            return<br>        }<br><br>        messagePublisher.send(message)<br>    }<br>}</pre><p>Method <strong>func</strong> send(<strong>_</strong> data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) <strong>throws</strong> helps send data between peers.</p><p>The delegate method <strong>func</strong> session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) is triggered on the device that received the message.</p><p>Also, an intermediate publisher messagePublisher is used to receive messages, since the MCSession delegate methods fire in the DispatchQueue global().</p><p>More details on the Multipeer Connectivity integration prototype can be found in this <a href="https://github.com/BugorBN/Multipeer-Chat">repository</a>. As an example, this technology has provided the ability to exchange messages between devices.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5pCbAIZ56QeAjVEBpbkmRQ.gif" /></figure><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7caaba7ff477" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating and Modifying UIKit Components Like in SwiftUI]]></title>
            <link>https://medium.com/better-programming/creating-and-modifying-uikit-components-like-in-swiftui-bf0010a8f43?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/bf0010a8f43</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sat, 29 Jul 2023 20:05:36 GMT</pubDate>
            <atom:updated>2024-12-04T20:58:44.949Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VOnrJawI7PKeNBSz" /><figcaption>Photo by <a href="https://unsplash.com/@firmbee?utm_source=medium&amp;utm_medium=referral">Firmbee.com</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Protocol-oriented programming is one of the most powerful and flexible tools for competent composition and distribution of responsibility in Swift. In one of the <a href="https://medium.com/@bugorbn/state-management-using-protocol-oriented-programming-generics-4c33bd290ff1">previous articles</a>, protocol-oriented programming was used to manage the state and build a safe sequence of state transitions without additional checks. If you have not read the <a href="https://medium.com/@bugorbn/state-management-using-protocol-oriented-programming-generics-4c33bd290ff1">previous article</a>, then it is recommended to read as material that will show one of the ways to use this wonderful approach.</p><p>In this article, we’ll explore another way to use protocol-oriented programming. As a bonus — we’ll write our extension for programming UI components in UIKit that mimics the SwiftUI experience.</p><p>What is the task before us? As we all know, all graphical components in UIKit are direct descendants of UIView, each with its own unique properties. A protocol-oriented approach will help us to endow each of the heirs with their own unique properties while making it possible to combine these properties in case we want to use the properties of the parent and the properties of the child. In addition to the protocol-oriented approach, we will also use the Decorator design pattern to bring the SwiftUI declarative syntax experience to UIKit.</p><p>Let’s start with the simplest. Select several basic UI classes with which we will start:</p><ol><li>UIView — all graphical components are inherited from it</li><li>UIControl — all UIButton, UISegmentedControl and so on are inherited from it</li><li>Final UI components like UILabel, UITextField, and so on</li></ol><p>The inheritance diagram can be seen below:</p><figure><img alt="Simplified inheritance scheme" src="https://cdn-images-1.medium.com/max/1024/1*Rl4QnCfOnhwPJ0dUSgeiLA.png" /><figcaption>Simplified inheritance scheme</figcaption></figure><p>Let’s start our task by creating an interface:</p><pre>protocol Stylable {}</pre><p>This protocol is the basis for all subsequent extensions for our case. Since all graphical components somehow inherit from UIView to cover all components — it is enough to extend UIView with this protocol:</p><pre>extension UIView: Stylable {}</pre><p>Some of the most used properties for customizing a UIView are cornerRadius, backgroundColor, clipsToBounds, contentMode, isHidden. Moreover, these properties are often used not only to configure the UIView, but also for its descendants.</p><p>Let’s extend the possibilities of Stylable for all UIView classes and their descendants:</p><pre>extension Stylable where Self: UIView {<br>    @discardableResult<br>    func cornerRadius(_ value: CGFloat) -&gt; Self {<br>        self.layer.cornerRadius = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func backgroundColor(_ value: UIColor) -&gt; Self {<br>        self.backgroundColor = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func clipsToBounds(_ value: Bool) -&gt; Self {<br>        self.clipsToBounds = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func contentMode(_ value: UIView.ContentMode) -&gt; Self {<br>        self.contentMode = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func isHidden(_ value: Bool) -&gt; Self {<br>        self.isHidden = value<br><br>        return self<br>    }<br>}</pre><p>Let’s check what this extension gave us:</p><pre>let customView = UIView()<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br>    <br>let customButton = UIButton()<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br>    <br>let segmentedControl = UISegmentedControl(items: [&quot;One&quot;, &quot;Two&quot;])<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br>    <br>let scrollView = UIScrollView()<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br><br>let textField = UITextField()<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)</pre><p>As we can see, thanks to the extension, we can declaratively change properties not only for UIView but also for its descendants.</p><p>Let’s move on to configuring the UIControl. For all of its descendants, one of the most used customizable things is tap, properties — isEnabled, tintColor, isUserInteractionEnabled.</p><p>Let’s extend the possibilities of Stylable for all UIControl classes and their descendants:</p><pre>extension Stylable where Self: UIControl {<br>    @discardableResult<br>    func action(_ value: (() -&gt; Void)?, event: UIControl.Event = .touchUpInside) -&gt; Self {<br>        let identifier = UIAction.Identifier(String(describing: event.rawValue))<br>        let action = UIAction(identifier: identifier) { _ in<br>            value?()<br>        }<br>        <br>        self.removeAction(identifiedBy: identifier, for: event)<br>        self.addAction(action, for: event)<br>        <br>        return self<br>    }<br>    <br>    @discardableResult<br>    func secondAction(_ value: ((Bool) -&gt; Void)?, controlEvent: UIControl.Event = .valueChanged) -&gt; Self {<br>        let identifier = UIAction.Identifier(String(describing: controlEvent.rawValue))<br>        let action = UIAction(identifier: identifier) { item in<br>            guard let control = item.sender as? UIControl else {<br>                return<br>            }<br>            value?(!control.isTracking)<br>        }<br>        <br>        self.removeAction(identifiedBy: identifier, for: controlEvent)<br>        self.addAction(action, for: controlEvent)<br>        <br>        return self<br>    }<br>    <br>    @discardableResult<br>    func isEnabled(_ value: Bool) -&gt; Self {<br>        self.isEnabled = value<br>        <br>        return self<br>    }<br>    <br>    @discardableResult<br>    func isUserInteractionEnabled(_ value: Bool) -&gt; Self {<br>        self.isUserInteractionEnabled = value<br>        <br>        return self<br>    }<br>    <br>    @discardableResult<br>    func tintColor(_ value: UIColor) -&gt; Self {<br>        self.tintColor = value<br>        <br>        return self<br>    }<br>}</pre><p>After the Stylable extension for UIControl, an additional customization option became available for all its descendants:</p><pre>let customButton = UIButton()<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br>    .tintColor(.red)<br>    .action {<br>       print(#function)<br>    }<br>    .isEnabled(true)<br>    .isUserInteractionEnabled(true)<br><br>    <br>let segmentedControl = UISegmentedControl(items: [&quot;One&quot;, &quot;Two&quot;])<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br>    .tintColor(.red)<br>    .action {<br>       print(#function)<br>    }<br>    .isEnabled(true)<br>    .isUserInteractionEnabled(true)</pre><p><em>It is worth noting that when calling the action method using a class method, you should initialize this component lazily to ensure that the class (self) is initialized before the component is initialized.</em></p><pre>lazy var customButton = UIButton()<br>    .backgroundColor(.red)<br>    .clipsToBounds(true)<br>    .cornerRadius(20)<br>    .tintColor(.red)<br>    .action { [weak self] in<br>         self?.actionTest()<br>    }<br>    .isEnabled(true)<br>    .isUserInteractionEnabled(true)<br>    <br>private func actionTest() {<br>    print(#function)<br>}</pre><p>Let’s also extend UITextField with some of the most popular custom properties:</p><pre>extension Stylable where Self: UITextField {<br>    @discardableResult<br>    func text(_ value: String?) -&gt; Self {<br>        self.text = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func font(_ value: UIFont) -&gt; Self {<br>        self.font = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func textAlignment(_ value: NSTextAlignment) -&gt; Self {<br>        self.textAlignment = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func textColor(_ value: UIColor) -&gt; Self {<br>        self.textColor = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func capitalizationType(_ value: UITextAutocapitalizationType) -&gt; Self {<br>        self.autocapitalizationType = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func keyboardType(_ value: UIKeyboardType) -&gt; Self {<br>        self.keyboardType = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func isSecureTextEntry(_ value: Bool) -&gt; Self {<br>        self.isSecureTextEntry = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func autocorrectionType(_ value: UITextAutocorrectionType) -&gt; Self {<br>        self.autocorrectionType = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func contentType(_ value: UITextContentType?) -&gt; Self {<br>        self.textContentType = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func clearButtonMode(_ value: UITextField.ViewMode) -&gt; Self {<br>        self.clearButtonMode = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func placeholder(_ value: String?) -&gt; Self {<br>        self.placeholder = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func returnKeyType(_ value: UIReturnKeyType) -&gt; Self {<br>        self.returnKeyType = value<br><br>        return self<br>    }<br>    <br>    @discardableResult<br>    func delegate(_ value: UITextFieldDelegate) -&gt; Self {<br>        self.delegate = value<br><br>        return self<br>    }<br><br>    @discardableResult<br>    func atributedPlaceholder(<br>        _ value: String,<br>        textColor: UIColor,<br>        textFont: UIFont<br>    ) -&gt; Self {<br>        let attributedString = NSAttributedString(<br>            string: value,<br>            attributes: [<br>                NSAttributedString.Key.foregroundColor: textColor,<br>                NSAttributedString.Key.font: textFont<br>            ]<br>        )<br><br>        self.attributedPlaceholder = attributedString<br><br>        return self<br>    }<br>}</pre><p>Thanks to this extension, now customizing UITextField has become even easier. To customize the GUI, the methods of its parents are available, as well as its own methods:</p><pre>lazy var textField = UITextField()<br>     .placeholder(&quot;Placeholder&quot;)<br>     .textColor(.red)<br>     .text(&quot;Text&quot;)<br>     .contentType(.URL)<br>     .autocorrectionType(.yes)<br>     .font(.boldSystemFont(ofSize: 12))<br>     .delegate(self) </pre><p>It’s worth noting that, similar to capturing self in the UIControl’s action method, assigning a delegate also requires textField to be lazy-initialized.</p><p>By analogy, the rest of the graphical components are expanded with properties that will be used for customization.<br> <br>As a bonus for my readers, I’ve compiled some of the most requested properties in <a href="https://github.com/BugorBN/Styler">this repository</a>. You need to copy the files to your project; they are ready to use.</p><p>Don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf0010a8f43" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/creating-and-modifying-uikit-components-like-in-swiftui-bf0010a8f43">Creating and Modifying UIKit Components Like in SwiftUI</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[State Management Using Protocol-Oriented Programming + Generics]]></title>
            <link>https://medium.com/better-programming/state-management-using-protocol-oriented-programming-generics-4c33bd290ff1?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/4c33bd290ff1</guid>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[generics]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[state-pattern]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Sat, 22 Jul 2023 20:08:32 GMT</pubDate>
            <atom:updated>2024-12-04T20:58:32.234Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wWhIxQwEpab1e2pg" /><figcaption>Photo by <a href="https://unsplash.com/es/@luk10?utm_source=medium&amp;utm_medium=referral">Lukas Tennie</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In this article, I would like to show one of the most convenient ways to manage the state of an object using a protocol-oriented approach and generics.</p><p>This approach is widely used in marketplaces, the banking environment, the service sector, and so on, and assumes that for each state of the object, it is predetermined into which state it is allowed to convert.</p><p>Consider an example:</p><ul><li>You ordered a product on Amazon, and your order is assigned the status Initiated</li><li>After the purchase, your product enters the state of the need for payment (ReadyForPayment)</li><li>After payment, it enters the processing state (Pending)</li><li>After checking the availability of the goods, the fact of payment, and transferring it to the delivery service, your order enters the Delivering status</li><li>After delivery of the goods, the status changes to Delivered, with the possibility of returning the goods within 30 days</li><li>After 30 days, the status of the product changes to Finished, and the possibility of returning the goods ceases</li></ul><p>In addition to the above positive cases, there are also possible pre-foreseen negative cases, such as:</p><ul><li>After ordering the goods (status ReadyForPayment), payment for the goods was not made within 10 minutes, and the order was put into the Cancelled status</li><li>After processing the order (status Pending), it turned out that the product was out of stock, which is why the status of the order changed to CancelledWithRefunded</li><li>After exceeding the pre-announced delivery time, you requested a refund, and the order status changed to CancelledWithRefunded</li><li>After receiving the order, the product turned out to be of inadequate quality, and you also requested a refund in exchange for returning the product. The order status changed to Refunded</li></ul><p>In more detail, a possible status change map is shown in the figure:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3nV7HK2r8hciHCem8vdVBg.png" /></figure><p>The designed system is required to support status routing and the ability to change / scale at the request of the business.</p><p>Let’s start with the most important unit of the system being designed:</p><pre>struct Order&lt;T&gt; {}</pre><p>As a generic parameter, we will use the order status. Let’s create for each order status, by the requirements described above, its own object:</p><pre>struct Initiated {}<br>struct ReadyForPayment {}<br>struct Pending {}<br>struct Delivering {}<br>struct Delivered {}<br>struct Finished {}<br>struct Cancelled {}<br>struct CancelledWithRefunded {}<br>struct Refunded {}</pre><p>The cancellation/refund capability implementation will be done using the “Strategy” design pattern. This will reduce the effort to change the statuses for which the business provides the possibility of cancellation/refusal/return of goods.</p><p>Let’s create our own protocol for each of the negative scenarios:</p><pre>protocol Cancellable {}<br>protocol CancellableWithRefund {}<br>protocol Refundable {}</pre><p>Taking into account the current statement of the problem, we will provide the possibility of canceling/refusing/returning goods for previously created statuses:</p><pre>struct Initiated {}<br>struct ReadyForPayment: Cancellable {}<br>struct Pending: CancellableWithRefund {}<br>struct Delivering: CancellableWithRefund {}<br>struct Delivered: Refundable {}<br>struct Finished {}<br>struct Cancelled {}<br>struct CancelledWithRefund {}<br>struct Refunded {}</pre><p>This completes the design of the system skeleton. Let’s move on to designing status visibility zones. The generic component of the order will help us with this.</p><p>1. According to the task statement, the Initiated status can only be changed to ReadyForPayment:</p><pre>extension Order where T == Initiated {<br>    var readyForPayment: Order&lt;ReadyForPayment&gt; {<br>        Order&lt;ReadyForPayment&gt;()<br>    }<br>}</pre><p>2. ReadyForPayment status changes to Pending after successful payment:</p><pre>extension Order where T == ReadyForPayment {<br>    var pending: Order&lt;Pending&gt; {<br>        Order&lt;Pending&gt;()<br>    }<br>}</pre><p>3. Pending status after checking the availability of goods, the fact of payment and transfer it to the delivery service changes to Delivering:</p><pre>extension Order where T == Pending {<br>    var delivering: Order&lt;Delivering&gt; {<br>        Order&lt;Delivering&gt;()<br>    }<br>}</pre><p>4. The status of Delivering changes to Delivered after delivery of the goods:</p><pre>extension Order where T == Delivering {<br>    var delivered: Order&lt;Delivered&gt; {<br>        Order&lt;Delivered&gt;()<br>    }<br>}</pre><p>5. Delivered status after 30 days excision changes to Finished:</p><pre>extension Order where T == Delivered {<br>    var finished: Order&lt;Finished&gt; {<br>        Order&lt;Finished&gt;()<br>    }<br>}</pre><p>6. Statuses that provide for Cancellable in advance can be changed to Cancelled:</p><pre>extension Order where T: Cancellable {<br>    var canceled: Order&lt;Cancelled&gt; {<br>        Order&lt;Cancelled&gt;()<br>    }<br>}</pre><p>7. CancellableWithRefund statuses can be changed to CancelledWithRefund:</p><pre>extension Order where T: CancellableWithRefund {<br>    var canceledWithRefund: Order&lt;CancelledWithRefund&gt; {<br>        Order&lt;CancelledWithRefund&gt;()<br>    }<br>}</pre><p>8. Statuses that provide for the possibility of returning Refundable goods in advance can be changed to Refunded:</p><pre>extension Order where T: Refundable {<br>    var refunded: Order&lt;Refunded&gt; {<br>        Order&lt;Refunded&gt;()<br>    }<br>}</pre><p>The system is ready. Now, we can use the constructor to collect any movement of statuses within predetermined possibilities.</p><p>For example, an order for which payment has not gone through moves like this:</p><pre>let cancelled = Order&lt;Initiated&gt;().readyForPayment.canceled</pre><p>The order for which it was delivered to the client moves like this:</p><pre>let finished = Order&lt;Initiated&gt;().readyForPayment.pending.delivering.delivered</pre><p>One of the important aspects: at each stage, the order movement occurs according to a pre-implemented routing. You cannot move an order from the Pending status to the Finished status bypassing all other intermediate statuses (if this was not allowed by your transition map).</p><p>Project sources can be found <a href="https://github.com/BugorBN/State-with-Protocol-Oriented-Programming-and-Generics">here</a>.</p><p>And finally, don’t hesitate to contact me on <a href="https://twitter.com/bugorbn">Twitter</a> if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c33bd290ff1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/state-management-using-protocol-oriented-programming-generics-4c33bd290ff1">State Management Using Protocol-Oriented Programming + Generics</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[UIViewController lifecycle under the hood]]></title>
            <link>https://bugorbn.medium.com/uiviewcontroller-lifecycle-under-the-hood-251a6581c03?source=rss-53da94055385------2</link>
            <guid isPermaLink="false">https://medium.com/p/251a6581c03</guid>
            <category><![CDATA[uiviewcontroller]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[uikit]]></category>
            <dc:creator><![CDATA[Boris Bugor]]></dc:creator>
            <pubDate>Fri, 14 Jul 2023 20:35:39 GMT</pubDate>
            <atom:updated>2024-12-04T20:58:19.139Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*o227S6QIWuNxoa2q" /><figcaption>Photo by <a href="https://unsplash.com/pt-br/@laurenmancke?utm_source=medium&amp;utm_medium=referral">Lauren Mancke</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>There are a lot of articles about <em>UIViewController</em> lifecycle. Most of them are aimed at remembering the order in which life cycle methods of <em>UIViewController</em> are called and at using this order for UI configuration and request network data</p><p>This article will not include another interpretation of this sequence, assuming that its reader is already familiar with the basics of the life cycle <em>UIViewController</em>.</p><p>My task is to show the work of the UIViewController life cycle from a different point of view, namely</p><ul><li>show the source, responsible for the formation and start of the life cycle methods</li><li>cover more corner cases than just showing <em>UIViewController</em> via <em>present</em> / <em>push</em> methods;</li></ul><p>The goals of this article are to develop a deeper understanding of how the life cycle works, which will avoid situations where life cycle methods are either called in the wrong way, or not called at all.</p><h3><strong>UIViewController</strong></h3><p>Let’s start with a small example in the playground and cover the call of the lifecycle methods with prints and initialize the instance of this <em>UIViewController</em></p><pre>class LCViewController: UIViewController {<br>    <br>    init() {<br>        super.init(nibName: nil, bundle: nil)<br>        print(#function, type(of: self))<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>    }<br>    <br>    override func loadView() {<br>        print(#function, type(of: self))<br>        super.loadView()<br>    }<br>    <br>    override func viewDidLoad() {<br>        print(#function, type(of: self))<br>        super.viewDidLoad()<br>    }<br>    <br>    override func viewWillAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewWillAppear(animated)<br>    }<br>    <br>    override func viewDidAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewDidAppear(animated)<br>    }<br>}<br><br>let vc = LCViewController()</pre><p>In the console, we see that the object initialization block has been executed.</p><pre>init() LCViewController</pre><p>The rest of the lifecycle methods did not work, at least for the reason that the initialized object was not shown on the screen. But is this a necessary condition?</p><p>Let’s try to change the background color of the <em>root view</em> for the initialized object:</p><pre>let vc = LCViewController()<br>vc.view.backgroundColor = .red</pre><p>Console:</p><pre>init() LCViewController<br>loadView() LCViewController<br>viewDidLoad() LCViewController</pre><p>What do we see? By default, the <em>view</em> is initialized <strong>lazily</strong>, but after the color has been changed, the view is finally loaded (methods that signal the start and end of view loading are triggered).</p><p>Let’s move on.</p><p>We know that the lifecycle methods will work if the <em>UIViewController</em> is shown on the screen (for example, using the <em>present</em> or <em>push</em> methods), but is it possible to trigger the lifecycle of a <em>UIViewController</em> without showing it on the screen?</p><p>It is.</p><p>It is enough to initialize the <em>UIWindow</em> instance, and place the same <em>UIViewController</em> created above as the <em>rootViewController</em>.</p><pre>let window = UIWindow()<br>let vc = LCViewController()<br>window.rootViewController = vc<br>window.makeKeyAndVisible()</pre><p>Console:</p><pre>init() LCViewController<br>loadView() LCViewController<br>viewDidLoad() LCViewController<br>viewWillAppear(_:) LCViewController<br>viewDidAppear(_:) LCViewController</pre><p>In fact, this operation is enough to activate all the lifecycle methods responsible for the appearance of the <em>UIViewController</em> on the screen.</p><p>A similar operation is at the heart of launching the application with the installation of the starting <em>UIViewController</em> programmatically (without <em>storyboard</em> / <em>xib</em>) and is used under the hood in the case of <em>storyboard</em> / <em>xib</em>.</p><p>According to the documentation, <a href="https://developer.apple.com/documentation/uikit/uiwindow"><em>UIWindow</em></a> is the very source responsible for the formation and start of the <em>UIViewController</em> life cycle.</p><p>Let’s look at the rest of the navigation cases in <em>UIKit</em>:</p><h3><strong>UINavigationController</strong></h3><p>Let’s create a subclass <em>UINavigationController</em>:</p><pre>class LCNavigationController: UINavigationController {<br>    <br>    override init(rootViewController: UIViewController) {<br>        super.init(rootViewController: rootViewController)<br>        print(#function, type(of: self))<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>    }<br>    <br>    override func loadView() {<br>        print(#function, type(of: self))<br>        super.loadView()<br>    }<br><br>    override func viewDidLoad() {<br>        print(#function, type(of: self))<br>        super.viewDidLoad()<br>    }<br><br>    override func viewWillAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewWillAppear(animated)<br>    }<br><br>    override func viewDidAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewDidAppear(animated)<br>    }<br>}<br><br>let vc = LCViewController()<br>let nc = LCNavigationController(rootViewController: vc)</pre><p>After the usual initialization in the console, we observe:</p><pre>init() LCViewController<br>loadView() LCNavigationController<br>viewDidLoad() LCNavigationController<br>init(rootViewController:) LCNavigationController</pre><p>In the case of <em>LCNavigationController</em>, the methods responsible for loading the view fire before the initializer, contrary to the sequence of the <em>UIViewController</em> life cycle. This must be kept in mind when writing the base <em>UINavigationController</em> classes.</p><p>In case of setting as <em>rootViewController</em>:</p><pre>let window = UIWindow()<br>let vc = LCViewController()<br>let nc = LCNavigationController(rootViewController: vc)<br>window.rootViewController = nc<br>window.makeKeyAndVisible()</pre><p>Console:</p><pre>init() LCViewController<br>loadView() LCNavigationController<br>viewDidLoad() LCNavigationController<br>init(rootViewController:) LCNavigationController<br>viewWillAppear(_:) LCNavigationController<br>loadView() LCViewController<br>viewDidLoad() LCViewController<br>viewWillAppear(_:) LCViewController<br>viewDidAppear(_:) LCNavigationController<br>viewDidAppear(_:) LCViewController</pre><p>The results undermine the theory of a chain of direct forwarding of lifecycle events from <em>UINavigationController</em> to <em>UIViewController</em>. The <em>UIViewController</em> starts loading only after the <em>UINavigationController’s</em> <em>viewWillAppear</em> fires.</p><h3><strong>TabBarController</strong></h3><p>Let’s initialize <em>UIViewController</em> and <em>UITabBarController</em>:</p><pre>class LCTabbarController: UITabBarController {<br>    init() {<br>        print(#function, type(of: self))<br>        super.init(nibName: nil, bundle: nil)<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>    }<br>    <br>    override func loadView() {<br>        print(#function, type(of: self))<br>        super.loadView()<br>    }<br><br>    override func viewDidLoad() {<br>        print(#function, type(of: self))<br>        super.viewDidLoad()<br>    }<br><br>    override func viewWillAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewWillAppear(animated)<br>    }<br><br>    override func viewDidAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewDidAppear(animated)<br>    }<br>}<br><br>let vc = LCViewController()<br>let tc = LCTabbarController()</pre><p>Bottom line: <em>UITabBarController</em> starts loading the view immediately after initialization without additional manipulations:</p><pre>init() LCViewController<br>init() LCTabbarController<br>loadView() LCTabbarController<br>viewDidLoad() LCTabbarController</pre><p>Immediately after setting the <em>UIViewController</em> , the <em>UITabBarController</em> forces the view to be loaded by <strong>the active (by default, the first)</strong> <em>UIViewController</em></p><pre>let vc = LCViewController()<br>let secondVC = LCViewController()<br>let tc = LCTabbarController()<br>tc.setViewControllers([vc, secondVC], animated: true)</pre><p>Console:</p><pre>init() LCViewController<br>init() LCTabbarController<br>loadView() LCTabbarController<br>viewDidLoad() LCTabbarController<br>loadView() LCViewController<br>viewDidLoad() LCViewController</pre><p>Immediately after setting the <em>UITabBarController</em> as the <em>rootViewController</em> of the <em>UIWindow</em>, the rest of the life cycle methods are triggered, which is responsible for the appearance of the <em>UITabBarController</em> / the first <em>UIViewController:</em></p><pre>let window = UIWindow()<br>let vc = LCViewController()<br>let secondVC = LCViewController()<br>let tc = LCTabbarController()<br>tc.setViewControllers([vc, secondVC], animated: true)<br>window.rootViewController = tc<br>window.makeKeyAndVisible()</pre><p>Console:</p><pre>init() LCViewController<br>init() LCViewController<br>init() LCTabbarController<br>loadView() LCTabbarController<br>viewDidLoad() LCTabbarController<br>loadView() LCViewController<br>viewDidLoad() LCViewController<br>viewWillAppear(_:) LCTabbarController<br>viewWillAppear(_:) LCViewController<br>viewDidAppear(_:) LCTabbarController<br>viewDidAppear(_:) LCViewController</pre><p>Here we can talk about direct forwarding of life cycle events from <em>UITabBarController</em> to <strong>the active (by default, the first)</strong> <em>UIViewController</em>.</p><p>As for the other root <em>UIViewControllers</em> of the <em>UITabBarController</em> stack, setting data fetch requests to <em>viewDidLoad</em> will cause requests to be sent only after the user has directly navigated to the screen. You need to remember this when developing and, as an example, choose other life cycle methods to receive data or force the loading of the view of these <em>UIViewController.</em></p><h3><strong>ContainerController</strong></h3><p>And the last thing worth considering within the framework of this article is the use of a container — child.</p><p>Let’s create a root container and inject another <em>UIViewController</em> into it</p><pre>class ContainerController: UIViewController {<br>    <br>    init() {<br>        super.init(nibName: nil, bundle: nil)<br>        print(#function, type(of: self))<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        super.init(coder: coder)<br>    }<br>    <br>    override func loadView() {<br>        print(#function, type(of: self))<br>        super.loadView()<br>    }<br>    <br>    override func viewDidLoad() {<br>        print(#function, type(of: self))<br>        super.viewDidLoad()<br>        <br>        let vc = LCViewController()<br>        vc.willMove(toParent: self)<br>        view.addSubview(vc.view)<br>        vc.view.bounds = view.bounds<br>        vc.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]<br>        vc.didMove(toParent: self)<br>        <br>    }<br>    <br>    override func viewWillAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewWillAppear(animated)<br>    }<br>    <br>    override func viewDidAppear(_ animated: Bool) {<br>        print(#function, type(of: self))<br>        super.viewDidAppear(animated)<br>    }<br>}</pre><p>As we have already checked, after the initialization of a regular <em>UIViewController</em>, only the <em>init</em> method fires.</p><pre>let containerVC = ContainerController()</pre><p>init() ContainerController</p><p>Let’s force the container to load the <em>view</em>:</p><pre>let containerVC = ContainerController()<br>containerVC.view.backgroundColor = .red</pre><p>Console:</p><pre>init() ContainerController<br>loadView() ContainerController<br>viewDidLoad() ContainerController<br>init() LCViewController<br>loadView() LCViewController<br>viewDidLoad() LCViewController</pre><p>Let’s place the container as <em>rootViewController</em> in <em>UIWindow</em></p><pre>let window = UIWindow()<br>let containerVC = ContainerController()<br>containerVC.view.backgroundColor = .red<br>window.rootViewController = containerVC<br>window.makeKeyAndVisible()</pre><p>Console:</p><pre>init() ContainerController<br>loadView() ContainerController<br>viewDidLoad() ContainerController<br>init() LCViewController<br>loadView() LCViewController<br>viewDidLoad() LCViewController<br>viewWillAppear(_:) ContainerController<br>viewWillAppear(_:) LCViewController<br>viewDidAppear(_:) LCViewController<br>viewDidAppear(_:) ContainerController</pre><p>In general, we can talk about the similar behavior of <em>ContainerController</em> and <em>UITabBarController</em>. Forwarding lifecycle events between parent and child is direct.</p><h3>Conclusion</h3><ul><li>The firing order of the lifecycle methods is not valid for all cases and may vary depending on the type of navigation.</li><li><em>UIWindow</em> — the source, responsible for the formation and start of the <em>UIViewController</em> life cycle</li><li><em>View</em> of <em>UIViewController</em> is initialized lazily, there are ways to force it to load</li><li>The firing order of the <em>UINavigationController</em> lifecycle methods can vary. Forwarding lifecycle events is not direct</li><li>In the case of <em>UITabBarController</em>, the <em>view</em> is loaded immediately after initialization. There is a direct redirect between the active <em>UIViewController</em> (the first <em>UITabBarController</em> in the stack) and the <em>UITabBarController</em>. By default, <em>UITabBarController</em> methods do not fire inactive <em>UIViewController</em> methods.</li><li>There is a direct forwarding of lifecycle events between an injected <em>UIViewController</em> and its container (on a parent-child basis).</li></ul><p>And finally, don’t hesitate to contact me at <a href="https://twitter.com/bugorbn">Twitter</a>, if you have any questions. Also, you can always <a href="https://www.buymeacoffee.com/bugorbn">buy me a coffee</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=251a6581c03" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>