In my ViewController, I am setting up multiple so-called PanelControls.
These PanelControls are initialized with a title, UISlider and information, what property of another UIView called ViewToEdit to change with that slider (ControlPanel has a reference to it).
It is always one single property of type CGFloat or UIColor.
I want to be able to pass a property (not a value) of ViewToEdit when initializing a PanelControl.
So I wanna use it like this:
PanelControl(title: "doesnt matter", propertyToEdit: ViewToEdit.propertyToEdit)
And PanelControl would implement it like this:
class PanelControl: UIView {
...
func sliderChanged(slider: UISlider) {
propertyToEdit = slider.value
}
}
please not that the above code is just my fantasy and doesn't actually work. It just illustrates my desired usage.
This way I could create many instances of PanelControl and pass each one different information on which property of ViewToEdit they control.
I have tried:
Using a closure, but that does not fit because it is not a complete statement I want to pass. Rather a part of a statement. So viewToEdit.propertyToEdit = ... with the right side of that statement set by PanelControl when executing it.
Literally passing it ViewToEdit.propertyToEdit but that obviously makes no sense as well.
What to I do?
How about using key paths:
class PanelControl: UIView {
let changer: (Float) -> Void
init<T: AnyObject>(title: String, object: T, property: ReferenceWritableKeyPath<T, Float>) {
changer = { object[keyPath: property] = $0 }
}
func sliderChanged(slider: UISlider) {
changer(slider.value)
}
}
You can use it like this:
PanelControl(title: "doesnt matter", object: myViewModel, property: \ViewToEdit.propertyToEdit)
Related
I am new to RxSwift and I apologise how badly the question is framed but I couldn't find the proper terminology.
So basically I have a let infoIconTapped: Driver<Void> in one struct, and I have let shouldShowInfoPopup: Driver<Bool> in another struct.
What I want to do is I want to change the value of (or drive it) shouldShowInfoPopup to true or false using infoIconTapped. This should be done using Driver only which is my requirement.
Any idea on how to do this?
It sounds like what you want to do is toggle the visibility of the popup based on the tapping of the button. If so, then you need to maintain state and that means using the .scan operator.
func shouldShowInfoPopup(infoIconTapped: Driver<Void>) -> Driver<Bool> {
return infoIconTapped
.scan(false) { current, _ in !current }
.startWith(false)
}
The above would be a free function (not in any class or struct) and could be used like this:
let showInfo = shouldShowInfoPopup(infoIconTapped: infoIconTapped)
If you must put the function in a class or struct, then put it in an extension on Driver itself like this:
extension SharedSequence where SharingStrategy == DriverSharingStrategy {
var shouldShowInfoPopup: Driver<Bool> {
scan(false) { current, _ in !current }
.startWith(false)
}
}
Which could be used like this:
let shouldShowInfoPopup = infoIconTapped.shouldShowInfoPopup
I'm faced with the problem of using generic class and inheritance.
Brief description of the problem:
I have a base class called BookPageDataSource and two inherited classes (ReadingBookPageDataSource and StarsBookPageDataSource) with different implementations.
Also, I have a generic class BookPageViewController that contains the generic parameter of this data source and two inherited classes (ReadingBookPageViewController and StarsBookPageViewController) from this class.
I need to write a method the return parameter of which is BookPageViewController<DataSource>.
// Data Sources
class BookPageDataSource { }
class ReadingBookPageDataSource: BookPageDataSource { }
class StarsBookPageDataSource: BookPageDataSource { }
// Controllers
class BookPageViewController<DataSource: BookPageDataSource>: UIViewController {
let dataSource: DataSource
init(dataSource: DataSource) {
self.dataSource = dataSource
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
}
final class ReadingBookPageViewController: BookPageViewController<ReadingBookPageDataSource> { }
final class StarsBookPageViewController: BookPageViewController<StarsBookPageDataSource> { }
// Communication
class Pager {
func currentPageController<DataSource>(at index: Int) -> BookPageViewController<DataSource> {
// for example
if index == 0 {
// How to remove the cast from the line below?
return readingPageController() as! BookPageViewController<DataSource>
}
return starsPageController() as! BookPageViewController<DataSource>
}
private func readingPageController() -> ReadingBookPageViewController {
return ReadingBookPageViewController(dataSource: ReadingBookPageDataSource())
}
private func starsPageController() -> StarsBookPageViewController {
return StarsBookPageViewController(dataSource: StarsBookPageDataSource())
}
}
The method currentPageController always crashes, because the DataSource is always equals to BookPageDataSource, not to ReadingBookPageDataSource or StarsBookPageDataSource.
Conceptual Discussion
Your concept for the architecture is flawed and this is leading to your issue.
Simple Generics Example
Here's a very simple example of a generic function, which just returns the value you give it:
func echo <T> (_ value: T) -> T { return value }
Because this function is generic, there is ambiguity about the type that it uses. What is T? Swift is a type-safe language, which means that ultimately there is not allowed to be any ambiguity about type whatsoever. So why is this echo function allowed? The answer is that when I actually use this function somewhere, the ambiguity about the type will be removed. For example:
let myValue = echo(7) // myValue is now of type Int and has the value 7
In the act of using this generic function I have removed the ambiguity by passing it an Int, and therefore the compiler has no uncertainty about the types involved.
Your Function
func currentPageController <DataSource> (at index: Int) -> BookPageViewController<DataSource>
Your function only uses the generic parameter DataSource in the return type, not in the input - how is the compiler supposed figure out what DataSource is?* I assume this is how you imagined using your function:
let pager = Pager()
let controller = pager.currentPageController(at: 0)
But now, what is the type of controller? What can you expect to be able to do with it? It seems that you're hoping that controller will take on the correct type based on the value that you pass in (0), but this is not how it works. The generic parameter is determined based on the type of the input, not the value of the input. You're hoping that passing in 0 will yield one return type, while 1 will yield a different one - but this is forbidden in Swift. Both 0 and 1 are of type Int, and the type is all that can matter.
As is usually the case with Swift, it is not the language/compiler that is preventing you from doing something. It is that you haven't yet logically formulated what is even is that you want, and the compiler is just informing you of the fact that what you've written so far doesn't make sense.
Solutions
Let's move on to giving you a solution though.
UIViewController Functionality
Presumably there is something that you wanted to use controller for. What is it that you actually need? If you just want to push it onto a navigation controller then you don't need it to be a BookPageViewController. You only need it to be a UIViewController to use that functionality, so your function can become this:
func currentPageController (at index: Int) -> UIViewController {
if index == 0 {
return readingPageController()
}
return starsPageController()
}
And you can push the controller that it returns onto a navigation stack.
Custom Functionality (Non-Generic)
If, however, you need to use some functionality which is specific to a BookPageViewController then it depends what it is you want to do. If there is a method on BookPageViewController like this:
func doSomething (input: Int) -> String
which doesn't make use of the generic parameter DataSource then probably you'll want to separate out that function into its own protocol/superclass which isn't generic. For example:
protocol DoesSomething {
func doSomething (input: Int) -> String
}
and then have BookPageViewController conform to it:
extension BookPageViewController: DoesSomething {
func doSomething (input: Int) -> String {
return "put your implementation here"
}
}
Now the return type of your function can be this non-generic protocol:
func currentPageController (at index: Int) -> DoesSomething {
if index == 0 {
return readingPageController()
}
return starsPageController()
}
and you can use it like this:
let pager = Pager()
let controller = pager.currentPageController(at: 0)
let retrievedValue = controller.doSomething(input: 7)
Of course, if the return type is no longer a UIViewController of any sort then you probably want to consider renaming the function and the related variables.
Custom Functionality (Generic)
The other option is that you can't separate out the functionality you need into a non-generic protocol/superclass because this functionality makes use of the generic parameter DataSource. A basic example is:
extension BookPageViewController {
func setDataSource (_ newValue: DataSource) {
self.dataSource = newValue
}
}
So in this case you really do need the return type of your function to be BookPageViewController<DataSource>. What do you do? Well, if what you really want is to use the setDataSource(_:) method defined above then you must have a DataSource object that you plan to pass in as an argument, right? If this is the case then we're making progress. Previously, you only had some Int value which you were passing into your function and the problem was that you couldn't specify your generic return type with that. But if you already have a BookPageDataSource value then it is at least logically possible for you to use this to specialize your
function.
What you say you want, however, is to just use an Int to get the controller at that index, regardless of what the DataSource type is. But if you don't care what the DataSource is of the returned BookPageViewController then how can you expect to set its DataSource to something else using the setDataSource(_:) method?
You see, the problem solves itself. The only reason you would need the return type of your function to be generic is if the subsequent functionality you need to make use of uses that generic type, but if this is the case then the controller you get back can't have just any old DataSource (you just wanted whichever one corresponds to the index you provide) - you need it to have exactly the type of DataSource which you plan to pass in when you use it, otherwise you're giving it the wrong type.
So the ultimate answer to your question is that, in the way that you were conceiving of it, there is no possible use for the function you were trying to construct. What's very cool about the way Swift is architected is that the compiler is actually able to figure out that logical flaw and prevent you from building your code until you've re-conceptualized it.
Footnote:
* It is possible to have a generic function which only uses the generic parameter in the return type and not in the input, but this won't help you here.
In my app I have a Storyboard with a bunch of elements laid out. I am setting properties of these elements from "ViewController.swift".
On the storyboard are two UIViews, which have been subclassed to allow for drawing methods. They are to be used as "signature" fields, where a user can sign their signature into the box. This is working fine.
One of the subclassed methohds of the UIViews is "eraseBitmap" which clears the UIView of the drawing.
class SigView: UIView {
...
func eraseBitmap() {
path = nil
cache = nil
}
}
I have a button that calls a function "erase" in the parent VC.
I have tried doing
func erase() {
SigView.eraseBitmap()
}
However that generates an error saying that I'm missing an argument. eraseBitmap, however, accepts no arguments.
If I add an argument, regardless what it is, I get a "SigView not convertible to..." error.
Any ideas, or a better way of coding this part?
Your SigView class defines a method eraseBitmap() - something like:
class SigView : UIView {
func eraseBitmap () { /* ... */ }
}
You'll apply this method to instances of SigView. So, somewhere you've got an instance of SigView in your UI, like:
var aView : SigView = ...
You'll then use:
func erase () {
aView.eraseBitmap()
}
or perhaps
func erase (view:SigView) {
view.eraseBitmap()
}
The error you are getting is caused by attempting to invoke a non-class method on a class. Non-class methods can only be invoked on instances of classes.
I am wondering if anyone knows how to (if possible) pass a UILabel through a function while being able to access and change its properties? Here's what I have:
func plusMinusChange(minus: UILabel, plus: UILabel) {
if (minus.hidden) {
minus.hidden=false
plus.hidden=true
} else {
minus.hidden=true
plus.hidden=false
}
}
And here's how I am calling it:
plusMinusChange(firstMinus, firstPlus)
I know this is probably really illogical but I want to give it a try anyways. If you were wondering, firstMinus and firstPlus are linked to UILabels on the storyboard.
Calls to methods (that is, funcs defined within a class or other type) require parameter labels for the second (and subsequent) parameter but not the first. If you want to change which labels are required at the call site, you change the declaration.
To require a label on the first parameter:
func plusMinusChange(#minus: UILabel, plus: UILabel) {
To require no label on the second:
func plusMinusChange(minus: UILabel, _ plus: UILabel) {
There is no need to use a conditional if there. You can use ! in front of the property to toggle its value as follow:
minus.hidden = !minus.hidden
plus.hidden = !plus.hidden
I’m currently writing some Swift code in a project that is predominately Objective-C. In our ObjC code, we have a header that declares typedef GPUImageOutput<GPUImageInput> MyFilter;. We can then declare e.g. a #property that can only be a GPUImageOutput subclass that implements GPUImageInput.
(NOTE: GPUImageOutput and GPUImageInput are not defined by me; they are part of the GPUImage library)
Our Swift code doesn't seem to recognize this, even though the header is #imported in our Bridging Header. I’ve tried to replicate the declaration in Swift, but neither of these are proper syntax:
typealias MyFilter = GPUImageOutput, GPUImageInput
typealias MyFilter = GPUImageOutput : GPUImageInput
You can't declare typealias like that.
The best we can do is something like this:
class MyClass {
private var filter:GPUImageOutput
init<FilterType:GPUImageOutput where FilterType:GPUImageInput>(filter:FilterType) {
self.filter = filter
}
func setFilter<FilterType:GPUImageOutput where FilterType:GPUImageInput>(filter:FilterType) {
self.filter = filter
}
func someMethod() {
let output = self.filter
let input = self.filter as GPUImageInput
output.someOutputMethod()
input.someInputMethod()
}
}
In Swift 4 you can achieve this with the new & sign (Below an example of a parameter confirming to UIViewController and UITableViewDataSource:
func foo(vc: UIViewController & UITableViewDataSource) {
// access UIViewController property
let view = vc.view
// call UITableViewDataSource method
let sections = vc.numberOfSectionsInTableView?(tableView)
}
In Swift, something like the following should accomplish your task, but it's different than its ObjC counterpart:
typealias GPUImageOutput = UIImage
#objc protocol GPUImageInput {
func lotsOfInput()
}
class GPUImageOutputWithInput: GPUImageOutput, GPUImageInput
{
func lotsOfInput() {
println("lotsOfInput")
}
}
// ...
var someGpuImage = GPUImageOutput()
var specificGpuImage = GPUImageOutputWithInput()
for image in [someGpuImage, specificGpuImage] {
if let specificImage = image as? GPUImageInput {
specificImage.lotsOfInput()
} else {
println("the less specific type")
}
}
UPDATE: now that I understand where/why you have these types ...
GPUImage seems to have a swift example that does what you want, as Swift-ly as possible.
See here:
class FilterOperation<FilterClass: GPUImageOutput where FilterClass: GPUImageInput>: FilterOperationInterface {
...
The type constraint syntax can be applied to functions, too, and with a where clause, that's probably as good as you're going to get directly in Swift.
The more I tried to understand how to port this somewhat common objc trope, the more I realized it was the most Swift-way. Once I saw the example in GPUImage itself, I was convinced it was at least your answer. :-)
UPDATE 2: So, besides the specific GPUImage example I linked to above that uses Swift, the more and more I think about this, either using a where clause to guard the setter function, or using a computable property to filter the set functionality seems the only way to go.
I came up with this strategy:
import Foundation
#objc protocol SpecialProtocol {
func special()
}
class MyClass {}
class MyClassPlus: MyClass, SpecialProtocol {
func special() {
println("I'm special")
}
}
class MyContainer {
private var i: MyClass?
var test: MyClass? {
get {
return self.i
}
set (newValue) {
if newValue is SpecialProtocol {
self.i = newValue
}
}
}
}
var container = MyContainer()
println("should be nil: \(container.test)")
container.test = MyClass()
println("should still be nil: \(container.test)")
container.test = MyClassPlus()
println("should be set: \(container.test)")
(container.test as? MyClassPlus)?.special()
Outputs:
should be nil: nil
should still be nil: nil
should be set: Optional(main.MyClassPlus)
I'm special
(Optionally, you could also use precondition(newValue is SpecialProtocol, "newValue did not conform to SpecialProtocol") in place of the is check, but that will act like an assert() can crash the app if the case isn't met. Depends on your needs.)
#rintaro's answer is a good one, and is a good example of using a where clause as a guard (both nice functional-ly, and Swift-ly). However, I just hate to write a setFoo() function when computable properties exist. Then again, even using a computable property has code smell, since we can't seem to be able to apply a generic type-constraint to the set'er, and have to do the protocol conformance test in-line.
You can use typealias keyword. Here is how to do it:
typealias MyNewType = MyExistingType
It doesn't matter whether MyExistingType is protocol or function or enum. All it needs to be some type. And the best part is you can apply access control on it. You can say
private typealias MyNewType = MyExistingType
That makes MyNewType is only accessible the context that is defined in.