Multiple checkbox buttons in swift 3 - swift

I have working code for a checkbox in swift, but the project I'm looking to build has about 50 or so checkboxes. In keeping with the goal of less code is best I'm wondering if there's a better way to write it all out than to just copy and paste. I tried linking multiple buttons to one iboutlet but I guess that was not the way to go seeing as how a different button would register selected then the one tapped.
working code:
#IBOutlet weak var buttonOne: UIButton!
var isButtonClicked: Bool!
override func viewDidLoad() {
super.viewDidLoad()
isButtonClicked = false
}
#IBAction func buttonClicked(_ sender: UIButton) {
if isButtonClicked == true {
isButtonClicked = false
}
else {
isButtonClicked = true
}
if isButtonClicked == true {
buttonOne.setImage(#imageLiteral(resourceName: "ButtonClicked"), for: .normal)
}
else {
buttonOne.setImage(#imageLiteral(resourceName: "ButtonUnclicked"), for: .normal)
}
}

I would suggest you to create a class for the checkboxes. Example:
class CheckBox: UIButton {
var checked = false {
didSet {
if checked == true {
self.setImage(#imageLiteral(resourceName: "ButtonClicked"), for: .normal)
}
else {
self.setImage(#imageLiteral(resourceName: "ButtonUnclicked"), for: .normal)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func tapped() {
self.checked = !self.checked
if checked == true {
self.setImage(#imageLiteral(resourceName: "ButtonClicked"), for: .normal)
}
else {
self.setImage(#imageLiteral(resourceName: "ButtonUnclicked"), for: .normal)
}
}
}
You can then use this class to create checkboxes programmatically using a for loop. Example:
for i in 0..<50 {
let checkbox = CheckBox(frame: CGRect(x: 0, y: i * 40, width: 40, height: 40))
checkbox.tag = 1
self.someView.addSubview(checkbox)
}

Related

How do I call a fromRootViewController method within a UIView class?

EDIT: This is a very beginner level question, so I apologise in advance
This is a sub part of a homework assignment I'm trying to crack but seems kind of impossible. I have implemented GADRewardBasedVideoAdDelegate to my UIView for Rewarded ads on Google Admob, and managed to implement all other instances of this delegate within "PopUp", which is a subclass of UIView.
However, this one method seems impossible to implement, which is:
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self)
}
where the issue is the ".present(fromRootViewController: self) portion of the code since the self refers not to a ViewController but to a UIView().
I honestly don't know how to tackle this situation, so I do not have anything to show for my attempts at this issue. I would really be grateful for any guidance that I can get on this topic, I've been stuck here for the better part of 2 days.
Here is the popUp class in case it helps make my code clearer
import UIKit
import GoogleMobileAds
class Popup: UIView, GADRewardBasedVideoAdDelegate {
func rewardBasedVideoAd(_ rewardBasedVideoAd: GADRewardBasedVideoAd, didRewardUserWith reward: GADAdReward) {
let vc = MainVC()
vc.lifeNumber += 3
vc.numberOfLivesLabel.text = "\(vc.lifeNumber)"
}
func rewardBasedVideoAdDidClose(_ rewardBasedVideoAd: GADRewardBasedVideoAd) {
GADRewardBasedVideoAd.sharedInstance().load(GADRequest(), withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
}
let playButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "icon"), for: .normal)
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
#objc func play() {
print("Play button pressed.")
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self) //<---------ERRONEOUS CODE
}
}
override init(frame: CGRect) {
super.init(frame: frame)
GADRewardBasedVideoAd.sharedInstance().load(GADRequest(), withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
GADRewardBasedVideoAd.sharedInstance().delegate = self
self.addSubview(popUpContainer)
playButton.centerXAnchor.constraint(equalTo: popUpContainer.centerXAnchor),
playButton.topAnchor.constraint(equalTo: popUpContainer.topAnchor, constant: 15),
playButton.heightAnchor.constraint(equalToConstant: 300)
playButton.widthAnchor.constraint(equalToConstant: 300)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Could not run init(coder)")
}
}
And here is my MainVC()
class MainVC: UIViewController {
var lifeNumber: Int = 3
lazy var numberOfLivesLabel: UILabel = {
let label = UILabel()
label.text = "\(lifeNumber)"
label.font = UIFont(name: "Copperplate", size: 25)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let buttonOne: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Hit me!", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
#objc func buttonPressed() {
let popUpView = Popup()
self.view.addSubview(popUpView)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(buttonOne)
view.addSubview(numberOfLivesLabel)
buttonOne.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
buttonOne.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
buttonOne.heightAnchor.constraint(equalToConstant: 300).isActive = true
buttonOne.widthAnchor.constraint(equalToConstant: 300).isActive = true
numberOfLivesLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
numberOfLivesLabel.bottomAnchor.constraint(equalTo: buttonOne.topAnchor, constant: 50).isActive = true
}
}
This code from hacking with swift will help.
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
Use the extension to get the popups parent view controller.
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self.findViewController())
}
You might want to put it on the main queue as well
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
DispatchQueue.main.async {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self.findViewController())
}
}

How can I access a property of a subclass of UIView in a SwiftUI View?

I made a simple application. I made a subclass of a UIView which presents a UIButton. Whenever I tap the button, the value of the "number" property increases by 1. I integrated this custom UIView in a SwiftUI View by the help of the UIViewRepresentable protocol. How can I access the "number" property in the SwiftUI View?
import UIKit
class CustomUIView: UIView {
var number = 0
override init(frame:CGRect) {
super.init(frame: frame)
createButton()
}
required init?(coder: NSCoder) {
fatalError("error")
}
private func createButton () {
let button = UIButton();
button.setTitle("Add", for: .normal)
button.setTitleColor(UIColor.blue, for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
self.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
#objc func buttonTapped(sender: UIButton) {
number += 1
print(number)
}
}
import SwiftUI
struct CustomButton: UIViewRepresentable {
func makeUIView(context: Context) -> CustomUIView {
let customButton = CustomUIView()
return customButton
}
func updateUIView(_ view: CustomUIView, context: Context) {
}
}
struct ContentView : View {
var body: some View {
NavigationView {
Text("I want to show here the value of the number property")
CustomButton().frame(height: 50)
}
}
}
I would recommend using a Binding in your custom view so that the SwiftUI view is still the source of truth of value.
class CustomUIView: UIView {
var number: Binding<Int>!
override init(frame:CGRect) {
super.init(frame: frame)
createButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
createButton()
}
private func createButton () {
let button = UIButton();
button.setTitle("Add", for: .normal)
button.setTitleColor(UIColor.blue, for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
self.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
#objc func buttonTapped(sender: UIButton) {
number.value += 1
}
}
struct CustomButton: UIViewRepresentable {
var binding: Binding<Int>
init(number: Binding<Int>) {
self.binding = number
}
func makeUIView(context: Context) -> CustomUIView {
let customButton = CustomUIView()
customButton.number = binding
return customButton
}
func updateUIView(_ view: CustomUIView, context: Context) {
}
}
struct ContentView : View {
#State var number = 0
var body: some View {
NavigationView {
Text("I want to show here the value of the number property")
.lineLimit(nil)
Text("Current value: \(number)")
CustomButton(number: $number).frame(height: 50)
}
}
}

addTarget() does not work on custom extensions

I made a custom UIView() and i'm trying to add a okButton to it.
However. I have added the button but when showing my UIView in one of my ViewControllers, But the button won't work. This is my code:
class Luna: UIView {
let luna = UIView()
let okButton = UIButton()
typealias completionHandler = (_ success:Bool) -> Void
// Not yet implemented
open var touchOutsideToHide: Bool = true;
private var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
override init(frame: CGRect) {
super.init(frame: frame)
okButton.setTitleColor(.blue, for: .normal)
okButton.setTitle("Okay", for: .normal)
okButton.titleLabel?.font = UIFont(name: "avenirnext-demibold", size: 16)
let gesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.pressButton(_:)))
okButton.addGestureRecognizer(gesture)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
//The target function
#objc func pressButton(_ sender: UIButton){ //<- needs `#objc`
// .. //
print("btn hit")
}
I do not get btn hit in the console and the tap isn't registered
the button is visible, it's added as a subview:
Screenshot
Button have default touch gesture, you don't need to add it. just add target as follow to fix your issue. Create protocol of your customer class and declare delegate for it in same class as follow.
protocol LunaDelegate: class {
func okButtonTapped(sender: UIButton)
}
class Luna: UIView {
let luna = UIView()
let okButton = UIButton()
typealias completionHandler = (_ success:Bool) -> Void
weak var delegate: LunaDelegate?
// Not yet implemented
open var touchOutsideToHide: Bool = true;
private var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
override init(frame: CGRect) {
super.init(frame: frame)
okButton.setTitleColor(.blue, for: .normal)
okButton.setTitle("Okay", for: .normal)
okButton.titleLabel?.font = UIFont(name: "avenirnext-demibold", size: 16)
okButton.addTarget(self, action: #selector(pressButton(sender:)), for: .touchUpInside)
}
#IBAction func pressButton(sender: UIButton) {
if let selfDelegate = self.delegate {
selfDelegate.okButtonTapped(sender: sender)
}
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Then after implement your code as follow to use your custom view. When your button will pressed then delegate method will be call in your view controller.
Usage:
class LunaViewController: UIViewController, LunaDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let luna = Luna(frame: CGRect.init(x: 100, y: 100, width: 100, height: 40))
luna.delegate = self
self.view.addSubview(luna)
}
// MARK:- Luna Delegate
func okButtonTapped(sender: UIButton) {
print("OK Button Pressed From Luna View")
}
}
This is the proper solution which I have already using in my current project. I hope this will help you.

Creating a checkbox programmatically using images as the UIButtons background

I have seen multiple questions on how to implement a checkbox by just changing the background image when clicked, but I don't understand why the checkbox only shows when I add it to my ViewController setupViews.
It simply will not show up or change when I have all the functionality in my actionButton function. Should i be using a protocol and delegate set up to get my button showing changing when clicked? Below is my code, I am hoping someone can shed some light as to what I am missing here?
class MainMenuViewController: UIViewController {
let clickingCheckbox = ClickingCheckbox()
var checkbox = UIImage(named: "Checked_Checkbox")
var empty_checkbox = UIImage(named:"Empty_Checkbox")
var isBoxClicked: Bool!
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
self.backgroundImage.addSubview(contentView)
self.contentView.addSubview(clickingCheckbox)
clickingCheckbox.snp.makeConstraints { (make) in
make.top.equalTo(signInButton.snp.bottom).offset(-MainMenuViewController.padding)
make.leading.equalTo(buttonView)
make.width.equalTo(signInButton).multipliedBy(0.2)
make.height.equalTo(clickingCheckbox.snp.width)
}
clickingCheckbox.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
clickingCheckbox.setImage(empty_checkbox, for: UIControlState.normal) #The checkbox only shows on screen if I put it here, however it does nothing when clicked!
}
#objc func buttonAction(_ sender: ClickingCheckbox) {
if isBoxClicked == true {
isBoxClicked = false
}else{
isBoxClicked = true
}
if isBoxClicked == true {
sender.setImage(checkbox, for: UIControlState.selected)
}else{
sender.setImage(empty_checkbox, for: UIControlState.normal)
}
print("test")
}
In my Class I have.....
class ClickingCheckbox: UIButton {
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
I have tried keeping the buttonAction functionality in the class, but that didn't work, I have tried a multiple different ways but can't get my head around how to show it working. All other advice is given to implement using IBOutlets so this would be really helpful for me to understand. Thanks
Try something like this:
class ClickingCheckbox: UIButton {
convenience init() {
self.init(frame: .zero)
self.setImage(UIImage(named: "Empty_Checkbox"), for: .normal)
self.setImage(UIImage(named: "Checked_Checkbox"), for: .selected)
self.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
}
#objc private func buttonTapped() {
self.isSelected = !self.isSelected
}
}

Custom view failed to render instance of the agent - crashes when setting a UIImage

I have a custom view with a button.
When adding the view in storyboard i get an error:
failed to render instance of the agent
When i debug the view from interface builder it crashes when it sets the image of the button.
CODE:
#IBDesignable
class UIHeader: UIView {
private lazy var backButton: UIButton = {
let btn = UIButton()
btn.tintColor = UIColor.lightGrayColor()
btn.translatesAutoresizingMaskIntoConstraints = false
//CRASH HERE//
btn.setImage(UIImage(named: "prev")!.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
btn.addTarget(self, action: #selector(UIHeader.OnBackButtonClickLister(_:)), forControlEvents: .TouchUpInside)
return btn
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
}
extension UIHeader {
#IBInspectable
var backButtonImage: UIImage? {
get {
return backButton.imageForState(.Normal)
}
set (newImage) {
backButton.setImage(newImage?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
}
}
}
extension UIHeader {
private func setupView() {
translatesAutoresizingMaskIntoConstraints = false
addSubview(backButton)
}
}