Adding and changing a custom UIView programmatically (Swift) - ios

I am trying to create a custom UIView that I can use in my other UIViewControllers.
The custom view:
import UIKit
class customView: UIView {
override init(frame: CGRect) {
super.init(frame:frame)
let myLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 250, height: 100))
addSubview(myLabel)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Then I want to add it in to a separate UIViewController:
let newView = customView(frame:CGRectMake(0, 0, 500, 400))
self.view.addSubview(newView)
This works to display the view, but what do I need to add to be able to change the properties (such as myLabel) from the UIViewController that is embedding the customView?
I'd like to be able to access and change the label from the viewController, allowing me to change text, alpha, font, or hide the label using dot notation:
newView.myLabel.text = "changed label!"
Trying to access the label now gives the error "Value of type 'customView' has no member 'myLabel'"
Thanks so much for any help!

This is because the property myLabel is not declared at the class level. Move the property declaration to class level and mark it public. Then you will be able to access it from outside.
Something like
import UIKit
class customView: UIView {
public myLabel: UILabel?
override init(frame: CGRect) {
super.init(frame:frame)
myLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 250, height: 100))
addSubview(myLabel!)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Related

Error "EXC BAD ACCESS"with subclass in Swift

I made 3 files GameViewController, GameView, GameItemView but if GameItemView inherits GameView than
EXC BAD ACCESS error appeared
like below code.
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
gameView.gameViewController = self
}
//....
}
class GameView: UIView {
weak var gameViewController: GameViewController! //when GameViewController will appear, BAD ACCESS error appear and stop here
weak var gameItemView: GameItemView!
override init(frame: CGRect) {
super.init(frame: frame)
gameItemView = GameItemView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.addSubview(gameItemView)
}
//....
}
class GameItemView: GameView {
override init(frame: CGRect) {
super.init(frame: frame)
//add some views
}
//...
}
I wrote some code of GameItemView on GameView before. But GameView's code became so long, so I moved a part of them to GameItemView. But then the error appeared. How can I solve it?
UPDATE:
I'll add the code where I got the error. I'm sorry I forgot to write it.
var gameGameViewController: GameViewController!
Why are you inheriting GamveViewItem from GameView? If you wanted to do so then why are coding like above which causes recursive calls to the init of GamveViewItem & GameView. Please either change the parent class of the GamveViewItem to UIView or some other or break the recursive pattern caused by the init.
GameView's init
override init(frame: CGRect) {
super.init(frame: frame)
// Here you're calling GameItemView's init, now go to GameItemView's init
gameItemView = GameItemView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.addSubview(gameItemView)
}
GameItemView's init
override init(frame: CGRect) {
// You're calling GameView's init here and it goes back to
// there and then this keep on happening
super.init(frame: frame)
//add some views
}
try this:
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
gameView.gameViewController = self
}
//....
}
class GameView: UIView {
var gameViewController = GameViewController()
var gameItemView = GameItemView()
override init(frame: CGRect) {
super.init(frame:frame)
gameItemView = GameItemView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.addSubview(gameItemView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameItemView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
//add some views
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

UICollectionReusableView Header not resizing on rotation - constraints?

I have a UICollectionReusableView for a UICollectionView Header Section in code as follows:
var headingLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
headingLabel = UILabel(frame: CGRectMake(12.0,12.0,frame.width,21.0))
headingLabel.numberOfLines = 1
headingLabel.textAlignment = NSTextAlignment.Center
headingLabel.textColor = UIColor.blackColor()
self.backgroundColor = UIColor.whiteColor()
self.addSubview(headingLabel)
}
I just want the heading to be centered in the display. This is fine so long as I don't rotate the device. If I rotate the device, the label is no longer in the center of the screen.
My guess is this is super simple, I just can't seem to find the answer. My assumption is that I could just programatically add constraints, but I can't quite work out how to do that.
Alternatively, I think there might be some way to force it to redo the headers on a layout change, but I couldn't get that working either.
Any pointers in the right direction would be hugely appreciated.
Many thanks.
You have to invalidate the layout BEFORE the collectionView is re-laid out (which happens in viewDidLayoutSubviews), therefore you must invalidate it in viewWillLayoutSubviews.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Handle rotation
collectionView.collectionViewLayout.invalidateLayout()
}
import Foundation
import UIKit
import MaterialComponents
class SectionHeaderAppointment:UICollectionReusableView {
var sectionHeaderlabel: UILabel!
var nameflatButton:MDCButton!
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
print("Rotation")
sectionHeaderlabel.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
nameflatButton.frame = CGRect(x: (frame.width - (frame.width/3))/2, y: 6, width: frame.width/3, height: frame.height - 8)
}
}

Can a UIView subclass create other custom view instances?

I have a UIView subclass called LifeCounter, of which I want to create one for each player. I was able to add two views to Main.storyboard, set their class to LifeCounter through the Attributes panel and create multiple instances that way, connect to the view controller, change properties, etc.
What I was thinking about now was creating a larger view, GameHeader, that will hold the LifeCounters and some other supplementary information such as time, game reset button, etc. GameHeader is a UIView subclass, but I can't get it to draw my LifeCounter views in the simulator and I have no idea why.
GameHeader is currently a view dragged into the storyboard, and given it's class with the Attributes panel.
GameHeader.swift
import UIKit
class GameHeader: UIView {
// MARK: Properties
// The Frame
let topFrame = CGRect(x: 0, y: 0, width: 320, height: 200)
// MARK: Initlization
required init() {
super.init(frame: topFrame)
// Adds the player one counter
let playerOneCounter = LifeCounter()
addSubview(playerOneCounter)
}
required init(coder aDecoder: NSCoder) {
// Calls the super class (UIView) initializer
super.init(coder: aDecoder)
}
}
LifeCounter.swift
import UIKit
class LifeCounter: UIView {
// MARK: Propterties
// Starting life total
var lifeTotal = 20 {
didSet {
// Updates the layout whenever the lifeTotal is updated
setNeedsLayout()
}
}
// Creates the UI Labels
// All created views need a defined frame for where they sit
// Is this neccesary outside of the init? Is there a better way?
var counter = UILabel(frame: CGRect(x: 0, y: 20, width: 100, height: 90))
var playerName = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
var winner = ""
// MARK: Initlization
// First init. LifeCounter takes a frame parameter, the adds the labels etc.
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
self.addLifeCounter()
}
required init(coder aDecoder: NSCoder) {
// Calls the super class (UIView) initializer
super.init(coder: aDecoder)
}
func addLifeCounter() {
print("addLifeCounter is running")
// Styles life counter label
counter.textColor = UIColor.blackColor()
counter.textAlignment = .Center
counter.font = UIFont.boldSystemFontOfSize(72)
counter.text = String(lifeTotal)
// Styles playerName label
playerName.text = "Player Name"
playerName.textAlignment = .Center
// Button
let minusButton = UIButton(frame: CGRect(x: 5, y: 110, width: 40, height: 40))
let plusButton = UIButton(frame: CGRect(x: 55, y: 110, width: 40, height: 40))
minusButton.backgroundColor = UIColor.redColor()
plusButton.backgroundColor = UIColor.blueColor()
// Button action
minusButton.addTarget(self, action: "minusLife:", forControlEvents: .TouchDown)
plusButton.addTarget(self, action: "plusLife:", forControlEvents: .TouchDown)
addSubview(playerName)
addSubview(counter)
addSubview(minusButton)
addSubview(plusButton)
}
// MARK: Button actions
func minusLife(minusButton: UIButton) {
lifeTotal -= 1
counter.text = String(lifeTotal)
}
func plusLife(plusButton: UIButton) {
lifeTotal += 1
counter.text = String(lifeTotal)
}
}
init(coder:) is the initializer that is called when a view is created from a storyboard or nib. If you move the code that creates and adds LifeCounter instances there, it should work.
A good strategy to make it more reusable is to create a setup method that is called from both initializers so that it will run whether it comes from a storyboard/nib or it is instantiated programmatically.
class GameHeader: UIView {
let topFrame = CGRect(x: 0, y: 0, width: 320, height: 200)
required init() {
super.init(frame: topFrame)
self.setupSubviews()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupSubviews()
}
func setupSubviews() {
let playerOneCounter = LifeCounter()
addSubview(playerOneCounter)
}
}
I discovered the error was in putting the LifeController drawing code inside the init(frame:) block instead of with the Coder block. When a view is added through Interface Builder, it uses init(coder:) instead of init(frame:)

how to declare UIView globally in swift?

Unable to create View globally. My code is :
required init(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
// CODING
class ViewController: UIViewController {
//Unable to create View globally, kindly help me,,
//Error coming like this
required init(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
var view11:UIView
var img_view11:UIImageView
.
.
.
#IBAction func long(sender: UILongPressGestureRecognizer)
{
var data1: NSData = UIImagePNGRepresentation(img.image)
view11 = UIView(frame: CGRect(x: 25, y: 0, width: 300, height: 200))
img_view11 = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
img_view11.image = UIImage(data: data1)
view11.addSubview(img_view11)
self.vw.addSubview(view11)
}
}
If we erase that "required" line, we get this error:
"fix-it Insert \n required initializer."
after that, if we run, then the thread error is thrown.
Outside the class do:
var imageView: UIImageView = UIImageView()
Then you will be able to use it globally
You can get rid of the error by replacing the fatalError code with super.init(coder: aDecoder).
This initialization is required for objects that are initialized from a nib (or storyboard), I believe.

Crashed when accessing property defined in BFPaperButton subclass

I am working on creating a customized button in Swift.
When I subclassed UIButton class, it works fine.
But it crashed when I replace super class with BFPaperButton (https://github.com/bfeher/BFPaperButton)
I have fixed the init method name conversion error with:
#define initFlat initWithFlat
#define initFlatWithFrame initWithFlatWithFrame
#define initRaised initWithRaised
#define initRaisedWithFrame initWithRaisedWithFrame
Then I got EXC_BAD_ACCESS when accessing the new defined property:
(An UserCountButton Instance).countLabel.text = ...
This is my implementation:
import UIKit
class UserButton: BFPaperButton {
let footerLabel = UILabel()
override init() {
super.init(flatWithFrame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width / 3, height: 100))
addSubview(footerLabel)
layer.contentsScale = UIScreen.mainScreen().scale
footerLabel.frame = CGRect(x: 0, y: 60, width: bounds.width, height: 40)
footerLabel.textColor = UIColor.grayColor()
footerLabel.textAlignment = .Center
footerLabel.font = UIFont.systemFontOfSize(12)
}
override init(raisedWithFrame frame: CGRect) {
super.init(raisedWithFrame: frame)
}
override init(flat: ()) {
super.init(flat: ())
}
override init(flatWithFrame frame: CGRect) {
super.init(flatWithFrame: frame)
}
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
class UserCountButton: UserButton {
let countLabel = UILabel()
override init() {
super.init()
addSubview(countLabel)
countLabel.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 90)
countLabel.textColor = UIColor.darkGrayColor()
countLabel.textAlignment = .Center
countLabel.font = UIFont.systemFontOfSize(32)
}
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
Is that a bug or some what? How can I fix it? (I'm using Xcode 6 Beta 5)
From what I can tell, the problem is that you are trying to change stuff on your countLabel, but you have defined it via let which makes it immutable. Try changing that to var, and then setting all of it's properties.
Also, it should be noted that I believe you need to do all the subview initialization BEFORE you call addSubview, for both your UserCountButton and your UserButton.

Resources