What is the best way to create same Views in Swift? - ios

Sometimes I need to create same Views (or ImageViews) in Swift.
For example, make same size and image, delete the view, and make it again (and sometimes change the view's image or size).
In this case, of course writing same codes each time to need the view is not good.
So I want to find good way to create same views multiple times.
Now I use this code when I create same ImageView.
*CreateImage.swift
class aImageView: UIImageView {
override init(frame: CGRect) {
super.init(frame: frame)
let aImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 70, height: 50))
aImageView.image = UIImage(named: "item_a01_70x50")
aImageView.tag = 100
aImageView.isUserInteractionEnabled = true
aImageView.backgroundColor = UIColor.lightGray
self.addSubview(aImageView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
class bImageView: UIImageView {
override init(frame: CGRect) {
super.init(frame: frame)
let bImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 70, height: 50))
bImageView.image = UIImage(named: "item_b02_70x50")
bImageView.tag = 200
bImageView.isUserInteractionEnabled = true
bImageView.backgroundColor = UIColor.lightGray
self.addSubview(bImageView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
*TopView.swift
class TopView: UIView {
var aview: aImageView!
var bview: bImageView!
init(frame: CGRect, stage:Int){
super.init(frame: frame)
aview = aImageView(frame :CGRect(x: 0, y: 0, width: 70, height: 50))
self.addSubview(aview)
bview = bImageView(frame :CGRect(x: 100, y: 100, width: 70, height: 50))
self.addSubview(bview)
//...and sometimes delete them, create them again other time, and create...
}
}
If you know better way, could you tell me?
Thanks.

i think you can make an extension of UIImageView like this:
extension UIImageView {
convenience init(_imageName: String = "",
_frameTulpe: (x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) = (0, 0, 0, 0),
_tag: Int = 0,
_isUserInteractionEnabled: Bool = true,
_backgroundColor: UIColor = UIColor.lightGray)
{
self.init(image: UIImage(named: _imageName))
frame = CGRect(x: _frameTulpe.x, y: _frameTulpe.y, width: _frameTulpe.w, height: _frameTulpe.h)
tag = _tag
isUserInteractionEnabled = _isUserInteractionEnabled
backgroundColor = _backgroundColor
}
}
And then, in your TopView.swift will be:
class TopView: UIView {
var aview: aImageView!
var bview: bImageView!
init(frame: CGRect, stage:Int){
super.init(frame: frame)
aview = UIImageView(_imageName: "item_a01_70x50", _frameTulpe:(0, 0, 70, 50), _tag: 100)
self.addSubview(aview)
bview = UIImageView(_imageName: "item_b02_70x50", _frameTulpe:(100, 100, 70, 50), _tag: 200)
self.addSubview(bview)
//...and sometimes delete them, create them again other time, and create...
}
}

uh hun! You just need merge the aImageView class and bImageView class. Then the variable property can be set after that the object be created, or you can define the other init method with it is contain the different parameter that be used for initializing the variable property.

Define just one class(ex: Utility.swift) which will provide custom Views.
Last but not the least you need to autolayout after adding subview(ex: aImageView).
I am following this technique.

Related

Swift Custom UIView with init parameters

I have witten a custom UIView class which takes several parameters and overrides an empty storyboard UIView(subclassed as MultiPositionTarget class instead of UIview) as below.The views below are linked as outlet to view controller as it is seen from the code below(in total 9 views).
// Initialize the targets
target1 = MultiPositionTarget(.zero,"targetTemp.png","David","Target")
target1.parentVC = self
targetList.append(target1)
However, it does not accept the parameters. And only loads the view. Here is my class below:
class MultiPositionTarget: UIView{
var targetName: String!
var bottomLabelName: String!
var imageName: String!
var isSelected: Bool!
var labelTop: UILabel!
var labelBottom: UILabel!
var parentVC: SelectTargetViewController!
init(_ frame: CGRect,_ imagName: String,_ targetname: String,_ targetlabel: String){
self.imageName = imagName
self.targetName = targetname
self.bottomLabelName = targetlabel
super.init(frame: frame)
setUp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
func setUp(){
let viewWidth = self.bounds.width
let viewHeight = self.bounds.height
// Add top label
labelTop = UILabel(frame: CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight/5))
//labelTop.center = CGPoint(x: 160, y: 285)
labelTop.textAlignment = .center
labelTop.font = UIFont(name:"System",size:6)
labelTop.text = self.imageName
labelTop.textColor = UIColor.white
labelTop.backgroundColor = hexStringToUIColor(hex: "#770B2C")
//labelTop.alpha = 0.5
self.addSubview(labelTop)
let image = UIImage(named: "targetTemp.png")
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 0, y: (viewHeight-viewHeight * 4/5), width: viewWidth, height: (viewHeight * 4/5))
self.addSubview(imageView)
// Add bottom Label
labelBottom = UILabel(frame: CGRect(x: 0, y: (viewHeight * 4/5), width: viewWidth, height: viewHeight/5))
//labelBottom.center = CGPoint(x: 160, y: 285)
labelBottom.textAlignment = .center
labelBottom.text = self.bottomLabelName
labelBottom.font = UIFont(name:"System",size:10)
labelBottom.textColor = UIColor.white
labelBottom.backgroundColor = hexStringToUIColor(hex: "770B2C")
labelBottom.alpha = 0.95
self.addSubview(labelBottom)
self.isUserInteractionEnabled = true
let viewTap = UITapGestureRecognizer(target: self, action: #selector(singleTap))
self.addGestureRecognizer(viewTap)
}
}
Any help or hint is appreciated. Thanks in advance stack overflow family.
This line is creating a new view:
target1 = MultiPositionTarget(.zero,"targetTemp.png","David","Target")
The new view is unrelated to the view shown on the screen. The new view isn't actually on the screen at all, because you haven't added it to the view hierarchy. Even if you did, it's invisible because it has a frame of zero.
In short, you can't call your custom initialiser and also design your view in the storyboard. You can initialise the properties you want either in the storyboard as well, or in code.
If you want to initialise the properties in the storyboard as well, you can modify imageName, bottomLabelName and targetName with #IBInsepctable:
#IBInsepctable var targetName: String!
#IBInsepctable var bottomLabelName: String!
#IBInsepctable var imageName: String!
// or, if you want to directly select an image from the storyboard:
// #IBInsepctable var image: UIImage!
This way those properties will show up in the property inspector in the storyboard.
If you want to initialise it in code, you must set it with assignment statements, and not with an initialiser:
target1.targetName = "David"
target1.bottomLabelName = "Target"
target1.imageName = "targetTemp.png"
target1.parentVC = self
targetList.append(target1)

How to get Width or Height of a UIView when I'm using "Equal width" constrain

I need help to solve this problem: I have a UITextFiled and I'm trying to apply a border at the bottom using this code:
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.addSublayer(border)
}
The problem is that the result is not correct, the border goes outside the textfield because in the text Field I'm using the "Equal width constrain" and the Width at design time is not the same Width at "Didload()" time. There is a way to get the width to the textField after "Equal width constrain" correction?
A much better approach is to
subclass UITextField
create the "underline border" layer on initialization
change the frame of that layer in layoutSubviews()
Example:
#IBDesignable
class UnderlinedTextField: UITextField {
let underlineLayer: CALayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(underlineLayer)
underlineLayer.backgroundColor = UIColor.black.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
underlineLayer.frame = CGRect(x: 0, y: bounds.height - 2.0, width: bounds.width, height: 2)
}
}
Result (I gave the text field a background color of .cyan to make it easy to see):
It automatically resizes the "underline" when the field size changes - such as on device rotation:
Note that, by making it #IBDesignable, you can also see the underline layer during design-time.
This example uses a default color of black for the "underline" but you can change it via code just like any other property change, e.g.:
testField.underlineLayer.backgroundColor = UIColor.red.cgColor
Override bounds variable and call your border drawing in didSet. Your layer would be updated every time view changes bounds.
var border = CALayer()
init(frame: CGRect) {
super.init(farme: frame)
self.layer.addSublayer(border)
}
override var bounds: CGRect {
didSet {
addBottomBorderWithColor(color: .black, width: 2)
}
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.setNeedsLayout()
}
I found a possible solution by myself (not the perfect one).
Because the Constrains are probably applied after DidLoad() and after viewDidLayoutSubviews(), I called the function to add the border inside the function viewDidAppear(). Now it works even if the new borders are shown with a small delay.
The best way is sub-class a UITextFiled as described here.
Custom class that can be applied to every UITextField - Swift
In this case there the object is created correctly

object value doesn't change after didset

I am using some cocoa pods frame to make UICollectionView. I am able to get the right data for each cell and setup views.
I want to have different ani
The animation end point should be related to the value of dataSource.The problem is that I can't use the didSet value as I wish. The frame of subviews I added are (0,0,0,0), the values of objects keep the same as initial value outside the didSet. I am not able to access the dataSource Value in setupView function.
Expected effect is that the red bar will move from left side to the right position:
class CereCell : DatasourceCell {
override var datasourceItem: Any?{
didSet {
guard let cere = datasourceItem as? CeremonyDay else { return }
nameLabel.text = cere.name
nameLabel.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
currentBar.frame = CGRect(x: 100, y: 100, width: cere.percentage*100 , height: 10)
position.x = cere.percentage
}
}
let currentBar: UIView = {
let currentBar = UIView(frame: CGRect(x: 290, y: 100, width: 300, height: 10))
currentBar.backgroundColor = .red
return currentBar
}()
override func setupViews() {
super.setupViews()
contentView.addSubview(currentBar)
let frame = currentBar.frame
print(frame)
It just print the (290, 100, 300, 10), which is not the value in didSet.

Background colour of UIVIew inside UIPickerView does not set properly

I am trying to implement a UIPickerView with custom design. I've taken a UIVIew inside a UIPickerView and a label inside my UIVIew. Color of my picker background is black and Now I am trying to apply white background color to UIVIew and label inside View. But the colour is not what exact I want. I want pure white colour but it's returning gray. Can anyone help me for this?
Here is my code and output:
if pickerView == pkr1 {
let myView = UIView(frame: CGRect(x: 5, y: 0, width: pickerView.bounds.width - 10, height: Utilities.isDeviceiPad() ? 80.0 : 60.0))
myView.backgroundColor = UIColor.white
myView.isOpaque = false
let myLabel = UILabel(frame: CGRect(x: 5, y: 0, width: myView.bounds.width - 10, height: myView.frame.height))
myLabel.text = arrTemp[row]
// myLabel.center.y = myView.center.y
myLabel.textAlignment = .center
myLabel.numberOfLines = 2
myLabel.backgroundColor = UIColor.white
myLabel.alpha = CGFloat(1)
myLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
myView.addSubview(myLabel)
return myView
}
Output:
The reason why the selected view is transparent is that UIPickerView subview, that holds all the content, uses CAGradientLayer as a mask to achieve the depth effect. For some reason, Apple decided to make the selected view transparent as well. We can't simply rid of that mask because we will lose that fancy effect. So we need to figure out more about that gradient.
Here is how this gradient looks. I've added the alpha channel values so you could see how this gradient behave.
As you can see, it has 6 colors and everything is fine except the two values in the middle. They are 0.8 instead of 1.0, that what makes you selected view transparent.
This is a very bad approach and I strongly do not recommend do that but if you need to make your selected item opaque no matter what, you can simply create a subclass of you UIPickerView and change the gradient like that.
class MyPickerView: UIPickerView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
self.changeGradientLayer()
}
func changeGradientLayer() {
guard let sublayers = self.subviews.first?.layer.sublayers else { return }
for case let sublayer as CAGradientLayer in sublayers {
sublayer.colors = [UIColor.black.withAlphaComponent(0).cgColor,
UIColor.black.withAlphaComponent(0.71).cgColor,
UIColor.black.withAlphaComponent(1.0).cgColor,
UIColor.black.withAlphaComponent(1.0).cgColor,
UIColor.black.withAlphaComponent(0.71).cgColor,
UIColor.black.withAlphaComponent(0).cgColor]
}
}
}
Hope it helps you.
Please add your view in picker view by below method and check it...hope it will work..
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView {
let myView = UIView(frame: CGRect(x: 5, y: 0, width: pickerView.bounds.width - 10, height: Utilities.isDeviceiPad() ? 80.0 : 60.0))
myView.backgroundColor = UIColor.white
myView.isOpaque = false
let myLabel = UILabel(frame: CGRect(x: 5, y: 0, width: myView.bounds.width - 10, height: myView.frame.height))
myLabel.text = arrTemp[row]
// myLabel.center.y = myView.center.y
myLabel.textAlignment = .center
myLabel.numberOfLines = 2
myLabel.backgroundColor = UIColor.white
myLabel.alpha = CGFloat(1)
myLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
myView.addSubview(myLabel)
return myView
}

UIView / UIControl ignoring frame rect. Always fullscreen

I am trying to build custom components in Swift 2.2, and obviously missing something essential. My views ignore the frame rect.
In the containing view, I add the view programmatically like this:
let arrowControl = ArrowControl(frame: CGRect(x: 100 , y: 100, width: 300, height: 300))
self.view.addSubview(arrowControl)
In fact, even if I programmatically add a UIView or UIControl, without subclassing it - then the view ignores the frame rect, and occupies the entire screen.
Here are the essential parts of my custom view:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
renderComponent(frame)
}
EDIT>>>>
If I change my example, and remove renderComponent, I still get the same problem.
override func layoutSubviews() {
super.layoutSubviews()
self.clipsToBounds = true
backgroundColor = UIColor.yellowColor()
// renderComponent(self.frame)
}
Or even doing the following ignores the frame rect, and gives me a full screen block of colour:
let test = UIView(frame: CGRect(x: 100 , y: 100, width: 300, height: 300))
test.backgroundColor = UIColor.redColor()
self.view.addSubview(test)
Doh! I worked it out. It turns out it was my own fault, as I'd implemented the following in the containing view:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
view.frame = UIScreen.mainScreen().bounds
}
}

Resources