Swift / iOS Code Challenge

Swift / iOS Code Challenge

此存储库包含一些最新 iOS 和 Swift 的代码示例。

系统要求

  • macOS 蒙特利 (v12.4)

  • XCode v13.4.1

  • 斯威夫特 5.5

如何运行

此代码示例在 iOS 15.1 iPod 7 代模拟器上进行测试。

  • 在 Xcode 中打开CodeChallenge.xcworkspace

  • 构建应用程序 (CMD + B)

  • 选择目标模拟器并运行应用程序 (CMD + R)

代码挑战

大多数代码示例都是作为基于单视图 UIKit 的 iOS 应用程序的一部分实现的。

初始视图控制器(ViewController.swift)用作索引页来调用不同的代码示例

索引视图/屏幕(视图控制器.swift)

The following button items are presenting and invoking different challenge results

  • API call with ClosureChallenge – 1

  • API call with AsyncChallenge – 1

  • UIKit Color SelectorChallenge – 2

  • SwiftUI Color SelectorChallenge – 2

  • UIKit IB LogoChallenge – 5

  • UIKit LogoChallenge – 5

  • SwiftUI LogoChallenge – 5

  • Logo View SegueChallenge – 6

  • Swift UI Logo VCChallenge – 6

  • Swift UI Logo ModalChallenge – 6

IndexView

Challenge 1


Your client has a need to asynchronously communicate with a third-party service.  This serice is at url http://ip.jsontest.com/.  The request is a GET.  The request requires no payload.  The response will be similar to the following json {"ip": "11.11.11.11"}.  Make a call to this service without blocking the application and display the results in the application log.
Create an asynchronous request to request data from the endpoint.  Ensure you do not block the user interface.  Print result to log.  Update user interface label with result.  Include appropriate error handling.

Please provide two different approaches.

- Async / await pattern
- Closure callback pattern

The code example is implemented in and invoked in by clickingChallenge-1/ApiCall.swiftViewController.swift

  • API call with Closure (Closure based)

@IBAction func apiAction(_ sender: Any) {
        self.ipClosureActionButton?.alpha = 0.3;
        self.ipClosureActionActivity?.isHidden = false;
        self.apiCaller.getIP { result in
            DispatchQueue.main.async {
                self.ipClosureActionButton?.alpha = 1.0;
                self.ipClosureActionActivity?.isHidden = true;
            }
            switch result {
            case .success(let ip):
                print("Closure IP: \(ip)")
                DispatchQueue.main.async {
                    self.ipLabel?.text = "IP Address (closure): \(ip)"
                }
            case .failure(let err):
                print("Closure API error: \(err)")
                DispatchQueue.main.async {
                    self.ipLabel?.text = "IP detection fail with error  (closure): \(err)"
                }
            }
        }
    }
  • API call with Async (Async / await pattern)

@IBAction func apiActionWithAsync(_ sender: Any) {
        self.asyncClosureActionButton?.alpha = 0.3
        self.asyncClosureActionActivity?.isHidden = false;
        do {
            Task.init {
                do {
                    let result = try await self.apiCaller.getIp();
                    DispatchQueue.main.async {
                        self.asyncClosureActionButton?.alpha = 1.0
                        self.asyncClosureActionActivity?.isHidden = true;
                    }
                    switch result {
                    case .success(let ip):
                        print("Async IP: \(ip)")
                        DispatchQueue.main.async {
                            self.ipLabel?.text = "IP Address (async): \(ip)"
                        }
                    case .failure(let err):
                        print("Async error: \(err)")
                        DispatchQueue.main.async {
                            self.ipLabel?.text = "IP detection fail with error (async): \(err)"
                        }
                    }
                } catch let error {
                    print("Api call fail with error \(error)")
                    DispatchQueue.main.async {
                        self.ipLabel?.text = "IP detection fail with error (async): \(error)"
                    }
                }
            }
        }
    }

Challenge 2

Create a segmented controller with six color options.  As the different segments are selected, change the color of the segment to the color selected.  Make sure to update the user interface color in the correct thread.

Please provide two different approaches.

- UIKit


The UIKit version of the solution is implemented in using storyboard and invoked through by the button click ColorSelectionViewController.swiftViewController.swiftUIKit Color Selection

@IBAction func showUIKitColorSelector(_ sender: Any) {}

Challenge-2-UIK

  • SwiftUI

SwiftUI version of code example is implemented in and user can view this screen through / buttonColorSelectSwiftUIView.swiftViewController.swiftSwiftUI Color Select

    @IBAction func showSwiftUIColorSelectView(_ sender: Any) {}

Challenge-2-SW

Challenge 3

Create a method that will take in string and return the same string without any spaces.  For example “EY is the best place to work.” would be returned as “EYisthebestplacetowork.”  Do this without the use of string methods like replacingOccurrences.  We want to see you manipulate the string directly.

The code example is implemented in Challenge-3-4-7/CodeChallengePlayground.playground

// Code Challenge - 3: String without space
// Manipulating String as Array of sub strings
func withoutSpace(input: String) -> String {
    return input.split(separator: " ").joined()
}

// Iterating String as Sequence of Characters creating new Sequence with Space char
func withoutSpaceV2(input: String) -> String {
    var result: [Character] = []
    for ch in input {
        if ch != " " {
            result.append(ch)
        }
    }
    return String(result)
}

// Manipulating String as array of characters and filtering space
func withoutSpaceV3(input: String) -> String {
    return String(Array(input).filter { $0 != " "})
}
let input = "EY is the best place to work."
let output1 = withoutSpace(input: input)
let resultIsMatching1 = output1 == "EYisthebestplacetowork."
let output2 = withoutSpaceV2(input: input)
let resultIsMatching2 = output2 == "EYisthebestplacetowork."
let output3 = withoutSpaceV3(input: input)
let resultIsMatching3 = output3 == "EYisthebestplacetowork."

Challenge 4

Use the best pattern to return the following array string as an array of uppercase strings. [“alpha”, “beta”, “gamma”]  There is a way to accomplish this with one line of code.

The code example is implemented in Challenge-3-4-7/CodeChallengePlayground.playground

// Code Challenge 4
let inputArray = ["alpha", "beta", "gamma"]
let outputArray = inputArray.map { $0.uppercased() }

Screenshot of Playground Playground

Challenge 5

Create a view controller segue that shows a new view controller with the EY logo centered regardless of the size device used.  Make sure you have a back button to return to the original view controller.

Please provide three different approaches.
- Interface Builder
- UIKit
- SwiftUI

All three solution is implemented in Group Challenge-5-6

Interface Builder Example

The example is implemented by , interface is developed in . View is presented with button action of LogoIBViewController.swiftMain.storyboardUIKit IB LogoViewController.swift

ScreenShot-IB

UIKit

This UIKit version of the solution is implemented by programmatically view creation in LogoUIKViewController.swift

func createImageView() {
        let image: UIImage = #imageLiteral(resourceName: "ey")
        let imageView = UIImageView(image: image)
        imageView.frame = CGRect(x: 0, y: 0, width: 2, height: 120)
        imageView.contentMode = .scaleAspectFit
        imageView.tag = imageViewTag
        imageView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(imageView)
        let xConstraint = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 0);
        let yConstraint = NSLayoutConstraint(item: imageView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.0, constant: 0)
        
        let widthConstraint = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 120)
        let heightConstraint = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 120)
        self.view.addConstraint(xConstraint)
        self.view.addConstraint(yConstraint)
        imageView.addConstraint(widthConstraint)
        imageView.addConstraint(heightConstraint)
        self.view?.layoutSubviews()
    }

ScreenShot-UIK

SwiftUI

The SwiftUI version is implemented in and view is presented from button action of LogoSwiftView.swiftSwiftUI LogoViewController.swift

struct ContentView: View {
    var body: some View {
        Image("ey")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 120, height: 120, alignment: .center)
            .border(Color.blue, width: 3.0)
            .clipped()
    }
}

ScreenShot-SW

Challenge 6

Use code only (no Interface Builder or xib) to create a view controller segue that shows a new view controller with the EY logo centered regardless of the size device used.  Make sure you have a back button to return to the original view controller.

Please provide two different approaches.
- UIKit
- SwiftUI

Assumption and decisions

  • Segue is view presentation technique for Storyboard. Here, we implemented a programmatic invocation of storyboard segue.

  • LogoUIKViewController.swift view rendering/layout segment is developed programmatically without Interface builder or StoryBoard, hence that is presented.

  • LogoSwiftUIViewController.swift (a programmatic subclass of ) is presenting storyboard view-controller object. Here, we invoke segue programmatically.UIHostingControllerHosting

  • For view, we created a programmatic modal presentation with with dismiss action (No Interface builder or Storyboard).SwiftUILogoSwiftViewModal.swift

UIKit Version

In the on touch up of action, the previously created controller is presented programmatically.ViewController.swiftLogo View SegueLogoUIKViewController.swift

// Code Challenge 6
    @IBAction func showLogoViewProgrammatically(_ sender: Any) {
        self.performSegue(withIdentifier: "showLogoView", sender: self)
    }

SwiftUI Version

In the on touch up of action is presenting view by programmatically invoking segue in Main.storyboardViewController.swiftSwiftUI Logo VCLogoSwiftUIViewController.swift

@IBAction func showLogoSwiftVC(_ sender: Any) {
        self.performSegue(withIdentifier: "showSwiftUILogoVC", sender: self)
    }

and is presenting in modal transition.SwiftUI Logo ModalLogoSwiftViewModal.swift

@IBAction func showLogoSwiftVCInModal(_ sender: Any) {
        self.present(UIHostingController(rootView: LogoSwiftViewModal(container: self)), animated: true)
    }

Challenge 7

Demonstrate your understanding of Combine’s Publisher and Subscriber by creating a simple publisher that you can subscribe to.  After subscribing, demonstrate that you receive and respond to events (write to debug log) and that you can cancel the subscription if desired.

The code example is implemented in Challenge-3-4-7/CodeChallengePlayground.playground

// Code Challenge 7

// Event type
typealias Event = String
// Event param tuple
typealias EventData = (event: Event, info: Any?)
// Event callba
ck closure type
typealias EventHandler = (_ event: EventData) -> Void

// Class ref to store event handler
class ListenerRef {
    var eventHandlerRef: EventHandler?
    
    init(handler: @escaping EventHandler) {
        self.eventHandlerRef = handler
    }
    
    func unSubscribe() {
        print("unsubscribing")
        self.eventHandlerRef = nil
    }
}

// Publisher
struct Publisher {
    var listeners: [Event : [ListenerRef]] = [:]
    
    mutating func subscribe(event: Event,_ handler: @escaping EventHandler) -> ListenerRef {
        var eventListeners: [ListenerRef] = self.listeners[event] ?? []
        let listenerRef = ListenerRef(handler: handler)
        eventListeners.append(listenerRef)
        self.listeners[event] = eventListeners
        return listenerRef
    }
    
    func publish(event: Event, info: Any?) {
        // Get listener
        let eventListeners: [ListenerRef] = self.listeners[event] ?? []
        for listener in eventListeners {
            if let handler: EventHandler = listener.eventHandlerRef {
                handler((event, info))
            } else {
                print("subscription removed")
            }
        }
    }
}

// Global publisher obj
var globalPublisher = Publisher()

// Subscription to "hello" event
var subscription = globalPublisher.subscribe(event: "hello") { event in
    let eventString = event.event
    print("HelloEventHandler: Received event: \(eventString) |  info: \(event.info ?? "No Info")")
}

// Publishing "hello" event without data
globalPublisher.publish(event: "hello", info: nil)
// Publishing "hello" event with data
globalPublisher.publish(event: "hello", info: "world")

// Subscription to "test" event
var subscription2 = globalPublisher.subscribe(event: "test") { event in
    let eventString = event.event
    print("TestEventHandler: Received event: \(eventString) |  info: \(event.info ?? "No Info")")
}

// Publishing test event
globalPublisher.publish(event: "test", info: "my event")

// Unsubscribing: "hello event"
subscription.unSubscribe()

globalPublisher.publish(event: "hello", info: nil)
globalPublisher.publish(event: "test", info: "my event - New")

Challenge-7

Known issues and conclusion

  • Application is not tested in iOS device.

  • No Unit or UI tests are added

  • Due to lack of time some variable names are not perfect.

GitHub

点击跳转