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 Closure
– Challenge – 1 -
API call with Async
– Challenge – 1 -
UIKit Color Selector
– Challenge – 2 -
SwiftUI Color Selector
– Challenge – 2 -
UIKit IB Logo
– Challenge – 5 -
UIKit Logo
– Challenge – 5 -
SwiftUI Logo
– Challenge – 5 -
Logo View Segue
– Challenge – 6 -
Swift UI Logo VC
– Challenge – 6 -
Swift UI Logo Modal
– Challenge – 6
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.swift
ViewController.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.swift
ViewController.swift
UIKit Color Selection
@IBAction func showUIKitColorSelector(_ sender: Any) {}
- SwiftUI
SwiftUI version of code example is implemented in and user can view this screen through / buttonColorSelectSwiftUIView.swift
ViewController.swift
SwiftUI Color Select
@IBAction func showSwiftUIColorSelectView(_ sender: Any) {}
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() }
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.swift
Main.storyboard
UIKit IB Logo
ViewController.swift
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()
}
SwiftUI
The SwiftUI version is implemented in and view is presented from button action of LogoSwiftView.swift
SwiftUI Logo
ViewController.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()
}
}
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.UIHostingController
Hosting
-
For view, we created a programmatic modal presentation with with dismiss action (No Interface builder or Storyboard).
SwiftUI
LogoSwiftViewModal.swift
UIKit Version
In the on touch up of action, the previously created controller is presented programmatically.ViewController.swift
Logo View Segue
LogoUIKViewController.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.swift
SwiftUI Logo VC
LogoSwiftUIViewController.swift
@IBAction func showLogoSwiftVC(_ sender: Any) {
self.performSegue(withIdentifier: "showSwiftUILogoVC", sender: self)
}
and is presenting in modal transition.SwiftUI Logo Modal
LogoSwiftViewModal.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")
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.