Is it possible to disable the focus ring around a TextField in swiftUI for Mac?
I had that question as well, and after a couple hours of fiddling around, it seems like the answer is no. However, it is possible to wrap an NSTextField and get rid of the focus ring.
The following code has been tested in the latest release.
struct CustomTextField: NSViewRepresentable {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func makeNSView(context: Context) -> NSTextField {
let textField = NSTextField(string: text)
textField.delegate = context.coordinator
textField.isBordered = false
textField.backgroundColor = nil
textField.focusRingType = .none
return textField
}
func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = text
}
func makeCoordinator() -> Coordinator {
Coordinator { self.text = $0 }
}
final class Coordinator: NSObject, NSTextFieldDelegate {
var setter: (String) -> Void
init(_ setter: #escaping (String) -> Void) {
self.setter = setter
}
func controlTextDidChange(_ obj: Notification) {
if let textField = obj.object as? NSTextField {
setter(textField.stringValue)
}
}
}
}
As stated in an answer by Asperi to a similar question here, it's not possible (yet) to turn off the focus ring for a specific field using SwiftUI; however, the following workaround will disable the focus ring for all NSTextField instances in the app:
extension NSTextField {
open override var focusRingType: NSFocusRingType {
get { .none }
set { }
}
}
Not ideal, but it does provide one option that doesn't require stepping too far outside of SwiftUI.
I'm apparently designing a drag and drop dropbox which can either select files by clicking it or dragging and dropping the files on it, and I want the selected files to be visible in a table next to it. My design logic is that whenever the user selects files from an NSOpenPanel, it passes the selected file paths into the CoreData and then an array retrieves them one by one from the CoreData, and finally, update the NSTableView's content by using reloadData().
Basically, my problem is that whenever I try to call ViewController().getDroppedFiles() from DropboxButton class, I always get a Fatal error: unexpectedly found nil while unwrapping an optional value.
My ViewController.swift:
import Cocoa
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
getDroppedFiles()
}
#IBOutlet weak var DroppedFilesTableView: NSTableView!
var droppedFiles: [DroppedFiles] = [] // Core Data class definition: DroppedFiles
func numberOfRows(in tableView: NSTableView) -> Int {
return droppedFiles.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let droppedFilesCollection = droppedFiles[row]
if (tableView?.identifier)!.rawValue == "fileNameColumn" {
if let fileNameCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileNameCell")) as? NSTableCellView {
fileNameCell.textField?.stringValue = droppedFilesCollection.fileName!
return fileNameCell
}
} else if (tableView?.identifier)!.rawValue == "filePathColumn" {
if let filePathCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "filePathCell")) as? NSTableCellView {
filePathCell.textField?.stringValue = droppedFilesCollection.filePath!
return filePathCell
}
}
return nil
}
#IBAction func DropboxClicked(_ sender: NSButton) {
// selected file paths
for filePath in selectedFilePaths {
if let context = (NSApp.delegate as? AppDelegate)?.persistentContainer.viewContext {
let droppedFilesData = DroppedFiles(context: context)
droppedFilesData.fileName = getFileName(withPath: filePath)
droppedFilesData.filePath = filePath
do {
try context.save()
} catch {
print("Unable to save core data.")
}
}
getDroppedFiles()
}
}
func getDroppedFiles() {
if let context = (NSApp.delegate as? AppDelegate)?.persistentContainer.viewContext {
do {
try droppedFiles = context.fetch(DroppedFiles.fetchRequest())
} catch {
print("Unable to fetch core data.")
}
}
DroppedFilesTableView.reloadData() // Fatal Error: unexpectedly found nil while unwrapping an optional value (whenever I call this function in other class)
}
}
I'm using a push button (NSButton) as the dropbox (it has its own class), which can easily be clicked and also supports dragging options.
My DropboxButton.swift:
import Cocoa
class DropboxButton: NSButton {
required init?(coder: NSCoder) {
super.init(coder: coder)
registerForDraggedTypes([NSPasteboard.PasteboardType.URL, NSPasteboard.PasteboardType.fileURL])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
// some other codes
return .copy
}
override func draggingExited(_ sender: NSDraggingInfo?) {
// some other codes
}
override func draggingEnded(_ sender: NSDraggingInfo) {
// some other codes
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
guard let pasteboard = sender.draggingPasteboard.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "NSFilenamesPboardType")) as? NSArray,
let filePaths = pasteboard as? [String] else {
return false
}
for filePath in filePaths {
if let context = (NSApp.delegate as? AppDelegate)?.persistentContainer.viewContext {
let droppedFilesData = DroppedFiles(context: context)
droppedFilesData.fileName = getFileName(withPath: filePath)
droppedFilesData.filePath = filePath
do {
try context.save()
} catch {
print("Unable to save core data.")
}
}
ViewController().getDroppedFiles() // found nil with reloadData() in ViewController.swift
}
return true
}
}
And this is my interface and code logic:
So, how can I reloadData() for the table view in my ViewController class from another class (DropboxButton: NSButton) so that whenever the user drags and drops files into the dropbox, the table view will reload?
P.S. To get this done means a lot to me, I really need to get this fixed in a short time, is there anyone can spend some time and help me?
You need to call getDroppedFiles() on a loaded instance of ViewController.
With ViewController().getDroppedFiles() you're creating a new instance of ViewController that is not shown anywhere (so controls are not initialized resulting in the nil error).
I found this solution useful for my case.
I used observer to pass through data and call functions from other controller classes, now I understand that I was creating a new instance of ViewController which is not loaded. Here is my code:
ViewController.swift:
class ViewController: NSViewController {
// other codes
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(getDroppedFiles), name: NSNotification.Name(rawValue: "reloadTableViewData"), object: nil)
}
#objc func getDroppedFiles() {
DroppedFilesTableView.reloadData()
}
}
DropboxButton.swift:
class DropboxButton: NSButton {
// other codes
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
// other codes
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTableViewData"), object: nil)
return true
}
}
And now, everything works perfectly, I can even add an userInfo: to pass data between files and classes.
This is more or less my Main.storyboard situation:
where I have a root UITabBarController with 5 possibile choices. Then, I want that some UIViewControllers can rotate to landscape while I want also some other UIViewControllers to have only landscape mode. So I have written this file.swift:
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate {
override func shouldAutorotate() -> Bool {
if (self.topViewController?.isKindOfClass(HomeViewController) != nil) {return false}
else if (self.topViewController?.isKindOfClass(ServicesViewController) != nil) {return false}
else if (self.topViewController?.isKindOfClass(PhotoGalleryViewController) != nil) {return false}
else if (self.topViewController?.isKindOfClass(SelectMapViewController) != nil) {return false}
else if (self.topViewController?.isKindOfClass(MapViewController) != nil) {return false}
else {return true}
}
}
class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {
override func shouldAutorotate() -> Bool {
return (self.selectedViewController as! UINavigationController).shouldAutorotate()
}
}
and I have assigned to all UINavigationControllers the same class
CustomNavigationController while I have assigned CustomTabBarController class to UITabBarController.
The result is that no view controller do not rotates. Is this because I have assigned the same class to them? Shall I create a custom navigation controller class for each UINavigationController I have?
UPDATE
A partial solution I found, even if it's a little intricate, is the following. I have modified the previous file like that:
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate {
override func shouldAutorotate() -> Bool {
return (self.topViewController?.shouldAutorotate())!
}
}
class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {
override func shouldAutorotate() -> Bool {
return (self.selectedViewController as! UINavigationController).shouldAutorotate()
}
}
Then, in view controllers where rotation is allowed I simply have:
override func shouldAutorotate() -> Bool {
return true
}
while in view controllers where rotation is not allowed I have:
override func shouldAutorotate() -> Bool {
return false
}
override func viewWillAppear(animated: Bool) {
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
Anyway there's a little problem because the animation which sets mode to portrait is not correct meaning that the width of the screen is not adjusted. if I go from a landscape view controller to a portrait only view controller then the view controller frame is not correct. I get
instead of this:
Try this in AppDelegate
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootController = self.window?.rootViewController as? UITabBarController {
if let navigationController = rootController.selectedViewController as? UINavigationController {
let controller = navigationController.topViewController!
if controller.isKindOfClass(HomeViewController.classForCoder()) {
return UIInterfaceOrientationMask.Portrait
}
if controller.isKindOfClass(ServicesViewController.classForCoder()) {
return UIInterfaceOrientationMask.Portrait
}
if controller.isKindOfClass(PhotoGalleryViewController.classForCoder()) {
return UIInterfaceOrientationMask.Portrait
}
if controller.isKindOfClass(SelectMapViewController.classForCoder()) {
return UIInterfaceOrientationMask.Portrait
}
if controller.isKindOfClass(MapViewController.classForCoder()) {
return UIInterfaceOrientationMask.Portrait
}
}
}
return UIInterfaceOrientationMask.All
}
Update:
This method forces application to change orientation in ViewController that should be only in Portrait (HomeViewController, ServicesViewController, PhotoGalleryViewController, SelectMapViewController, MapViewController):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
In Swift, how can I build an area in a window of my Mac app where a user can drag-and-drop a folder onto this area, and have my app receive the path of the folder?
In principle, it seems to me that this is a similar concept to Apple's CocoaDragAndDrop example app. I've tried to work my way through understanding that source code, but the app is written in Objective-C and I've not successfully managed to replicate its functionality in the app I am building.
Thank you in advance.
In a custom NSView:
override init(frame frameRect: NSRect)
{
super.init(frame: frameRect)
registerForDraggedTypes([kUTTypeFileURL,kUTTypeImage])
}
override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation
{
println("dragging entered")
return NSDragOperation.Copy
}
The catch is, to get it working I had to put that custom view as a leaf and the last view in the storyboard
This worked for me (on a NSWindow subclass):
In awakeFromNib:
registerForDraggedTypes([NSFilenamesPboardType])
Then add the following operations (at least draggingEntered and performDragOperation) to the window (or view):
func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
let sourceDragMask = sender.draggingSourceOperationMask()
let pboard = sender.draggingPasteboard()!
if pboard.availableTypeFromArray([NSFilenamesPboardType]) == NSFilenamesPboardType {
if sourceDragMask.rawValue & NSDragOperation.Generic.rawValue != 0 {
return NSDragOperation.Generic
}
}
return NSDragOperation.None
}
func draggingUpdated(sender: NSDraggingInfo) -> NSDragOperation {
return NSDragOperation.Generic
}
func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
return true
}
func performDragOperation(sender: NSDraggingInfo) -> Bool {
// ... perform your magic
// return true/false depending on success
}
my 2 cents for OSX - swift 5 (and fixed for loading from XIBs/storybord.)
// Created by ing.conti on 31th jan 2020.
//
import Cocoa
class AnalysisView: NSView {
override init(frame frameRect: NSRect)
{
super.init(frame: frameRect)
self.registerMyTypes()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.registerMyTypes()
}
final private func registerMyTypes()
{
registerForDraggedTypes(
[NSPasteboard.PasteboardType.URL,
NSPasteboard.PasteboardType.fileURL,
NSPasteboard.PasteboardType.png,
NSPasteboard.PasteboardType.fileNameType(forPathExtension: "wtf")
])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
print("draggingEntered")
return NSDragOperation.copy
}
override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
let allow = true // check your types...
print("prepareForDragOperation")
return allow
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let pasteBoard = sender.draggingPasteboard
print("performDragOperation")
if let urls = pasteBoard.readObjects(forClasses: [NSURL.self]) as? [URL]{
// consume them...
print(urls)
return true
}
return false
}
}
How does one go about making a delegate, i.e. NSUserNotificationCenterDelegate in swift?
Here's a little help on delegates between two view controllers:
Step 1: Make a protocol in the UIViewController that you will be removing/will be sending the data.
protocol FooTwoViewControllerDelegate:class {
func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}
Step2: Declare the delegate in the sending class (i.e. UIViewcontroller)
class FooTwoViewController: UIViewController {
weak var delegate: FooTwoViewControllerDelegate?
[snip...]
}
Step3: Use the delegate in a class method to send the data to the receiving method, which is any method that adopts the protocol.
#IBAction func saveColor(_ sender: UIBarButtonItem) {
delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}
Step 4: Adopt the protocol in the receiving class
class ViewController: UIViewController, FooTwoViewControllerDelegate {
Step 5: Implement the delegate method
func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
colorLabel.text = "The Color is " + text
controller.navigationController.popViewController(animated: true)
}
Step 6: Set the delegate in the prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! FooTwoViewController
vc.colorString = colorLabel.text
vc.delegate = self
}
}
And that should work. This is of course just code fragments, but should give you the idea. For a long explanation of this code you can go over to my blog entry here:
segues and delegates
If you are interested in what's going on under the hood with a delegate I did write on that here:
under the hood with delegates
Delegates always confused me until I realized that a delegate is just a class that does some work for another class. It's like having someone else there to do all the dirty work for you that you don't want to do yourself.
I wrote a little story to illustrate this. Read it in a Playground if you like.
Once upon a time...
// MARK: Background to the story
// A protocol is like a list of rules that need to be followed.
protocol OlderSiblingDelegate: class {
// The following command (ie, method) must be obeyed by any
// underling (ie, delegate) of the older sibling.
func getYourNiceOlderSiblingAGlassOfWater()
}
// MARK: Characters in the story
class BossyBigBrother {
// I can make whichever little sibling is around at
// the time be my delegate (ie, slave)
weak var delegate: OlderSiblingDelegate?
func tellSomebodyToGetMeSomeWater() {
// The delegate is optional because even though
// I'm thirsty, there might not be anyone nearby
// that I can boss around.
delegate?.getYourNiceOlderSiblingAGlassOfWater()
}
}
// Poor little sisters have to follow (or at least acknowledge)
// their older sibling's rules (ie, protocol)
class PoorLittleSister: OlderSiblingDelegate {
func getYourNiceOlderSiblingAGlassOfWater() {
// Little sis follows the letter of the law (ie, protocol),
// but no one said exactly how she had to respond.
print("Go get it yourself!")
}
}
// MARK: The Story
// Big bro is laying on the couch watching basketball on TV.
let bigBro = BossyBigBrother()
// He has a little sister named Sally.
let sally = PoorLittleSister()
// Sally walks into the room. How convenient! Now big bro
// has someone there to boss around.
bigBro.delegate = sally
// So he tells her to get him some water.
bigBro.tellSomebodyToGetMeSomeWater()
// Unfortunately no one lived happily ever after...
// The end.
In review, there are three key parts to making and using the delegate pattern.
the protocol that defines what the worker needs to do
the boss class that has a delegate variable, which it uses to tell the worker class what to do
the worker class that adopts the protocol and does what is required
Real life
In comparison to our Bossy Big Brother story above, delegates are often used for the following practical applications:
Communication: one class needs to send some information to another class.
Code example 1: sending data from one view controller to another
Code example 2: sending text input from a custom keyboard to a text field
Customization: one class wants to allow another class to customize it.
The great part is that these classes don't need to know anything about each other beforehand except that the delegate class conforms to the required protocol.
I highly recommend reading the following two articles. They helped me understand delegates even better than the documentation did.
What is Delegation? – A Swift Developer’s Guide
How Delegation Works – A Swift Developer’s Guide
One more note
Delegates that reference other classes that they do not own should use the weak keyword to avoid strong reference cycles. See this answer for more details.
It is not that different from obj-c.
First, you have to specify the protocol in your class declaration, like following:
class MyClass: NSUserNotificationCenterDelegate
The implementation will look like following:
// NSUserNotificationCenterDelegate implementation
func userNotificationCenter(center: NSUserNotificationCenter, didDeliverNotification notification: NSUserNotification) {
//implementation
}
func userNotificationCenter(center: NSUserNotificationCenter, didActivateNotification notification: NSUserNotification) {
//implementation
}
func userNotificationCenter(center: NSUserNotificationCenter, shouldPresentNotification notification: NSUserNotification) -> Bool {
//implementation
return true
}
Of course, you have to set the delegate. For example:
NSUserNotificationCenter.defaultUserNotificationCenter().delegate = self;
I got few corrections to post of #MakeAppPie
First at all when you are creating delegate protocol it should conform to Class protocol. Like in example below.
protocol ProtocolDelegate: class {
func myMethod(controller:ViewController, text:String)
}
Second, your delegate should be weak to avoid retain cycle.
class ViewController: UIViewController {
weak var delegate: ProtocolDelegate?
}
Last, you're safe because your protocol is an optional value. That means its "nil" message will be not send to this property. It's similar to conditional statement with respondToselector in objC but here you have everything in one line:
if ([self.delegate respondsToSelector:#selector(myMethod:text:)]) {
[self.delegate myMethod:self text:#"you Text"];
}
Above you have an obj-C example and below you have Swift example of how it looks.
delegate?.myMethod(self, text:"your Text")
Here's a gist I put together. I was wondering the same and this helped improve my understanding. Open this up in an Xcode Playground to see what's going on.
protocol YelpRequestDelegate {
func getYelpData() -> AnyObject
func processYelpData(data: NSData) -> NSData
}
class YelpAPI {
var delegate: YelpRequestDelegate?
func getData() {
println("data being retrieved...")
let data: AnyObject? = delegate?.getYelpData()
}
func processYelpData(data: NSData) {
println("data being processed...")
let data = delegate?.processYelpData(data)
}
}
class Controller: YelpRequestDelegate {
init() {
var yelpAPI = YelpAPI()
yelpAPI.delegate = self
yelpAPI.getData()
}
func getYelpData() -> AnyObject {
println("getYelpData called")
return NSData()
}
func processYelpData(data: NSData) -> NSData {
println("processYelpData called")
return NSData()
}
}
var controller = Controller()
DELEGATES IN SWIFT 2
I am explaining with example of Delegate with two viewControllers.In this case, SecondVC Object is sending data back to first View Controller.
Class with Protocol Declaration
protocol getDataDelegate {
func getDataFromAnotherVC(temp: String)
}
import UIKit
class SecondVC: UIViewController {
var delegateCustom : getDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backToMainVC(sender: AnyObject) {
//calling method defined in first View Controller with Object
self.delegateCustom?.getDataFromAnotherVC(temp: "I am sending data from second controller to first view controller.Its my first delegate example. I am done with custom delegates.")
self.navigationController?.popViewControllerAnimated(true)
}
}
In First ViewController Protocol conforming is done here:
class ViewController: UIViewController, getDataDelegate
Protocol method definition in First View Controller(ViewController)
func getDataFromAnotherVC(temp : String)
{
// dataString from SecondVC
lblForData.text = dataString
}
During push the SecondVC from First View Controller (ViewController)
let objectPush = SecondVC()
objectPush.delegateCustom = self
self.navigationController.pushViewController(objectPush, animated: true)
First class:
protocol NetworkServiceDelegate: class {
func didCompleteRequest(result: String)
}
class NetworkService: NSObject {
weak var delegate: NetworkServiceDelegate?
func fetchDataFromURL(url : String) {
delegate?.didCompleteRequest(result: url)
}
}
Second class:
class ViewController: UIViewController, NetworkServiceDelegate {
let network = NetworkService()
override func viewDidLoad() {
super.viewDidLoad()
network.delegate = self
network.fetchDataFromURL(url: "Success!")
}
func didCompleteRequest(result: String) {
print(result)
}
}
Very easy step by step (100% working and tested)
step1: Create method on first view controller
func updateProcessStatus(isCompleted : Bool){
if isCompleted{
self.labelStatus.text = "Process is completed"
}else{
self.labelStatus.text = "Process is in progress"
}
}
step2: Set delegate while push to second view controller
#IBAction func buttonAction(_ sender: Any) {
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
secondViewController.delegate = self
self.navigationController?.pushViewController(secondViewController, animated: true)
}
step3: set delegate like
class ViewController: UIViewController,ProcessStatusDelegate {
step4: Create protocol
protocol ProcessStatusDelegate:NSObjectProtocol{
func updateProcessStatus(isCompleted : Bool)
}
step5: take a variable
var delegate:ProcessStatusDelegate?
step6: While go back to previous view controller call delegate method so first view controller notify with data
#IBAction func buttonActionBack(_ sender: Any) {
delegate?.updateProcessStatus(isCompleted: true)
self.navigationController?.popViewController(animated: true)
}
#IBAction func buttonProgress(_ sender: Any) {
delegate?.updateProcessStatus(isCompleted: false)
self.navigationController?.popViewController(animated: true)
}
Simple Example:
protocol Work: class {
func doSomething()
}
class Manager {
weak var delegate: Work?
func passAlong() {
delegate?.doSomething()
}
}
class Employee: Work {
func doSomething() {
print("Working on it")
}
}
let manager = Manager()
let developer = Employee()
manager.delegate = developer
manager.passAlong() // PRINTS: Working on it
Delegates are a design pattern that allows one object to send messages to another object when a specific event happens.
Imagine an object A calls an object B to perform an action. Once the action is complete, object A should know that B has completed the task and take necessary action, this can be achieved with the help of delegates!
Here is a tutorial implementing delegates step by step in swift 3
Tutorial Link
Here is real life delegate scenario
Lets make our own UITextField and UITextFieldDelegate
// THE MYSTERIOUS UITEXTFIELD
protocol UITextFieldDelegate {
func textFieldDidChange(_ textField: UITextField) -> Void
}
class UITextField {
var delegate: UITextFieldDelegate?
private var mText: String?
var text: String? {
get {
return mText
}
}
init(text: String) {
}
init() {
}
func setText(_ text: String) {
mText = text
delegate?.textFieldDidChange(self)
}
}
// HERE IS MY APP
class Main {
let textfield = UITextField()
func viewDidLoad() {
print("viewDidLoad")
textfield.delegate = self
textfield.setText("Hello")
}
}
extension Main: UITextFieldDelegate {
func textFieldDidChange(_ textField: UITextField) {
print(textField.text ?? "No string")
}
}
let main = Main()
main.viewDidLoad()
Here Simple Code Example of Delegate:
//MARK: - Protocol ShowResult
protocol ShowResult: AnyObject {
func show(value: Int)
}
//MARK: - MyOperation Class
class MyOperation {
weak var delegate: ShowResult?
func sum(fNumber: Int, sNumber: Int) {
delegate?.show(value: fNumber + sNumber)
}
}
//MARK: - ViewController Class
class ViewController: UIViewController,ShowResult {
var myOperation: MyOperation?
override func viewDidLoad() {
super.viewDidLoad()
loadMyOperation()
myOperation?.delegate = self
myOperation?.sum(fNumber: 100, sNumber: 20)
}
private func loadMyOperation() {
if myOperation == nil {
myOperation = MyOperation()
}
}
func show(value: Int) {
print("value: \(value)")
}
}
The solutions above seemed a little coupled and at the same time avoid reuse the same protocol in other controllers, that's why I've come with the solution that is more strong typed using generic type-erasure.
#noreturn public func notImplemented(){
fatalError("not implemented yet")
}
public protocol DataChangedProtocol: class{
typealias DataType
func onChange(t:DataType)
}
class AbstractDataChangedWrapper<DataType> : DataChangedProtocol{
func onChange(t: DataType) {
notImplemented()
}
}
class AnyDataChangedWrapper<T: DataChangedProtocol> : AbstractDataChangedWrapper<T.DataType>{
var base: T
init(_ base: T ){
self.base = base
}
override func onChange(t: T.DataType) {
base.onChange(t)
}
}
class AnyDataChangedProtocol<DataType> : DataChangedProtocol{
var base: AbstractDataChangedWrapper<DataType>
init<S: DataChangedProtocol where S.DataType == DataType>(_ s: S){
self.base = AnyDataChangedWrapper(s)
}
func onChange(t: DataType) {
base.onChange(t)
}
}
class Source : DataChangedProtocol {
func onChange(data: String) {
print( "got new value \(data)" )
}
}
class Target {
var delegate: AnyDataChangedProtocol<String>?
func reportChange(data:String ){
delegate?.onChange(data)
}
}
var source = Source()
var target = Target()
target.delegate = AnyDataChangedProtocol(source)
target.reportChange("newValue")
output: got new value newValue
In swift 4.0
Create a delegate on class that need to send some data or provide some functionality to other classes
Like
protocol GetGameStatus {
var score: score { get }
func getPlayerDetails()
}
After that in the class that going to confirm to this delegate
class SnakesAndLadders: GetGameStatus {
func getPlayerDetails() {
}
}
In swift 5
I am a beginner, I think this is easiest way to understand in practical scenario
Note:Any improvisations are most appreciated
protocol APIService {
func onSuccessResponse() -> AnyObject
func onFailureResponse() -> AnyObject
}
class APIHelper{
var delegate : APIService?
func postUsersDataAPI() {
//assuming API communication is success
if(success){
let _: AnyObject? = delegate?.onSuccessResponse()
}else if(failure){
let _: AnyObject? = delegate?.onFailureResponse()
}
}
func getAllUsersAPI() {
//assuming API communication is success
if(success){
let _: AnyObject? = delegate?.onSuccessResponse()
}else if(failure){
let _: AnyObject? = delegate?.onFailureResponse()
}
}
}
class ViewController:UIViewController,APIService {
func onSuccessResponse() -> AnyObject {
print("onSuccessResponse") as AnyObject
}
func onFailureResponse() -> AnyObject {
print("onFailureResponse") as AnyObject
}
#IBAction func clickBtnToPostUserData(_ sender: Any) {
let apiHelper = APIHelper()
apiHelper.delegate = self
apiHelper.postAPI()
}