[<Register ("ChatViewCell")>]
type ChatViewCell (handle: IntPtr) as this =
inherit UICollectionViewCell (handle)
[<DefaultValue>] static val mutable private id : NSString
static member init =
printfn "Initializing ChatViewCell."
ChatViewCell.id <- new NSString("ChatCell")
override this.ReuseIdentifier = ChatViewCell.id
let mutable profileImageView = new UIImageView()
let mutable nameLabel = new UILabel()
let mutable messageLabel = new UILabel()
let mutable timeofMessageLabel = new UILabel()
let mutable dividerLineView = new UIView()
let mutable countLabel = new UILabel()
let setupView() =
profileImageView.Frame <- CGRect(50.0, 0.0, 200.0, 100.0)
profileImageView.ContentMode <- UIViewContentMode.ScaleAspectFill
profileImageView.Layer.CornerRadius <- Conversions.nfloat(30)
I have the following UICollectionViewCell and I want to call the setupView method when the cell is initialised. However, the setupView method doesn't seem to be available within init. I tried moving it above init, however, this doesn't work either.
setupView is defined as an instance function because it doesn't not have the static modifier. It must be an instance function (or an instance method) because it accesses profileImageView which is an instance field.
The static member init cannot call an instance function because there is no way to explicitly pass an instance to an instance function (you can only explicitly pass an instance to a method).
If you want to do some initialization on construction of your ChatViewCell you can simply put your initialization statements in the body of the class. When you do this you need to use the do keyword which is normally implicit.
e.g.
type ChatViewCell (handle: IntPtr) as this =
inherit UICollectionViewCell (handle)
let mutable profileImageView = new UIImageView()
do profileImageView.Frame <- CGRect(50.0, 0.0, 200.0, 100.0)
do profileImageView.ContentMode <- UIViewContentMode.ScaleAspectFill
do profileImageView.Layer.CornerRadius <- Conversions.nfloat(30)
Useful references:
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/members/constructors
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/members/let-bindings-in-classes
Related
when I use let UISwitch replace lazy var UISwitch in UITableViewCell like this:
let trigger: UISwitch = {
let sw = UISwitch()
sw.addTarget(self, action: #selector(onTriggerChanged(sender:)), for: .valueChanged)
return sw
}()
I found the function onTriggerChanged never called, so I do some test, here is my test code:
class TestCell: UITableViewCell {
lazy var trigger: UISwitch = {
let sw = UISwitch()
print("trigger \(self)")
sw.addTarget(self, action: #selector(onTriggerChanged(sender:)), for: .valueChanged)
return sw
}()
var trigger2: UISwitch = {
let sw = UISwitch()
print("trigger2 \(self)")
sw.addTarget(self, action: #selector(onTriggerChanged(sender:)), for: .valueChanged)
return sw
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let trigger3: UISwitch = {
let sw = UISwitch()
print("trigger3 \(self)")
sw.addTarget(self, action: #selector(onTriggerChanged(sender:)), for: .valueChanged)
return sw
}()
contentView.addSubview(trigger)
contentView.addSubview(trigger2)
contentView.addSubview(trigger3)
}
}
print message is:
trigger <TestCell: 0x......>
trigger2 (Function)
trigger3 <TestCell: 0x......>
and trigger2 valueChanged event not called.
so what difference of self in trigger/trigger2/trigger3
I'm sure you're aware, self (in the usual sense) is not available in property initialisers of non-lazy properties, which is why you use lazy as a workaround.
The self that you are printing in trigger2 can't refer to the self instance. It actually refers to an instance method called self, declared in NSObject, which is inherited by TestCell.
This is actually an instance method. In Swift, you are allowed to refer to instance methods without having an instance (remember that you don't have access to the actual self instance here). It's just that their type becomes (T) -> F, where T is the type in which the instance method is declared, and F is the function type for the method. Essentially, they become static methods that take an instance as parameter, and give you back the original instance method.
For example, the self method, when referenced without an instance, would have the type:
(TestCell) -> (() -> TestCell)
You can see that is indeed what it is by printing out type(of: self).
Clearly, adding the self method as the target doesn't work. It can't respond to the selector onTriggerChangedWithSender.
For trigger1 and trigger3, the self instance is available, and self mean the usual thing - the current instance.
Lazy var is computed when the variable is required somewhere and only runs the initializing code once while non lazy var and let compute the value when the variable is initialized. and where is the onTriggerChanged function.
I edited exactly what I want to achieve, this time different example without taken parameter in function.
What I want to achieve is:
var array = [String]()
func create() {
var arrayCount = array.count // will be 0
var nameForUI = "view/(arrayCount)" // will be view0
let nameForUI: UIVIew = {
let view = UIVIew()
return view
}()
array.append(nameForUI)
view.addSubview(nameForUI)
//
}
next time if I call create() func , the next view will be called "view1" So my question is, how to achieve this result? every time function will called it will create new element with new name.
Edit
To directly answer your question: No, you cannot do that.
Code written in Swift is compiled -- it is not an interpreted / scripted language.
So you cannot write code that creates a button named "littleButton" and then have a line of code littleButton.backgroundColor = .red
You can sort of do this by creating a Dictionary that maintains the "name" of the element as the key, and a reference to the element as the value.
Start with initializing an empty dictionary:
var buttonsDict: [String : UIButton] = [String : UIButton]()
Your "create" func can start like this:
func createButton(named str: String) -> Void {
// create a button
let b = UIButton()
// give it a default title
b.setTitle("Button", for: .normal)
// add it to our Dictionary
buttonsDict.updateValue(b, forKey: str)
}
When you want to create a button:
createButton(named: "littleButton")
When you want to access that button by name:
// use guard to make sure you get a valid button reference
guard let btn = buttonsDict["littleButton"] else { return }
view.addSubview(btn)
Edit 2
Another option, which is perhaps more similar to your edited question:
// initialize empty array of views
var viewsArray: [UIView] = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
// create 5 views
for _ in 0..<5 {
create()
}
// ...
// then, somewhere else in your code
viewsArray[0].backgroundColor = .red
viewsArray[1].backgroundColor = .green
viewsArray[2].backgroundColor = .blue
viewsArray[3].backgroundColor = .yellow
viewsArray[4].backgroundColor = .orange
}
func create() -> Void {
// create a view
let v = UIView()
// add it to our array
viewsArray.append(v)
// add it as a subview
view.addSubview(v)
}
As you see, instead of trying to reference the created views by name (which you cannot do), you can reference them by array index.
Just remember that arrays are zero-based... so the first element added to the array will be at [0] not [1].
I have a UILabel that is generic and I will pass data into it from another controller. I want have it so that the label will be the text for the UISegmentedControl, however I get the error that 'Instance member 'lessonLabel' cannot be used on type LearnTopicHeaderView (My class that is a UICollectionReusableView).
import UIKit
class LearnTopicHeaderView: UICollectionReusableView {
let lessonLabel = UILabel(text: "X Lessons", font: .systemFont(ofSize: 14))
let segmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["Overview", lessonLabel, "Apply"]) // Here is the error.
sc.selectedSegmentIndex = 0
sc.layer.cornerRadius = 27
sc.addTarget(self, action: #selector(selectedIndex), for: .valueChanged)
return sc
}()
}
I'm not sure how to fix this.
The items that are passed to the UISegmentedControl initialiser can be strings or UIImage instances. You can't pass a UILabel instance.
I am trying to make a UIImage view clickable, but I am having no luck. What is the best way to complete this task? The error I am getting is "Cannot convert value of type '() -> _' to specified type 'UIImageView'".
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "ic_file_upload_white_48pt")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectorProfileImage)))
imageView.isUserInteractionEnabled = true
return imageView
}
You are telling the compiler that you want to make profileImageView contain a closure. If you want profileImageView to contain the results of that closure, you need to add parens after in order to invoke the closure:
lazy var profileImageView: UIImageView = {
//your code here
return imageView
}()
Note the parentheses after the closure. That assigns the result of calling the closure to your variable profileImageView the first time you reference the variable.
Edit:
Any time you see a type of (<something>) -> type it's a closure. The -> bit separates the parameters from the return type. Swift's error messages can be hard to decipher, but that's a clue that you're returning a closure rather than whatever is expected.
Edit #2:
Note that there are 2 similar constructs related to a variable defined by a closure in Swift: Computed properties and lazy variables.
Computed properties
A computed property is declared as
var computed: type { closure_returning_result }
There is no equals sign in a computed property. Every time you ask for a value from the computed property, the closure code runs, and the returned value of the closure is the new value of the property.
Lazy vars:
A lazy var looks like this:
var lazy: type = expression_returning_result
The expression is often a closure, but it doesn't have to be. A common form of a lazy var would use a closure, like this:
var lazy: type = { closure_returning_result }()
There is an equals sign in the declaration of a lazy var. If you use a closure to assign a value to a lazy var, you need to add parentheses after the closure, so the lazy var is assigned the returned value of the closure, not the closure itself. This thread came up when #jameel forgot the closing parentheses in his code.
Consider the following code:
var counter = 1
var computed: Int {
counter += 1
return counter
}
var lazy: Int = {
counter += 1
return counter
}()
print("lazy = \(lazy)")
print("lazy = \(lazy)")
print("lazy = \(lazy)")
print("computed = \(computed)")
print("computed = \(computed)")
print("computed = \(computed)")
That prints the output:
lazy = 2
lazy = 2
lazy = 2
computed = 3
computed = 4
computed = 5
Note that the value of the lazy var doesn't change, but the value of the computed property does. That is the key difference between them.
A lazy variable gets evaluated once the first time you ask for it, and that value "sticks". The expression/closure that gives the value is not run until you ask for it, and then only once.
In contrast, a computed property always uses a closure, and every time you ask for the value of a computed property, the closure is executed.
Looks like a syntax issue, try
func profileImageView() -> UIImageView {
let imageView = UIImageView()
imageView.image = UIImage(named: "ic_file_upload_white_48pt")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectorProfileImage)))
imageView.isUserInteractionEnabled = true
return imageView
}
You should add paranthesis after the end of computed property in order to execute it.
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "ic_file_upload_white_48pt")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectorProfileImage)))
imageView.isUserInteractionEnabled = true
return imageView
}()
I didn't found answer for my question in swiftbook.
Is this possible to create property-like closure for local variable in swift? I mean smt like further snippet:
func someFunc() {
// here goes our closure
var myRect:CGRect {
var x = 10
var y = 20
var width = 30
var heigth = 40
myRect = CGPointMake(x,y,width,heigth)
}
}
I have complexity evaluation of UI elements position. This trick should make my code much readable
This is called read-only computed property where you can omit the getter to simplify declaration:
var myRect: CGRect {
let x:CGFloat = 10
let y:CGFloat = 20
let width:CGFloat = 30
let height:CGFloat = 40
return CGRectMake(x, y, width, height)
}
Read-Only Computed Properties
A computed property with a getter but no setter is known as a
read-only computed property. A read-only computed property always
returns a value, and can be accessed through dot syntax, but cannot be
set to a different value.
NOTE
You must declare computed properties—including read-only computed
properties—as variable properties with the var keyword, because their
value is not fixed. The let keyword is only used for constant
properties, to indicate that their values cannot be changed once they
are set as part of instance initialization.
You can simplify the declaration of a read-only computed property by
removing the get keyword and its braces:
Documentation Swift Conceptual Properties
Why not try this way?
fun someFunc() {
var myRect = {() -> CGRect in
let x:CGFloat = 10.0
let y:CGFloat = 20.0
let width:CGFloat = 30.0
let height:CGFloat = 40.0
return CGRectMake(x,y,width,height)
}
myRect() //Call it
}
EDIT I think if there are some requirements to calculate some points position like maxElement use closure is good to save some small functions.