Displaying custom UIView in a ViewController - ios

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.

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

Storyboard xib live view renders all subviews in top-left corner

I am using XCode 12.1 and I am trying to create some reusable XIBs and want to render them in the storyboard. My problem is that all those views will be rendered in the top-left corner, just like if the frame of my loaded view is CGRect.zero. The view controller in the storyboard looks like this:
I have made a subclass of UIView that dynamically loads a view from .xib:
import UIKit
#IBDesignable
class NibView: UIView {
required init?(coder: NSCoder) {
super.init(coder: coder)
self.createNibView()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.createNibView()
}
private func createNibView() {
guard let view = loadFromNib() else { return }
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
self.createConstraints(for: view)
self.setNeedsLayout()
}
private func loadFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as? UIView
view?.translatesAutoresizingMaskIntoConstraints = false
return view
}
private func createConstraints(for view: UIView) {
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: view.topAnchor),
self.bottomAnchor.constraint(equalTo: view.bottomAnchor),
self.leadingAnchor.constraint(equalTo: view.leadingAnchor),
self.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
and a subclass of this view:
import UIKit
#IBDesignable
class RDTextField: NibView {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var errorLabel: UILabel!
#IBOutlet weak var errorIcon: UIImageView!
}
with the associated xib:
In storyboard, I have added a view for which I've set the class of this custom view, which is the selected view from the image with the storyboard.
I have also tried playing with awakeAfter(using coder:) with no better results, I tried to use the autoresizing mask, which is slightly better, in the sense that it sets the origin correctly, but it does not have the correct size. Anyway, I would like to use translatesAutoresizingMaskIntoConstraints disabled and use my own constraints.
I have also tried to debug these views, but XCode won't attach to any process and I cannot find the logs inside the Console.app.
Note: I have an IBDesignable extension for UIView which helps me setting border color, width and radius. I have tried to disable it, but without any results.
Note 2: At runtime everything works properly, but live view in storyboard is pretty much needed as well.
Did anyone encounter this or have any idea how to approach this issue?
The root view of a Storyboard object always uses an Autoresizing Mask. Your code is disabling that.
Remove one line from your createNibView() func in your NibView class
private func createNibView() {
guard let view = loadFromNib() else { return }
// don't do this!
//self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
self.createConstraints(for: view)
self.setNeedsLayout()
}
That should do it.

Interacting with custom UIView inside view controller

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()

loading from xib and translatesautoresizingmaskintoconstraints

import UIKit
class ViewController: UIViewController {
#IBOutlet weak var vsuper: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let v = view2.getView()
vsuper.backgroundColor = UIColor.black
vsuper.addSubview(v)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//xib file
import UIKit
class view2: UIView {
override func awakeFromNib() {
super.awakeFromNib()
}
static func getView()->view2{
let v = Bundle.main.loadNibNamed("view2", owner: nil, options: nil)?.first as! consentview
// v.translatesAutoresizingMaskIntoConstraints = false
return v;
}
}
While I load xib into another view in story board it doesn't get added inside that view if translateautoresizingmaskintoconstraints is set to false but if I remove that line it gets added to the view.
if I set it to false it takes space on top left otherwise it gets added inside the view. Why so ? even though I am adding it tov super
You did not set the constraints. translatesAutoresizingMaskIntoConstraints = false means you don't want the xib frame to be translated into constraints thus you will setup constraints yourself. Thats why its not assigning any bounds to the xib view on your superview. Try making some constraints with the superview after addSubview() call. such as,
v.leadingAnchor.constraint(equalTo: vsuper.leadingAnchor, constant: 0).isActive = true
v.trailingAnchor.constraint(equalTo: vsuper.trailingAnchor, constant: 0).isActive = true
v.topAnchor.constraint(equalTo: vsuper.topAnchor, constant: 0).isActive = true
v.bottomAnchor.constraint(equalTo: vsuper.bottomAnchor, constant: 0).isActive = true
vsuper.layoutIfNeeded()
Try it
import UIKit
class MyView: UIView {
// your outlets from your view can be there
// your functions for your view
func myFunc() {
}
}
import UIKit
class ViewController: UIViewController {
var myView: MyView!
override func viewDidLoad() {
super.viewDidLoad()
if let contentView = Bundle.main.loadNibNamed("MyView", owner: self, options: nil)?.first as? MyView {
myView = contentView
self.view.addSubview(myView)
}
// set background color your custom view
myView.backgroundColor = UIColor.black
// call functions for your custom view
myView.myFunc()
}
}

Referencing superview inside of view code class file

I'm currently trying to learn constraints and styling programmatically in Swift. I'm also trying to maintain clean and modularized code by splitting up code that relates to "styling".
I simply have my LoginViewController:
import UIKit
class LoginViewController: UIViewController {
var loginView: LoginView!
override func viewDidLoad() {
super.viewDidLoad()
loginView = LoginView(frame: CGRect.zero)
self.view.addSubview(loginView)
// AutoLayout
loginView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets.zero, excludingEdge: .bottom)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
Then my LoginView:
import UIKit
class LoginView: UIView {
var shouldSetupConstraints = true
var headerContainerView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
// Header Container View
headerContainerView = UIView(frame: CGRect.zero)
headerContainerView.backgroundColor = UIColor(red:0.42, green:0.56, blue:0.14, alpha:1.0) // #6B8E23
headerContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerContainerView)
headerContainerView.topAnchor.constraint(equalTo: self.superview!.topAnchor)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func updateConstraints() {
if(shouldSetupConstraints) {
// AutoLayout constraints
shouldSetupConstraints = false
}
super.updateConstraints()
}
}
Where I am getting stuck is with just simply trying to add this headerContainerView to the top of my superview. I want to be able to add it so it pins itself to the top, left and right of the superview and only 1/3 of the superview's height. I continue to try and reference the superview with no success and I cannot find a solution that helps me understand on the internet. Any suggestions on how I can complete this?
Thank you for taking the time for those that respond.
NOTE: I did start out using PureLayout which is really nice. However, I am an individual that likes to understand what is going on behind the scenes or at least how to write the code at its base level. You can see that I am using a PureLayout function in my LoginViewController, but I am looking to change that. I would prefer a solution that doesn't add a third party library.
Here self in the custom UIView class is the parent view of headerContainerView so , You can add this , Also I recommend to learn constraints first without 3rd party libraries to fully understand the concept as you will learn a lot from seeing conflicts and other things , once done , shift to libraries
override init(frame: CGRect) {
super.init(frame: frame)
// Header Container View
headerContainerView = UIView(frame: CGRect.zero)
headerContainerView.backgroundColor = UIColor(red:0.42, green:0.56, blue:0.14, alpha:1.0) // #6B8E23
headerContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerContainerView)
headerContainerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
headerContainerView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
headerContainerView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
headerContainerView.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier:1.0/3.0, constant: 0.0).active = true
}
// loginView layout
override func viewDidLoad() {
super.viewDidLoad()
loginView = LoginView(frame: CGRect.zero)
self.view.addSubview(loginView)
loginView.translatesAutoresizingMaskIntoConstraints = false
loginView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
loginView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
loginView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
loginView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
headerContainerView.snp.makeConstraints { (make) in
make.top.equalTo(self)
make.leading.and.trailing.equalTo(self)
make.height.equalTo(self.frame.height/3)
}
With SnapKit.
With SnapKit, you can do the following:
override func viewDidLoad() {
super.viewDidLoad()
loginView = LoginView(frame: CGRect.zero)
self.view.addSubview(loginView)
// AutoLayout
loginView.snp.makeConstraints { (make) in
make.left.equalTo(view.snp.left)
make.right.equalTo(view.snp.right)
make.top.equalTo(view.snp.top)
make.height.equalTo(view.snp.height).multipliedBy(1/3)
}
}

Resources