Crashed when accessing property defined in BFPaperButton subclass - ios

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.

Related

Background Colour in UIView not showing

I have a UIView that should show two colours, red and orange, based on the value of rating: Double The problem is that when I run the app nothing is showing up. In my output log the function prints that it has run and that the rating value is what it should be. So I am not sure why nothing is showing up when I run the app, I just see white.
class RatingViewController: UIView {
var rating: Double = 1.0
var rate: Double? {
didSet {
rating = rate!
setUpView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setUpView() {
Self.backgroundcolor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
You have to add setUpView in init(: method
class RatingViewController: UIView {
var rating: Double = 1.0
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init(frame: CGRect, rate: Double) {
super.init(frame: frame)
self.rating = rate
setUpView()
}
private func setUpView() {
self.backgroundColor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
Now you can call this class with custom init method like that:
// chnage frame and rate according to your requirment
let rView = RatingViewController(frame: CGRect(x: 0, y: 0, width: 320, height: 300), rate: 2.0)

Collection Cell Content Size

I added a view as parent red view in CollectionViewCell and the next blue subview at the center of the parent view. It works correctly and the sub view goes at the center of the parent view before collection cell size is not changed. But, The cell size is changed by conforming the method from UICollectionViewDelegateFlowLayout protocol and the view is not centered of the cell correctly. How I can solve this issue ?
class ItemCollectionViewCell: UICollectionViewCell {
var parentView: UIView!
var circularView: UIView!
var itemImage: UIImageView!
var itemName: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
// self.updateView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.updateView()
}
func updateView(){
self.clipsToBounds = true
self.parentView = UIView(frame: CGRect(x: 0.0, y: 0.0, width:
self.frame.size.width, height: self.frame.size.height))
self.parentView.backgroundColor = UIColor.red
self.circularView = UIView(frame: CGRect(x: 0, y: 0, width:
self.parentView.frame.size.width / 4 , height:
self.parentView.frame.size.width / 4 ))
self.circularView.backgroundColor = UIColor.blue
self.addSubview(parentView)
self.parentView.addSubview(self.circularView)
self.circularView.center = self.parentView.center
}
}
1]2
Try this code
self.circularView.center = CGPoint(x: self. parentView.bounds.midX, y: self. parentView.bounds.midY)
self.parentView.addSubview(self.circularView)
self.addSubview(parentView)
Try doing:
self.circularView.center = CGPointMake(CGRectGetMidX(self.parentView.bounds), CGRectGetMidY(self.parentView.bounds))
Why this should work? Try checking values of self.parentView.center for each cell, they might not be what you want them to be, because center property gives values with respect to parent view coordinate system.
Remove the following line from updateView()
self.circularView.center = self.parentView.center
Please add it to the following method:
override func layoutSubviews() {
super.layoutSubviews()
self.circularView.center = self.parentView.center
}
e.g.
class ItemCollectionViewCell: UICollectionViewCell {
var parentView: UIView!
var circularView: UIView!
var itemImage: UIImageView!
var itemName: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
// self.updateView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.updateView()
}
func updateView(){
self.clipsToBounds = true
self.parentView = UIView(frame: CGRect(x: 0.0, y: 0.0, width:
self.frame.size.width, height: self.frame.size.height))
self.parentView.backgroundColor = UIColor.red
self.circularView = UIView(frame: CGRect(x: 0, y: 0, width:
self.parentView.frame.size.width / 4 , height:
self.parentView.frame.size.width / 4 ))
self.circularView.backgroundColor = UIColor.blue
self.addSubview(parentView)
self.parentView.addSubview(self.circularView)
}
override func layoutSubviews() {
super.layoutSubviews()
self.circularView.center = self.parentView.center
}
}

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")
}
}

IBDesignable inside UITableViewCell not rendered properly

I have a grid of custom "text below image" UIButtons inside a static UITableViewCell.
To make 8 UIButtons form 4x2 grid, I put them into 2 horizontal UIStackViews and wrapped them with a vertical UIStackView.
You can see details in the screenshots below.
It runs perfect on simulator and real devices, but not rendered properly in Storyboard. I'd like to know if these are something missing in my code or some settings I should check/uncheck in Storyboard.
Runtime behavior(works well)
In Storyboard(chaos)
Outline
Custom Button:
import UIKit
#IBDesignable class YPTextBelowImageButton: UIButton {
#IBInspectable var heightRatio: CGFloat = 0.8 {
didSet {
self.setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 0, y: 0, width: contentRect.size.width, height: contentRect.size.height * heightRatio)
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 0, y: contentRect.size.height * heightRatio, width: contentRect.size.width, height: contentRect.size.height * (1 - heightRatio))
}
}
Preview:
func prepareForInterfaceBuilder not working
override func prepareForInterfaceBuilder() {
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}

TapGestureRecogniser not getting called when a subview

I have a view A that handles UITapGestureRecogniser. When it's on its own everything works great.
I have another View B that holds six of the View A objects. When I add View B to my ViewController the UITapGestureRecogniser stops working.
I have isUserInteractionEnabled = true on all the views.
Can anyone spot why it's not working?
How can I check if the tapGestures are being activated upon touch?
Thanks
Note: The ViewController doesn't have any UIGestureRecognisers on it
SingleView
class QTSingleSelectionView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
fileprivate func initialize() {
let tap = UITapGestureRecognizer(target: self, action: #selector(onTapListener))
addGestureRecognizer(tap)
}
func onTapListener() {
print("tap")
_isSelected.toggle()
setSelection(isSelected: _isSelected, animated: true)
}
}
Multiple views
class QTMutipleChoiceQuestionSelector: UIView {
var selectors: [QTSingleSelectionView] = []
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
fileprivate func initialize() {
for selector in selectors {
selector.removeFromSuperview()
}
selectors.removeAll()
guard let dataSource = dataSource else { return }
let count = dataSource.numberOfAnswers(self)
for index in 0..<count {
let selector = QTSingleSelectionView()
selector.frame = CGRect(x: 0, y: topMargin + heightOfSelector*CGFloat(index), width: frame.width, height: heightOfSelector)
selector.text = dataSource.mutipleChoiceQuestionSelector(self, textForAnswerAtIndex: index)
selector.selected = dataSource.mutipleChoiceQuestionSelector(self, isItemSelectedAtIndex: index)
selector.isUserInteractionEnabled = true
addSubview(selector)
}
}
}
ViewController
lazy var multipleChoiceView: QTMutipleChoiceQuestionSelector = {
let selector = QTMutipleChoiceQuestionSelector()
selector.dataSource = self
selector.isUserInteractionEnabled = true
return selector
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(multipleChoiceView)
}
I have tested your code. The problem is in line:
let selector = QTMutipleChoiceQuestionSelector() //Wrong!
This line initializes the view with frame (0,0,0,0), that means it cannot receive any touch event, even if you could see it.
The solution is giving the view a size to receive events:
let selector = QTMutipleChoiceQuestionSelector(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height))
Try This -
Instead of :
for index in 0..<count {
let selector = QTSingleSelectionView()
selector.frame = CGRect(x: 0, y: topMargin + heightOfSelector*CGFloat(index), width: frame.width, height: heightOfSelector)
selector.text = dataSource.mutipleChoiceQuestionSelector(self, textForAnswerAtIndex: index)
selector.selected = dataSource.mutipleChoiceQuestionSelector(self, isItemSelectedAtIndex: index)
selector.isUserInteractionEnabled = true
addSubview(selector)
}
You should use :
for index in 0..<count {
let aFrame: CGRect = CGRect(x: 0, y: topMargin + heightOfSelector*CGFloat(index), width: frame.width, height: heightOfSelector)
let selector = QTSingleSelectionView(frame: aFrame)
selector.text = dataSource.mutipleChoiceQuestionSelector(self, textForAnswerAtIndex: index)
selector.selected = dataSource.mutipleChoiceQuestionSelector(self, isItemSelectedAtIndex: index)
selector.isUserInteractionEnabled = true
addSubview(selector)
}
This will call the appropriate initializer which is init(frame: CGRect) which has the initializer() inside it. But right now your code call's default init() function of the UIView which does not have your custom initialize.

Resources