Interacting with custom UIView inside view controller - ios

I am created a UIView which I want to display in my view controller. I have created the UIView and it shows with other UI components, but the problems I have now is I con not interact with the elements of the on the UIView.
below is my code
class SliderView: CustomView {
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var sliderImage: UIImageView!
#IBOutlet weak var sliderText: UILabel!
override func initialize() {
super.initialize()
let name = String(describing: type(of: self))
let nib = UINib(nibName: name, bundle: .main)
nib.instantiate(withOwner: self, options: nil)
self.addSubview(self.containerView)
self.containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.containerView.topAnchor.constraint(equalTo: self.topAnchor),
self.containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return sliderImage.frame.contains(point)
}
#IBAction func clickme(_ sender: UIButton) {
print("SWIPPERD minmax22g")
}
}
in the viewcontroller
weak var sliderView: SliderView!
override func loadView() {
super.loadView()
let sliderView = SliderView()
self.view.addSubview(sliderView)
NSLayoutConstraint.activate([
sliderView.topAnchor.constraint(equalTo: self.view.topAnchor),
sliderView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
sliderView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
self.sliderView = sliderView
}
override func viewDidLoad() {
super.viewDidLoad()
sliderView.sliderText.text = "HOOOOO WORKS"
}

1- You shouldn't add an outlet as a subview again inside the custom view
#IBOutlet weak var containerView: UIView!
self.addSubview(self.containerView)
add this method and use it to get an instance
static func getInstance() -> SliderView {
return Bundle.main.loadNibNamed("SliderView", owner: self, options: nil)![0] as! SliderView
}
2- This will make the imageView the only active frame inside the view
return sliderImage.frame.contains(point)
3- Don 't add the subview inside loadView , add it inside viewDidLoad
let sliderView = SliderView()
to
let sliderView = SliderView.getInstance()

Related

UIView: how does the appearance() proxy work?

I have created a simple custom UIView:
final class TestView: UIView {
var testColor: UIColor = .white {
didSet {
backgroundColor = testColor
}
}
}
Then I wrote this in my view controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var testView: TestView!
#IBOutlet weak var testView2: TestView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
TestView.appearance().testColor = .red
}
}
}
By doing this, I get an error:
Could you help me understanding what's wrong here and how to implement the UIAppearance proxy for any custom UIView?
Thank you for your help
You need to make the property #objc and dynamic:
final class TestView: UIView {
#objc dynamic var testColor: UIColor? = .white {
didSet {
backgroundColor = testColor
}
}
}
Worth noting: the UIAppearance proxy does NOT affect views which are already part of the view hierarchy.
So, in your example, adding #objc dynamic to your property will get rid of the crash, but you will not see any change to the two #IBOutlet views.
If you call it as part of viewDidLoad() (instead of DispatchQueue.main.asyncAfter):
override func viewDidLoad() {
super.viewDidLoad()
TestView.appearance().testColor = .red
}
The two #IBOutlet views will get the red background.
Or, if you add a new view to the hierarchy, it will get the red background:
class AppearanceTestViewController: UIViewController {
#IBOutlet weak var testView: TestView!
#IBOutlet weak var testView2: TestView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
TestView.appearance().testColor = .red
self.addAnotherTestView()
}
}
func addAnotherTestView() -> Void {
let v = TestView()
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
NSLayoutConstraint.activate([
v.widthAnchor.constraint(equalToConstant: 240.0),
v.heightAnchor.constraint(equalTo: v.widthAnchor),
v.centerXAnchor.constraint(equalTo: view.centerXAnchor),
v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// this newly added view WILL have a red background
}
}

Using UIView (Singleton) on different viewcontrollers

I have a UIView which have a button and some view to indicate sucess and failure. I am trying to use that UIView on other view controllers and receive the button action on called view controllers.
This is what i have tried so far
protocol FailViewDelegate: class {
func tryAgainTapped()
}
class AlertView: UIView {
static let instance = AlertView()
weak var delegate : FailViewDelegate?
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var messageLbl: UILabel!
#IBOutlet weak var dashIMageView: AnimatableImageView!
#IBOutlet weak var circleView: AnimatableView!
#IBOutlet weak var iconStatus: AnimatableImageView!
#IBOutlet weak var tryAgainButton: AnimatableButton!
#IBOutlet weak var parentView: UIView!
private override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("AlertView", owner: self, options: nil)
}
enum AlertType {
case success
case failure
}
func showAlert(alertType: AlertType, to: UIViewController) {
switch alertType {
case .success:
dashIMageView.image = UIImage(named: "circle-dash-blue")
circleView.backgroundColor = UIColor(hexString: "#4EBFFF")
titleLbl.text = "Success"
titleLbl.textColor = UIColor(hexString: "#4EBFFF")
messageLbl.text = "Your ticket has been created."
tryAgainButton.isHidden = true
iconStatus.image = UIImage(named: "icon-check")
case .failure:
dashIMageView.image = UIImage(named: "circle-dash-red")
circleView.backgroundColor = UIColor(hexString: "#EB3708")
titleLbl.text = "Failure"
titleLbl.textColor = UIColor(hexString: "#EB3708")
messageLbl.text = "There was an error, creating your ticket."
tryAgainButton.isHidden = false
iconStatus.image = UIImage(named: "icon-close")
}
parentView.center = to.view.center
to.view.addSubview(parentView)
}
func dismissAlert() {
parentView.removeFromSuperview()
}
#IBAction func tryAgainButtonTapped(_ sender: AnimatableButton) {
delegate?.tryAgainTapped()
}
}
This is how i have called the view
class CreateTicketViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
AlertView.sharedInstance.delegate = self
}
#IBAction func createTicketTapped(_ sender: AnimatableButton) {
AlertView.sharedInstance.showAlert(alertType: .failure, to: self)
}
}
extension CreateTicketViewController : FailViewDelegate {
func tryAgainTapped() {
print("Try Again Tapped")
}
}
This is the error that i got
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
(in dashIMageView.image = UIImage(named: "circle-dash-red")) and when i remove the dashImageView then the error occur for nextView and so on
You don't need to make it a singleton, which in this case (View) is a very uncommon approach, I think. You can create as many instances of that view as you want any time, anywhere (on other ViewControllers) you want and specify them, the way you want them to have.
When you want a view to be rendered and be visible, it always must be part of the view hierarchy in the current visible view controllers main view. And a view can only have ONE SUPERVIEW at the time, so whenever you add a (singleton) view to another superview, it will be removed from an other superview. If you want the same view on many view controllers (no problem), just don't let it be a singleton.
So first thing to do -> Remove the singleton design by commenting out that line:
class AlertView: UIView {
// make this line a comment or just remove it
// static let instance = AlertView()
weak var delegate : FailViewDelegate?
In your different view controllers you just create that instances of your AlertView and set the delegate correctly like this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myAlertView = AlertView()
myAlertView.delegate = self
// then you don't need this anymore
// AlertView.sharedInstance.delegate = self
}

Displaying custom UIView in a ViewController

I am creating a UIView which I want to display in my view controller. I have created the UIView and it shows, but the problems I have now are:
When I call the UIView in my view controller, I can no longer interact with the elements of the view controller. The CustomView I created has completely prevented the interaction with my view controller and I want to be able to interact with the UIViewController.
I want to hide the status bar which includes the battery percentage and network bar and other things so the view completely covers them. I implemented a code to cover them, but it returns an error.
below is my code
class SliderView: CustomView {
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var sliderImage: UIImageView!
#IBOutlet weak var sliderText: UILabel!
override func initialize() {
super.initialize()
let name = String(describing: type(of: self))
let nib = UINib(nibName: name, bundle: .main)
nib.instantiate(withOwner: self, options: nil)
self.addSubview(self.containerView)
self.containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.containerView.topAnchor.constraint(equalTo: self.topAnchor),
self.containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return sliderImage.frame.contains(point)
}
override var prefersStatusBarHidden: Bool {
return true
}
// THIS THROWS an error 'Property does not override any property from its superclass'
}
my UIView is called in my Viewcontroller like
weak var sliderView: SliderView!
override func loadView() {
super.loadView()
let sliderView = SliderView()
self.view.addSubview(sliderView)
NSLayoutConstraint.activate([
sliderView.topAnchor.constraint(equalTo: self.view.topAnchor),
sliderView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
sliderView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
sliderView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
self.sliderView = sliderView
}
override func viewDidLoad() {
super.viewDidLoad()
sliderView.sliderText.text = "HOOOOO WORKS"
}
NSLayoutConstraint.activate([
sliderView.topAnchor.constraint(equalTo: self.view.topAnchor),
sliderView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
sliderView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
sliderView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
Your custom view is covering the whole UIViewController. Therefore, it is not possible to interact with the UIViewController.
Just try to replace the second constraint with the following to cover only half of the screen's height:
sliderView.topAnchor.constraint.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -UIScreen.main.bounds.height*0.5)
This is just a suggestion to see the difference, i.e., not a solution.

How to use custom view with in swift?

I want to add a custom view in TableViewHeader. But when I run the following code it creates a Cycle and app stuck for any user interaction.
import UIKit
class ExpandableView: UIView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
var contentView: UIView!
#IBOutlet weak var checkMarkView: UIImageView!
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
public override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
}
private func setUpView() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "ExpandableView", bundle: bundle)
self.contentView = nib.instantiate(withOwner: self, options: nil).first as? UIView
addSubview(contentView)
contentView.center = self.center
contentView.autoresizingMask = []
contentView.translatesAutoresizingMaskIntoConstraints = true
}
}
I am using it as follows:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let frame = CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 60)
let expandabelView = ExpandableView(frame: frame)
return expandabelView
}
And it shows following error on run.
There may be many other ways, but I recommend you to make your ExpandableView reusable to improve performance.
First of all, simplify your ExpandableView class:
import UIKit
class ExpandableView: UITableViewHeaderFooterView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
#IBOutlet weak var checkMarkView: UIImageView!
}
Please do not miss that the superclass is UITableViewHeaderFooterView, not UIView.
Second, check the settings of your ExpandableView.xib:
The Custom View setting of the defined view needs to be ExpandableView.
When you cannot choose ExpandableView from the pull down list, you may need to input manually. Do not forget to check Inherit Module From Target.
The Custom View setting of the File's Owner needs to be empty.
If there's some class already set, remove it manually.
Confirm all Outlets are connected properly to your ExpandableView.
(You should better reconnect them all, after you modified your xib.)
You may need to re-structure your view hierarchy and/or constraints.
Third, modify your view controller holding the table view.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
//...
let nib = UINib(nibName: "ExpandableView", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "ExpandableView")
tableView.estimatedSectionHeaderHeight = 60
tableView.sectionHeaderHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let expandabelView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "ExpandableView")
// Frame size should be represented with constraints.
return expandabelView
}
When you create custom UIView, it should follow this if you want to use it init with frame.
class CustomView: UIView {
//This should be contentview of your xib
#IBOutlet var view: UIView!
let nibName = "CustomView"
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
private func xibSetup()
{
Bundle.main.loadNibNamed(nibName, owner: self, options: nil)
self.view.autoresizesSubviews = true
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.view)
self.view.frame = self.frame
}
}
Since you need to load view from nib, just load it and then add subview to your view. Then set content view's frame and set autoresizing mask of content view correctly
private func setUpView() {
Bundle.main.loadNibNamed("ExpandableView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}

Swift Delegate is not being called

I have completed all the needed code for delegate to work. In my viewcontroller:
class ViewController: UIViewControllerCustomViewDelegate
I also have this:
override func viewDidLoad() {
super.viewDidLoad()
let myCustomView = Bundle.main.loadNibNamed("ImageHeaderView", owner: self, options: nil)?[0] as! ImageHeaderView
myCustomView.delegate = self
}
func goToNextScene() {
print("GOTOSCENE2")
}
And in my custom view I have this:
import UIKit
protocol CustomViewDelegate: class { // make this class protocol so you can create `weak` reference
func goToNextScene()
}
#available(iOS 10.0, *)
class ImageHeaderView : UIView {
#IBOutlet weak var followme: UISwitch!
#IBOutlet weak var profileImage : UIImageView!
#IBOutlet weak var backgroundImage : UIImageView!
weak var delegate: CustomViewDelegate?
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor(hex: "E0E0E0")
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.height / 2
self.profileImage.clipsToBounds = true
self.profileImage.layer.borderWidth = 1
self.profileImage.layer.borderColor = UIColor.white.cgColor
//self.profileImage.setRandomDownloadImage(80, height: 80)
//self.backgroundImage.setRandomDownloadImage(Int(self.frame.size.width), height: 100)
}
#IBAction func followme(_ sender: AnyObject) {
UserDefaults.standard.set(followme.isOn, forKey: "followme")
}
#IBAction func logout(_ sender: AnyObject) {
delegate?.goToNextScene()
print("GOTOSCENE")
}
}
There is are no errors thrown but when I click/tap the button, nothing happens. It just prints "GOTOSCENE".
What I feel is
Your problem is right here
override func viewDidLoad() {
super.viewDidLoad()
let myCustomView = Bundle.main.loadNibNamed("ImageHeaderView", owner: self, options: nil)?[0] as! ImageHeaderView
myCustomView.delegate = self
}
I think you have added imageHeaderview from storyboard and in viewdidload you are creating new object of ImageHeaderView and assigning delegate to newly created object.
Try to outlet your ImageHeaderView and assign delegate to outleted object.
Hope this will fix your issue.

Resources