How to implement an independent custom UIView with nib file in Swift? - uiview

Many times I would like to implement custom UIViews, but I always have the same problem : my IBOutlets are nil when I want to use them.
I tried everything I could see on the web, but nothing works...
class ImagePickerView: UIView, ImagePickerDelegate {
#IBOutlet weak var imageViewSelected: UIImageView!
//MARK: - Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tapGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.onAddPicturePressed))
tapGesture.minimumPressDuration = 0
self.addGestureRecognizer(tapGesture)
}
override func awakeFromNib() {
super.awakeFromNib()
//imageViewSelected = UIImageView()
}
//MARK: - ImagePickerDelegate
func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {}
func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
self.imageViewSelected.image = images[0] // (the limit is set to 1 image)
imagePicker.dismiss(animated: true, completion: nil)
}
func cancelButtonDidPress(_ imagePicker: ImagePickerController) {
imagePicker.dismiss(animated: true, completion: nil)
}
//MARK: - Events
func onAddPicturePressed(gesture: UITapGestureRecognizer) {
if gesture.state == .began {
self.backgroundColor = CustomsColors.gray
} else if gesture.state == .ended {
self.backgroundColor = CustomsColors.lightGray
}
showImagePicker()
}
//MARK: - Utils
func showImagePicker() {
let imagePickerController = ImagePickerController()
imagePickerController.delegate = self
imagePickerController.imageLimit = 1
Configuration.mainColor = CustomsColors.green
Configuration.cancelButtonTitle = "Annuler"
Configuration.doneButtonTitle = "OK"
Configuration.noImagesTitle = "Pas d'images disponibles"
self.parentViewController?.present(imagePickerController, animated: true, completion: nil)
}
}
I tried self = Bundle.main.loadNibNamed("ImagePickerView", owner: self, options: nil)?.first as! ImagePickerView but I can't asign self.
I want something really simple, but I don't know how to do it...

Follow following steps in which view controller you want to add nib:
Make Object:
var priceView : FV_ViewToShowPrice!
Give Frame:
priceView = Bundle.main.loadNibNamed("FV_ViewToShowPrice", owner: self, options: nil)![0] as! FV_ViewToShowPrice
priceView.frame = self.view.bounds
Add in view:
self.view.addSubview(priceView)

Related

Adding UIView with data to the window

I need to add a UIView with size of tabbar but exactly above tabbar. This view allow user to come back to a started workout. My idea is holding data inside UIView and instantiate a View Controller with unfinished data when user clicks button. The problem is that when I want to instantiate VC my data in UIView become nil.
class BeforeRoutineClass {
// HERE I CREATE the UIView
func showWorkoutView(temporaryRoutine: Routine) {
guard let tabBar = navigationController.tabBarController else { return print("Tabbar is nil") }
let window = UIApplication.shared.keyWindow!
let height = tabBar.tabBar.frame.height
view2 = BackToWorkoutButton(frame: CGRect(x: 0, y: (tabBar.tabBar.frame.origin.y) - height, width: window.frame.width, height: height), routine: temporaryRoutine)
window.addSubview(view2!)
}
}
class BackToWorkoutButton: UIView {
var routine: Routine?
init(frame: CGRect, routine: Routine?, bobo: String?) {
self.routine = routine
super.init(frame: frame)
customInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func customInit() {
let xibView = Bundle.main.loadNibNamed("BackToWorkoutButton", owner: self, options: nil)?.first as! UIView
xibView.frame = self.bounds
addSubview(xibView)
}
#IBAction func backButtonTapped(_ sender: UIButton) {
// THERE IS NIL ALWAYS
guard let routine = routine else { return print("Routine is nil") }
let routineVC = RoutineFactory.startWorkoutScene(routine: routine)
let navBarOnModal = UINavigationController(rootViewController: routineVC)
navBarOnModal.modalPresentationStyle = .fullScreen
guard let nav = UIApplication.shared.windows.last?.rootViewController else { return print("there is no nav")}
nav.present(navBarOnModal, animated: true, completion: nil)
self.removeFromSuperview()
}
}

Subclassing UITextField to include a picker and a toolbar

Problem
I have an application that has a user registration view. It has many UITextField, and many of these have a picker with a toolbar embedded to close the picker i.e:
myTextField.inputView = myPicker
myTextField.inputAccessoryView = myToolbar
Essentially I want to reuse these text fields in different parts of my application, so I thought of subclassing UITextField, something like PickerUITextField.
Attempt
I've tried something like this:
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
self.resignFirstResponder()
}
}
Question
However, how can I detect in the view controller that the user has pressed the "Done" button of my PickerUITextField? In other words:
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
// I want this to be triggered whenever the country picker closes
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
Thank you for the help.
You can create a closure in PickerUITextField to perform done button action.
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
var doneBtnAction:(() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
doneBtnAction?()
self.resignFirstResponder()
}
}
And in your view controller, you can assign a closure. It will be called when you tap the done button.
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
override func viewDidLoad() {
super.viewDidLoad()
country.doneBtnAction = { [weak self] in
print("User selected \(self?.country.text!)")
}
}
}
You can use a protocol/delegate:
protocol PickerUITextFieldDelegate: class {
func didSelectCountry()
}
class PickerUITextField: UITextField {
// UITextField already have a 'delegate' we need a different name
weak var pickerDelegate: PickerUITextFieldDelegate?
let picker = UIPickerView()
let toolbar = UIToolbar()
#objc func removeToolBar() {
self.resignFirstResponder()
self.pickerDelegate?.didSelectCountry()
}
}
// We need to implement the PickerUITextFieldDelegate delegate here:
class UserRegistrationViewController: UIViewController, PickerUITextFieldDelegate {
#IBOutlet weak var country: PickerUITextField!
// Don't forget to set the delegate!
override func viewDidLoad() {
super.viewDidLoad()
self.country.pickerDelegate = self
}
// This will now be triggered by the delegate
func didSelectCountry() {
print("User selected \(country.text!)")
}
}

why doesn't this code for passing data between controllers work?

I'm trying to understand delegates so I created this simple app to test it out, I just want to pass a string from the secondViewController to the MainViewController... I tried to follow all the steps but it seems there's something missing and I don't have a clue about what it might possibly be... here's my code..
main view controller
class mainController: UIViewController, sendDataDelegate {
let label: UILabel = {
let lab = UILabel()
lab.text = "No data"
lab.textAlignment = .center
lab.translatesAutoresizingMaskIntoConstraints = false
return lab
}()
let tapRecognizer = UITapGestureRecognizer()
let secondVC = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.green
// assign delegate to self, not working...
secondVC.delegate = self
tapRecognizer.addTarget(self, action: #selector(goToSecondVC))
view.addGestureRecognizer(tapRecognizer)
view.addSubview(label)
// label constraints..
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":label]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-200-[v0]", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":label]))
}
#objc func goToSecondVC(){
show(SecondViewController(), sender: nil)
}
func passData(str: String) {
print(str)
label.text = str
}
}
and the second view controller plus protocol to send the data...
import UIKit
//protocol to pass the data..
protocol sendDataDelegate {
func passData(str: String)
}
class SecondViewController: UIViewController {
let tapRecog = UITapGestureRecognizer()
let dataStr = "data passed"
var delegate: sendDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.red
tapRecog.addTarget(self, action: #selector(goBack))
view.addGestureRecognizer(tapRecog)
}
#objc func goBack(){
// trying to pass data here..
delegate?.passData(str: dataStr)
// dismiss view...
dismiss(animated: true, completion: nil)
}
}
I'd like to point out I'm trying to learn everything by code, so no storyboards being used here.. how can I do this just by code?
thank you all in advance for the answers! have a great day)
In your function goToSecondVC you are creating a brand new view controller and ignoring the property that you created.
Change it to use secondVC instead of SecondViewController().
This correction will solve your problem:
#objc func goToSecondVC(){
show(secondVC, sender: nil)
}
Protocols are better to be named with a capital letter:
protocol SendDataDelegate: AnyObject {
func passData(str: String)
}
And don't forget about weak delegate:
weak var delegate: SendDataDelegate?
change goToSecondVC() method as below:
#objc func goToSecondVC(){
let vc = SecondViewController()
vc.delegate = self
show(vc, sender: nil)
}

Using image as gesture recognizer in Swift/Xcode, image coming up nil

I just got finished going through the big nerd ranch iOS programming book and started my first 'project'. I'm trying to use a UIImageView as a button but my image is coming up as nil and I cannot figure out why. I'm new at this, so any help, or even just identifying any parts of this code that don't make sense is appreciated.
import UIKit
class ImageScreenViewController: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "picture1")
override func viewDidLoad() {
super.viewDidLoad()
// set image on screen
imageView.image = picture1
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
note, picture1 is in Assets.xcassets.
Some explanation for your question
encodeWithCoder(_ aCoder: NSCoder) {
// Serialize
}
init(coder aDecoder: NSCoder) {
// Deserialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//You used
}
To add a Gesture you actually do not need to implement coder here as These are being used to store the state
Check following example
1- ImageView is being taken from storyBoard
2- myImage is being Added using code
Now , Storyboard initially implants serialise and deserialise mechanism
Thus imageView is a property being added from storyboard so no need for coder there
import UIKit
class ImageVC: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "cc")
//ImageView programmaticlly
var myImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// set image on screen
imageView.image = picture1
//setting properties and frame
myImage.image = picture1
myImage.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.view.addSubview(myImage)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//adding gesture
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.myImage.addGestureRecognizer(tapRecognizer)
self.myImage.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
As if you implement above Algo It won't crash , as For Properties being added from storyboard can directly be accessed in didload or willAppear as we never going to remove them from superview so indirectly its serialised
Second case - myImage - Added Programmatically can also be removed from superView and added again Thus can be serialised means need to be
you can add tapRecongnize in viewDidload because in init method imageview not initialize and return nil so that you can use like this
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = picture1
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
var image: UIImage?
#IBOutlet weak var imageChoisie: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageChoisie.image = image // from Segue
// Click on Image
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageChoisie.addGestureRecognizer(tapRecognizer)
self.imageChoisie.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
//print("tapped the picture")
dismiss(animated: true, completion: nil) // back previous screen
}

Remove nib from superview

I am simply trying to remove a view from it's superview when a respective button is pressed, but am failing to do so.
The view is a custom nib I created, called RequestLocationView
Called from viewDidLayoutSubviews:
func initBeaconServices() {
locationManager = CLLocationManager()
if locationManager.responds(to: #selector(CLLocationManager.requestWhenInUseAuthorization)) {
requestView = RequestLocationView(frame: UIScreen.main.bounds)
requestView.setupView()
requestView.alpha = 0
requestView.delegate = self
self.navigationController?.view.addSubview(requestView)
UIView.animate(withDuration: 0.5, delay : 1, options: .curveEaseOut, animations: {
self.requestView.alpha = 1
}, completion: nil)
}
locationManager.delegate = self
locationManager.pausesLocationUpdatesAutomatically = true
let uuid = UUID(uuidString: "869A6E2E-AE14-4CF5-8313-8D6976058A7A")
// Create the beacon region to search for with those values.
beaconRegion = CLBeaconRegion(proximityUUID: uuid!, identifier: "com.dejordan.Capiche")
}
Here's the code for the RequestView:
class RequestLocationView: UIView {
#IBOutlet weak var acceptButton: UIButton!
#IBOutlet weak var rejectButton: UIButton!
var lView: UIView!
weak var delegate: HandleLocationPermissionDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
private func xibSetup() {
lView = loadViewFromNib()
lView.frame = bounds
lView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
lView.translatesAutoresizingMaskIntoConstraints = true
addSubview(lView)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first
return nibView as! UIView
}
func setupView() {
acceptButton.layer.cornerRadius = acceptButton.frame.height / 2
}
func fadeIn(completion: #escaping (Bool) -> Void) {
UIView.animate(withDuration: 1.3, animations: {
self.alpha = 1
}, completion: completion)
}
func fadeOut(completion: ((Bool) -> Void)?) {
UIView.animate(withDuration: 1.3, animations: {
self.alpha = 0
}, completion: completion)
}
#IBAction func acceptPressed(_ sender: UIButton) {
delegate?.allowPermissions()
}
#IBAction func rejecttPressed(_ sender: UIButton) {
delegate?.denyPermissions()
}
}
Here are the delegate methods in the parent viewController:
func allowPermissions() {
requestView.fadeOut {(finished) in
if finished {
self.locationManager.requestWhenInUseAuthorization()
self.requestView.removeFromSuperview()
}
}
}
func denyPermissions() {
requestView.fadeOut {(didFinish) in
self.requestView.removeFromSuperview()
}
}
And this is what happens when you click reject... You can see the background slightly fading but that's it, and the view stays where it is.
I've been scratching my head for a while on this one, and have scoured many Stack Overflow posts to no avail...
It's possible the CLLocationManager code is interacting in a not-so-friendly manner.
If moving the RequestLocationView initialization into viewDidLoad corrects the issue, that's likely the cause.
To get the proper radius on your acceptButton, try adding a layoutSubviews handler to your custom view:
override func layoutSubviews() {
acceptButton.layer.cornerRadius = acceptButton.bounds.height / 2
}

Resources