passing in interfaces in Swift to configuring view methods - swift

In iOS examples, I'll often see setting the object on a UITableViewCell subclass and then that subclass knows how to configure the view.
Pseudo-code
class MyCustomObject {
var name = ""
}
class MyCell : UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
func setMyCustomObject(obj: MyCustomObject) {
nameLabel = obj.name
}
}
I've been looking at some TypeScript/Angular code as well these days and I've seen patterns where they'll create an interface for the object.
more pseudo-code
interface ICustomInterface {
name: String
}
class MyCustomObject : ICustomInterface {
...
}
Then you could do something in your view code just passing in the interface instead of the actual object.
setObjectForView(obj: ICustomInterface) {
nameLabel = obj.name
}
Is this something that can be done in Swift to be more flexible down the line? Or is it not really necessary? When I try to do something like this in Swift, I get linker errors I do not understand so I'm wondering if this paradigm is even a good fit for Swift or not.

In Swift, that's a protocol. It's very powerful for certain problems, but it's not something worth applying to every cell.
If you do this, the better name would be Nameable since it's something you can name (like Sliceable, Printable, Comparable, etc). Leading prefixes like I is not good Swift style. It's hard to imagine it being worth the trouble for this particular example, though.

Related

form of initialisation swift

I have seen the following form of initialisation being used in some projects that I have worked on:

class MyClass: UIViewController {
private let myView: MyView!
func initMyView() {
let tempView = MyView()
tempView.someIntProperty = 5
myView = tempView
}
}
I wanted to know if there’s any particular advantage of using such type of initialisation? The only benefit I can see is readability and protecting my class property from mutation. The copy made is a shallow copy so performance isn’t an issue either. Is there any other benefit to using such a way of initialisation?

How to iterate through subviews in Swift for macOS and return object types

After finding this question and answers, I thought I would try to reduce the size of my code considerably by looping through various objects in the view and set parameters.
I cannot use the tag value because I do not set tags, except in rare circumstances. I set plain language identifiers for all of my UI elements. I will use NSTextField as an example to try and set the delegate for each NSTextField in the view.
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet var myTextField1: NSTextField!
// ... and many, many more NSTextFields ...
override func viewDidLoad {
super.viewDidLoad()
let textFields = self.view.subviews.filter { $0.isKind(of: NSTextField.self) }
for textField in textFields {
if textField.identifier!.rawValue[textField.identifier!.rawValue.startIndex] != "_" { // Avoiding Swift assigned identifiers
textField.delegate = self
}
}
}
}
I am being told that Value of type 'NSView' has no member 'delegate', which makes sense because the NSView of the NSTextField is what is actually being placed into the list, not the actual NSTextField object.
Since IBOutlet Collections aren't available for macOS, I can't simply iterate through a collection to do what I want to do. At least as a far as I know of.
I assume the goal is to get textFields to have type [NSTextField].
Right now, you have filter with is (which doesn't change type)
let textFields = self.view.subviews.filter { $0.isKind(of: NSTextField.self) }
You should change this to compactMap with as? (which does change type).
let textFields = self.view.subviews.compactMap { $0 as? NSTextField }

Using Custom UIKit Classes in SwiftUI

I have created my own custom classes for UIKit objects. now i want to use same classes in SwiftUI, How can i achieve that and how much effort it will take.
Also if i want i will need to write same classes in swift UI.
example, I have custom UILable subclass WMLabel.
class WMLabel: UILabel {
var myproperty: String = "" {
didSet {
self.text = myproperty
}
}
}
so how can i use WMLabel in swiftUI?
I have tried ObserverableObject and UIViewRepresentable, but not able to access the properties.
You can definitely use your UIKit classes. To get basic access to the properties, you'll want to be looking at makeUIView, which occurs when the view is first created and updateUIVew.
Using your example code:
class WMLabel: UILabel {
var myproperty: String = "" {
didSet {
self.text = myproperty
}
}
}
struct WMLabelRepresented: UIViewRepresentable {
var text : String
func makeUIView(context: Context) -> WMLabel {
return WMLabel()
}
func updateUIView(_ uiView: WMLabel, context: Context) {
uiView.myproperty = text
}
}
struct ContentView : View {
var body: some View {
WMLabelRepresented(text: "My text")
}
}
If there are things that can't be expressed declaratively, you'll want to look into coordinators and as you mentioned, possible an ObservableObject to communicate data imperatively to your view, but often you can find ways to express most things declaratively.
If you want an example of more complex imperative communication, here's a couple of links to another answers of mine:
https://stackoverflow.com/a/65926143/560942
https://stackoverflow.com/a/66845387/560942
Converting all of your custom classes and interfacing with them is going to be one heck of a chore if you have a few of them. You would end up using something called UIViewRepresentable which requires quite a few things, the most annoying of which called a coordinator. You'd almost be better off rewriting your classes into a SwiftUI version. Here's Apple's documentation on interfacing SwiftUI with UIKit: https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
Here's an example of conversion of that UILabel into SwiftUI, with accessible properties.
Example Conversion
struct WMLabel: View {
var myProperty: String
var body: some View {
Text(myProperty)
}
}
Example Usage
struct Example: View {
var body: some View {
WMLabel(myProperty: "Hello World!")
}
}
As you can see, there is little code involved in converting something to SwiftUI, if you start getting involved in UIViewRepresentable you have to start playing with coordinators and a bunch of other interfacing methods just to make it work. Sometimes it's required, but in most cases I'd try and avoid it.

Making ViewController slimmer by moving TableView away

I am quite new into programming and facing some issues while trying to slim down my ViewController by moving creation of the tableView and associated views to the separate class and moving delegates and datasource from VC to separate one.
My constellation of current files and work is as follows:
After network connection, when data are received, I am calling inside a closure class to set view which will have embedded UITableView.
Fetched data are being saved into the CoreData stack
I instantiate view from another class
var detailView: DetailView! { return self.view as? DetailView }
Once I will download first part of the UI (separate call which works fine)
I am moving onto the part which is messy and surpass my abilities
I call a function createReposCard()
dispatchGroup.notify(queue: dispatchQueue) {
DispatchQueue.main.async {
// slide animation
UIView.animate(withDuration: 0.75, delay: 0.5, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.0, options: [], animations: {
self.detailView.reposCard.center = CGPoint(x: self.detailView.reposCard.center.x, y: self.detailView.reposCard.center.y-UIScreen.main.bounds.height)
}, completion: nil)
self.detailView.createReposCard(for: self.repos)
self.detailView.detailsTableView.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.removeFromSuperview()
}
}
Code is incomplete to depict the problem only, but what it does one by one.
I am passing info about selected user (irrelevant info)
then I am making a reference to my DetailsViewController which still e.g. holds CoreData stack
In initializer I am instantiating detailsViewController and passing it onto the class which holds delegates (I am passing it to have there reference to the CoreData)
class DetailView: UIView {
var selectedUser: User?
var detailsViewController: DetailsViewController!
let detailsTableView: UITableView = {
let tableView = UITableView()
tableView.frame = CGRect.zero
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reposCell")
return tableView
}()
init(selectedUser:User, frame: CGRect) {
super.init(frame: frame)
self.selectedUser = selectedUser
detailsViewController = DetailsViewController()
let tableViewDelegates = TableViewDelegates(detailsViewController: detailsViewController)
detailsTableView.delegate = tableViewDelegates
detailsTableView.dataSource = tableViewDelegates
}
And finally code jumps into the depicted class where I am knocked down by "Unexpectedly found nil while implicitly unwrapping..."
public class TableViewDelegates: NSObject, UITableViewDataSource,UITableViewDelegate {
private let detailsViewController: DetailsViewController
init(detailsViewController: DetailsViewController){
self.detailsViewController = detailsViewController
super.init()
}
public func numberOfSections(in tableView: UITableView) -> Int {
return detailsViewController.fetchedResultsController.sections?.count ?? 1
}
...
Actually I don't know if my concept is good as I feel quite lost, but my intentions were as follows:
Move View creation to other class
Move TableView delegates to other class
Finally - move networking and CoreData to other class.
But as I see, simple data passing is overcoming my abilities.
I think this question can be divided into two parts:
1) Why is my variable nil when I unwrap it?
I don't think we have enough information to answer this accurately, but my overall approach would be like this:
Check what nil variable is being unwrapped;
Make sure this variable is being properly initialized;
Make sure that the object is not being incorrectly deinitialized;
If you're using Storyboard, use the inspectors to check if everything is set correctly.
There's a particular observation about step 2: you should check the order of execution of your methods to make sure that the variable is properly initialized. Why am I emphasizing this? Because there's a chance that some view (e.g., detailView) is initialized like an ordinary UIView, and then you try to access an element that is not part of a UIView object (e.g., a table view). In other words, check if you're setting the custom views before you try to access them.
2) How to structure the project in a more organized way?
This is a more interesting question, and I think that choosing a better approach will help you to avoid issues like what you're experiencing. I will divide this into some topics. Everything here is my personal opinion and doesn't necessarily reflect the best approach, especially because "best" is subjective here.
PersistenceManager class
First, passing a reference of a view controller to another class just to access CoreData doesn't seem like a good option. A better approach would be to have a PersistenceManager class, for example. You could use an object of this class to fetch and save data. You could pass this object instead of the view controller.
In some architectures (e.g., VIPER), it wouldn't be correct for the view controller to access the persistence directly, so it would be more appropriate to pass an array of already fetched objects. For example:
class TableViewController {
private let tableView: UITableView!
private var currentlyDisplayedUsers: [Users]?
func displayUsers(_ users: [Users]) {
self.currentlyDisplayedUsers = users
self.tableView.reloadData()
}
}
In the example above, the tableView would display currentlyDisplayedUsers, which would be updated by the method displayUsers, which would be called by someone else, like a Presenter.
Network class
Second, I think you should have a network class to download data from the internet. You would use instances of the Network class in the application logic. In other words, you would have something like
// This is in the application logic
// In MVC, the logic is in the Controller, while in VIPER the logic is in the Interactor
class ApplicationLogic {
let networkAPI: Network?
...
func fetchUserData() {
networkAPI?.fetchUsers() { data, error in
// Update the UI based on the response
// Using the previous example, you could call displayUsers here
}
}
}
TableView, TableViewDelegate, and TableViewDataSource
Finally, how to organize these guys. UITableViewDelegate is responsible for telling us about events in the table, while UITableViewDataSource is responsible for filling the table with data, which means that both are strongly related to the table view itself. That said, imho, both should be implemented in different swift files, but as extensions of the view controller that has a reference to the table view. Something like
// TableViewController+UITableViewDelegate.swift
extension TableViewController: UITableViewDelegate {
...
}
// TableViewController+UITableViewDataSource.swift
extension TableViewController: UITableViewDataSource {
...
}
Using this approach, the delegate and the data source would have access to the users array, mentioned earlier. Here is an example of how to implement a similar approach using VIPER.
So, I hope I could provide a basic idea on these topics. If you want to understand more about how to structure your code, I suggest researching iOS design patterns and architectural patterns. Some architectural design patterns that are famous in iOS development are MVC, MVP, MVVM, and VIPER.

Swift: property that is a UIView subclass implementing a protocol

Here's the scenario. I have a number of different views I want to show, depending on the model object I'm showing the user. So I've set up a protocol whereby any view that implements it can be presented.
class MyItem { /* some model properties */ }
protocol ItemView: class {
// some protocol methods (e.g. updateWithItem(), etc)
func setupItem(item: MyItem)
}
class SpecificItemView: UIView, ItemView { /* there will be multiple classes like this */
func setupItem(item: MyItem) {
// do item setup
}
}
class AnotherItemView: UIView, ItemView {
func setupItem(item: MyItem) {
// do DIFFERENT item setup
}
}
Then when I go to use them in a view controller, I've been given one of my ItemView classes:
class MyViewController: UIViewController {
var itemView: ItemView? // could be a SpecificItemView or AnotherItemView
override func viewDidLoad() {
itemView?.setupItem(MyItem())
itemView?.removeFromSuperview() /* this line won't compile */
}
}
Everything works except that last line, where I try to call up to a UIView method (removeFromSuperview). No surprise there, since ItemView has no relation to UIView.
In Objective C I would solve this issue by specifying my itemView ivar like so:
#property (nonatomic, strong) UIView<ItemView> *itemView;
But I can't seem to find a similar syntax for Swift. I suspect there is no way to use this pattern in Swift. How do I achieve my overall goal of interchangeable UIView classes in a Swift-friendly manner?
One hacky solution I've found so far, is to add any UIView methods I call (e.g. removeFromSuperview) to my ItemView protocol.
Another suggestion I got, not on SO, from Maurice Kelly, was to make a UIView subclass, implementing the ItemView protocol, that both SpecificItemView and AnotherItemView can descend from. You can see it implemented in this gist. While that solves the issue of encapsulating class AND protocol in a single type (e.g. var itemView: ItemViewParentClass) it basically makes the protocol pointless, since you're now implementing all the protocol's methods in your parent class and overriding them in your subclasses. The single biggest drawback of this solutions is that you have to cast instances of subclasses (SpecificItemView) to the new hypthetical parent class (ItemViewParentClass) when you refer to them in your View Controller.
As of Swift 5, this is possible by constraining your protocol to only be applied to UIViews. To do this, just do the following:
protocol ItemView: UIView {
func setupItem(item: MyItem)
}
By adding this constraint to your protocol, any object that the swift compiler recognizes as an ItemView will also have all UIView methods available on it.
Similarly, if a non-UIView subclass attempts to implement the ItemView protocol, your code will not compile.
As you're discovering, there's no way to specify both a class and a protocol for an object.
The easiest way to fix the problem is to add removeFromSuperview (and any other UIView methods you need to insure are present) to your protocol.
Perhaps the following (in Swift 4) would do the trick?
class MyViewController: UIViewController {
typealias T = UIView & ItemView
var itemView: T?
}
ItemView does not extend UIView, but your SpecificItemView does. Naturally, no removeFromSuperView exists on your ItemView.
In your ViewController you declare an ItemView type object, rather than a SpecificItemView
Change your MyViewController code to:
var itemView:SpecificItemView?