I have a data model for a UITableViewCell that looks like this:
class SettingsContentRow {
var title: String
var cellType: Type // How do i do this?
var action:((sender: UITableViewCell.Type) -> ())?
var identifier: String {
get { return NSStringFromClass(cellType) }
}
init(title: String, cellType: Type) {
self.title = title
self.cellType= cellType
}
}
The idea is to put these in an array to facilitate building a settings view using a UITableViewController, and when requesting a cell i can just query the model for both the identifier and the cell Type. But i cannot figure out what keyword to use instead of Type. I have tried Type, AnyClass, UITableViewCell.Type and they all give rise to type assignment errors when i try to instantiate the model class.
The syntax you want is UITableViewCell.Type. This is the type of something that is a subclass of UITableViewCell. You can accept the type of any class using AnyClass, but you should usually avoid that. Most of the time, if you think you want AnyClass, you really want a generic.
When you try to pass your type to this init, it'll be something like:
SettingsContentRow("title", cellType: MyCell.self)
Referring to types directly is a little uncommon, so Swift requires that you be explicit by adding .self to it.
You may in fact want a generic here anyway. I'd probably write it this way:
final class SettingsContentRow<Cell: UITableViewCell> {
typealias Action = (Cell) -> ()
let title: String
let action: Action?
var identifier: String {
get { return NSStringFromClass(Cell.self) }
}
init(title: String, action: Action?) {
self.title = title
self.action = action
}
}
class MyCell: UITableViewCell {}
let row = SettingsContentRow(title: "Title", action: { (sender: MyCell) in } )
Related
My code is setup in the following way:
class MyClass {
let text: String
init(text: String) {
self.text = text
}
}
var items = [MyClass]()
let item = MyClass(text: "sometext")
The idea is to get item into the items array, such as with:
items.append(item)
The main problem with this is that I initialise these items in a swift file, and I would get the error
Expressions are not allowed at the top level
Also, I'd prefer not to use this somewhere such as viewDidLoad() because there may be too many MyClass items.
I've tried this
class MyClass {
let text: String
static var items = [MyClass]()
init(text: String) {
self.text = text
MyClass.items.append(self)
}
}
as suggested here
However, after intialising several MyClass objects, items is still empty.
Is there any way I can do this?
EDIT: thank you for the suggestions so far. These were typos (using item rather than self, and items, rather than MyClass.items. This is how I've used it in my code. It compiles & still doesn't work, sadly.
Your code does not compile. This is the error-free code for the class.
class MyClass {
let text: String
static var items = [MyClass]()
init(text: String) {
self.text = text
MyClass.items.append(self)
}
}
The two things that were wrong in your code.
You should be appending self to items, because self is the instance of Item that you create which is available inside init.
static methods should be accessed with their class name.
Example:
let blah = MyClass(text: "blah")
let bleh = MyClass(text: "bleh")
print(MyClass.items.count) // Prints 2
I'm a swift beginner, so be gentle...
I'm having trouble assigning a function as a parameter.
I have defined this struct:
struct dispatchItem {
let description: String
let f: ()->Void
init(description: String, f: #escaping ()->()) {
self.description = description
self.f = f
}
}
I make use of this within a class called MasterDispatchController like so:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: testStaticTable),
dispatchItem(description: "Editable Table", f: testEditableTable)
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
etc.
Then I have a table view in my code that dispatches out to whichever function was clicked on (there are more than just the two I showed in the code above, but that's unimportant) like so
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f()
}
So... The compiler is not happy with this. It says when I am defining the dispatchItems let statement:
Cannot convert value of type '(MasterDispatchController) -> () -> ()' to expected argument type '() -> ()'
I figure... ok... I'm not sure I exactly understand this, but it seems like the compiler wants to know what class the callback functions are going to come from. I can see why it might need that. So I kind of blindly follow the pattern the compiler gives me, and change my struct to:
struct dispatchItem {
let description: String
let f: (MasterDispatchController)->()->Void
init(description: String, f: #escaping (MasterDispatchController)->()->()) {
self.description = description
self.f = f
}
}
Great the compiler is happy there, but now when I try to call the function with dispatchItems[indexPath.row].f() it says:
Missing parameter #1 in call
The function has no parameters, so I got confused...
I thought maybe it was asking me for the instance of the object in question which would make some sense... that would be "self" in my example, so I tried dispatchItems[indexPath.row].f(self) but then I got an error:
Expression resolves to an unused function
So I'm kind of stuck.
Sorry if this is a stupid question. Thanks for your help.
The problem is that you're trying to refer to the instance methods testStaticTable and testEditableTable in your instance property's initialiser before self is fully initialised. Therefore the compiler cannot partially apply these methods with self as the implicit parameter, but can instead only offer you the curried versions – of type (MasterDispatchController) -> () -> ().
One might be tempted then to mark the dispatchItems property as lazy, so that the property initialiser runs on the first access of the property, when self is fully initialised.
class MasterDispatchController : UITableViewController {
lazy private(set) var dispatchItems: [DispatchItem] = [
DispatchItem(description: "Static Table", f: self.testStaticTable),
DispatchItem(description: "Editable Table", f: self.testEditableTable)
]
// ...
}
(Note that I renamed your struct to conform to Swift naming conventions)
This now compiles, as you now can refer to the partially applied versions of the methods (i.e of type () -> Void), and can call them as:
dispatchItems[indexPath.row].f()
However, you now have a retain cycle, because you're storing closures on self which are strongly capturing self. This is because when used as a value, self.someInstanceMethod resolves to a partially applied closure that strongly captures self.
One solution to this, which you were already close to achieving, is to instead work with the curried versions of the methods – which don't strongly capture self, but instead have to be applied with a given instance to operate on.
struct DispatchItem<Target> {
let description: String
let f: (Target) -> () -> Void
init(description: String, f: #escaping (Target) -> () -> Void) {
self.description = description
self.f = f
}
}
class MasterDispatchController : UITableViewController {
let dispatchItems = [
DispatchItem(description: "Static Table", f: testStaticTable),
DispatchItem(description: "Editable Table", f: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f(self)()
}
func testEditableTable() {}
func testStaticTable() {}
}
These functions now take a given instance of MasterDispatchController as a parameter, and give you back the correct instance method to call for that given instance. Therefore, you need to first apply them with self, by saying f(self) in order to get the instance method to call, and then call the resultant function with ().
Although it may be inconvenient constantly applying these functions with self (or you may not even have access to self). A more general solution would be to store self as a weak property on DispatchItem, along with the curried function – then you can apply it 'on-demand':
struct DispatchItem<Target : AnyObject> {
let description: String
private let _action: (Target) -> () -> Void
weak var target: Target?
init(description: String, target: Target, action: #escaping (Target) -> () -> Void) {
self.description = description
self._action = action
}
func action() {
// if we still have a reference to the target (it hasn't been deallocated),
// get the reference, and pass it into _action, giving us the instance
// method to call, which we then do with ().
if let target = target {
_action(target)()
}
}
}
class MasterDispatchController : UITableViewController {
// note that we've made the property lazy again so we can access 'self' when
// the property is first accessed, after it has been fully initialised.
lazy private(set) var dispatchItems: [DispatchItem<MasterDispatchController>] = [
DispatchItem(description: "Static Table", target: self, action: testStaticTable),
DispatchItem(description: "Editable Table", target: self, action: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].action()
}
func testEditableTable() {}
func testStaticTable() {}
}
This ensures that you have no retain cycles, as DispatchItem doesn't have a strong reference to self.
Of course, you may be able to use unowned references to self, such as shown in this Q&A. However, you should only do so if you can guarantee that your DispatchItem instances don't outlive self (you would want to make dispatchItems a private property for one).
The problem here is that Swift treats class methods and functions differently under the hood. Class methods get a hidden self parameter (similar to how it works in Python), which allows them to know on which class instance they were called. That's why even though you declared testEditableCode as () -> (), the actual function has type (MasterDispatchController) -> () -> (). It needs to know on which object instance it was called.
The correct way to do what you're trying to do would be to create a closure which calls the correct method, like such:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: {() in
self.testStaticTable()
}),
dispatchItem(description: "Editable Table", f: {() in
self.testEditableTable()
})
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
If you're familiar with JavaScript, the {() in ...code...} notation is the same as function() { ...code... } or the same as lambda: ...code... in Python.
Here is the way to achieve what you want.
Implement protocol with parameters you want:
protocol Testable {
func perfromAction()
var description: String { get set }
weak var viewController: YourViewController? { get set } //lets assume it is fine for testing
}
Accessing your UIViewController like this is not quite right, but for now it is ok. You can access labels, segues, etc.
Create Class for each test you want:
class TestA: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
class TestB: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
You can add some custom parameters for each Class, but those 3 from protocol are required.
Then your UIViewController would be like
class YourViewController: UIViewController {
var arrayOfTest: [Testable] = []
override func viewDidLoad() {
super.viewDidLoad()
arrayOfTest.append(TestA(viewController: self, description: "testA"))
arrayOfTest.append(TestB(viewController: self, description: "testB"))
arrayOfTest[0].perfromAction()
arrayOfTest[1].perfromAction()
}
func testCallback(description: String) {
print("I am called from \(description)")
}
}
I'm a swift beginner, so be gentle...
I'm having trouble assigning a function as a parameter.
I have defined this struct:
struct dispatchItem {
let description: String
let f: ()->Void
init(description: String, f: #escaping ()->()) {
self.description = description
self.f = f
}
}
I make use of this within a class called MasterDispatchController like so:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: testStaticTable),
dispatchItem(description: "Editable Table", f: testEditableTable)
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
etc.
Then I have a table view in my code that dispatches out to whichever function was clicked on (there are more than just the two I showed in the code above, but that's unimportant) like so
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f()
}
So... The compiler is not happy with this. It says when I am defining the dispatchItems let statement:
Cannot convert value of type '(MasterDispatchController) -> () -> ()' to expected argument type '() -> ()'
I figure... ok... I'm not sure I exactly understand this, but it seems like the compiler wants to know what class the callback functions are going to come from. I can see why it might need that. So I kind of blindly follow the pattern the compiler gives me, and change my struct to:
struct dispatchItem {
let description: String
let f: (MasterDispatchController)->()->Void
init(description: String, f: #escaping (MasterDispatchController)->()->()) {
self.description = description
self.f = f
}
}
Great the compiler is happy there, but now when I try to call the function with dispatchItems[indexPath.row].f() it says:
Missing parameter #1 in call
The function has no parameters, so I got confused...
I thought maybe it was asking me for the instance of the object in question which would make some sense... that would be "self" in my example, so I tried dispatchItems[indexPath.row].f(self) but then I got an error:
Expression resolves to an unused function
So I'm kind of stuck.
Sorry if this is a stupid question. Thanks for your help.
The problem is that you're trying to refer to the instance methods testStaticTable and testEditableTable in your instance property's initialiser before self is fully initialised. Therefore the compiler cannot partially apply these methods with self as the implicit parameter, but can instead only offer you the curried versions – of type (MasterDispatchController) -> () -> ().
One might be tempted then to mark the dispatchItems property as lazy, so that the property initialiser runs on the first access of the property, when self is fully initialised.
class MasterDispatchController : UITableViewController {
lazy private(set) var dispatchItems: [DispatchItem] = [
DispatchItem(description: "Static Table", f: self.testStaticTable),
DispatchItem(description: "Editable Table", f: self.testEditableTable)
]
// ...
}
(Note that I renamed your struct to conform to Swift naming conventions)
This now compiles, as you now can refer to the partially applied versions of the methods (i.e of type () -> Void), and can call them as:
dispatchItems[indexPath.row].f()
However, you now have a retain cycle, because you're storing closures on self which are strongly capturing self. This is because when used as a value, self.someInstanceMethod resolves to a partially applied closure that strongly captures self.
One solution to this, which you were already close to achieving, is to instead work with the curried versions of the methods – which don't strongly capture self, but instead have to be applied with a given instance to operate on.
struct DispatchItem<Target> {
let description: String
let f: (Target) -> () -> Void
init(description: String, f: #escaping (Target) -> () -> Void) {
self.description = description
self.f = f
}
}
class MasterDispatchController : UITableViewController {
let dispatchItems = [
DispatchItem(description: "Static Table", f: testStaticTable),
DispatchItem(description: "Editable Table", f: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f(self)()
}
func testEditableTable() {}
func testStaticTable() {}
}
These functions now take a given instance of MasterDispatchController as a parameter, and give you back the correct instance method to call for that given instance. Therefore, you need to first apply them with self, by saying f(self) in order to get the instance method to call, and then call the resultant function with ().
Although it may be inconvenient constantly applying these functions with self (or you may not even have access to self). A more general solution would be to store self as a weak property on DispatchItem, along with the curried function – then you can apply it 'on-demand':
struct DispatchItem<Target : AnyObject> {
let description: String
private let _action: (Target) -> () -> Void
weak var target: Target?
init(description: String, target: Target, action: #escaping (Target) -> () -> Void) {
self.description = description
self._action = action
}
func action() {
// if we still have a reference to the target (it hasn't been deallocated),
// get the reference, and pass it into _action, giving us the instance
// method to call, which we then do with ().
if let target = target {
_action(target)()
}
}
}
class MasterDispatchController : UITableViewController {
// note that we've made the property lazy again so we can access 'self' when
// the property is first accessed, after it has been fully initialised.
lazy private(set) var dispatchItems: [DispatchItem<MasterDispatchController>] = [
DispatchItem(description: "Static Table", target: self, action: testStaticTable),
DispatchItem(description: "Editable Table", target: self, action: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].action()
}
func testEditableTable() {}
func testStaticTable() {}
}
This ensures that you have no retain cycles, as DispatchItem doesn't have a strong reference to self.
Of course, you may be able to use unowned references to self, such as shown in this Q&A. However, you should only do so if you can guarantee that your DispatchItem instances don't outlive self (you would want to make dispatchItems a private property for one).
The problem here is that Swift treats class methods and functions differently under the hood. Class methods get a hidden self parameter (similar to how it works in Python), which allows them to know on which class instance they were called. That's why even though you declared testEditableCode as () -> (), the actual function has type (MasterDispatchController) -> () -> (). It needs to know on which object instance it was called.
The correct way to do what you're trying to do would be to create a closure which calls the correct method, like such:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: {() in
self.testStaticTable()
}),
dispatchItem(description: "Editable Table", f: {() in
self.testEditableTable()
})
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
If you're familiar with JavaScript, the {() in ...code...} notation is the same as function() { ...code... } or the same as lambda: ...code... in Python.
Here is the way to achieve what you want.
Implement protocol with parameters you want:
protocol Testable {
func perfromAction()
var description: String { get set }
weak var viewController: YourViewController? { get set } //lets assume it is fine for testing
}
Accessing your UIViewController like this is not quite right, but for now it is ok. You can access labels, segues, etc.
Create Class for each test you want:
class TestA: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
class TestB: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
You can add some custom parameters for each Class, but those 3 from protocol are required.
Then your UIViewController would be like
class YourViewController: UIViewController {
var arrayOfTest: [Testable] = []
override func viewDidLoad() {
super.viewDidLoad()
arrayOfTest.append(TestA(viewController: self, description: "testA"))
arrayOfTest.append(TestB(viewController: self, description: "testB"))
arrayOfTest[0].perfromAction()
arrayOfTest[1].perfromAction()
}
func testCallback(description: String) {
print("I am called from \(description)")
}
}
In a super Class called TableViewCell I have a property
class TableViewCell {
var model: AnyObject?
}
In a class called CountryTableViewCell I wrote this code
class CountryTableViewCell : TableViewCell {
var model:[AnyObject]? {
didSet {
// do some stuff
}
}
}
and I got this error
property model with [AnyObject]? cannot override a property with type
Anyobject?
Is it not possible to change the property model to an array?
No, you cannot use like that. Also AnyObject should be replaced by Any if using Swift 3.
You can change your code as below:
class TableViewCell {
var model: Any?
}
class CountryTableViewCell : TableViewCell {
override var model: Any? {
didSet {
}
}
}
Now if you want to get an array of the model in didSet then you can type cast it as below code.
class CountryTableViewCell : TableViewCell {
override var model: Any? {
didSet {
if let arrModel = model as? [Any] {
// Do Stuff...
}
}
}
}
No the property's type cannot be changed. If this is allowed, it would violate type safety by:
let subcell: CountryTableViewCell = CountryTableViewCell()
let supercell: TableViewCell = subcell
supercell.model = "anything that is not an array" as NSString
let wrong = subcell.model // not an array!
#ParthAdroja's answer showed a runtime workaround for this. You won't get an array at compile type, but at least you can ensure you have an array at runtime.
In principle if the property is read-only this specialization in subclass should work (it is same as restricting the return type of a function), but it doesn't work as of Swift 3. Anyone care about this can file an SE request to the Swift team ☺.
(Additionally, Apple do have some magic to make it work with bridged Objective-C classes, but we don't know what it is yet.)
I have a custom control that is using a datasource to fetch items (as an NSTableView would do). The datasource can return Any-type, as long as it's hashable. The items are used as a key in a private dictionary.
The control (custom view) is added to the UI in interface builder.
I run into problems when I am querying the datasource with a nil parameter because nil is not convertible to hashable.
What is the proper way to do this?
protocol DataSourceProtocol
{
func numberOfChildrenOfItem<Item: Hashable>(item: Item?) -> Int
func child<Item: Hashable>(index: Int, ofItem item: Item?) -> Item
}
class MyControl : NSControl
{
var dataSource : DataSourceProtocol!
func reloadData()
{
//using string as an example of a hashable
let countA = dataSource.numberOfChildrenOfItem("item") // ok
let countB = dataSource.numberOfChildrenOfItem(nil) // not ok
let childA = dataSource.child(0, ofItem: "item") //ok
let childB = dataSource.child(0, ofItem: nil) //not ok
self.reloadChildren(childA)
self.reloadChildren(childB)
}
func reloadChildren<Item: Hashable>(item: Item)
{}
}
Use NSNull() to get a null object, which you can then compare to another NSNull() to see if its empty or not.