Is this a good way to show a view by touch a button at the middle of the navigation bar and remove it by touch anywhere else? - ios

I'm trying to put a button at the middle of the navigation bar, it will show a list when I touch it (I added a pure UIView here instead of a UITabeView to just make the code simpler) . And then the additional view will be removed when I touch anywhere else. So I add a background view whose size is the same as the screen to response my touch. Although it still behind the navigation bar.
Here is my question:
Is this a good implementation?
class ViewController: UIViewController {
var optionView: UIView!
var backgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(ViewController.titleButtonTapped), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 30)
button.backgroundColor = .red
navigationItem.titleView = button
}
func titleButtonTapped() {
backgroundView = UIView(frame: UIScreen.main.bounds)
backgroundView.backgroundColor = .clear
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleGesture)) // add this gesture to response my touch
backgroundView.addGestureRecognizer(gesture)
view.addSubview(maskView)
optionView = UIView(frame: CGRect(x: -40, y: 30, width: 100, height: 100)) // x = button.wdith / 2 - optionView.width / 2
optionView.backgroundColor = .red
navigationItem.titleView?.addSubview(alertView)
}
func handleGesture() {
optionView.removeFromSuperview()
backgroundView.removeFromSuperview()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Now it looks like the following.
Edit:
The following is my implementation of a popover view.
func buttonTapped() {
let popoverViewController = UIViewController()
popoverViewController.preferredContentSize = CGSize(width: 300, height: 300)
popoverViewController.modalPresentationStyle = .popover
let presentationController = popoverViewController.popoverPresentationController!
presentationController.delegate = self
presentationController.sourceView = view
presentationController.sourceRect = CGRect(x: 100, y: 100 , width: 100, height: 100)
present(popoverViewController, animated: true, completion: nil)
}
// delegate
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
It's a little bit different frrom the Apple documentation. They recommended that we'd better configure the presentation controller after calling present(:animated: completion:) method. But it doesn't work if I don't configure it before presentation. Maybe, because I set the delegate.
Configuring the popover presentation controller after calling present(_:animated:completion:) might seem counter-intuitive but UIKit does not create a presentation controller until after you initiate a presentation. In addition, UIKit must wait until the next update cycle to display new content onscreen anyway. That delay gives you time to configure the presentation controller for your popover.

For using a popover or not, it depends on the purpose of this pop over view. If it has lots of information, it will be better to separate it out to another view controller and make segue to it on button click. This will provides user the full screen to look at whatever it is.
For me, adding a button at the center of a navigation bar is not usual. You have to inform me about it for me to click on it.
In conclusion:
If you want a popover view to tell user hints or show them something, it will be better to use UIPopoverPresentationController so that you don't need to care about the styles.
If you want another view to show data, list of pictures etc, it will be better to use a segmented control or another view controller

var optionView: UIView!
var backgroundView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(ViewController.titleButtonTapped), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 30)
button.backgroundColor = .red
navigationItem.titleView = button
}
func titleButtonTapped()
{
if backgroundView == nil
{
backgroundView = UIView(frame: UIScreen.main.bounds)
backgroundView.backgroundColor = .clear
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleGesture)) // add this gesture to response my touch
backgroundView.addGestureRecognizer(gesture)
view.addSubview(backgroundView)
}
if optionView == nil
{
optionView = UIView(frame: CGRect(x: -40, y: 30, width: 100, height: 100)) // x = button.wdith / 2 - optionView.width / 2
optionView.backgroundColor = .red
navigationItem.titleView?.addSubview(optionView)
}
}
func handleGesture()
{
if optionView != nil
{
optionView.removeFromSuperview()
optionView = nil
}
if backgroundView != nil
{
backgroundView.removeFromSuperview()
backgroundView = nil
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}

Related

Create UIButton on top layer UITableViewController like as AirBnB

How to create UIButton in UITableViewController like as AirBnB map.
Highlighted in red in screenshot.
It's quite easy actually, just create the table view as you always do (either storyboard or code) then create the button, position it correctly, set constraints and make sure it's not a subview of the table view but the view that contains the table view.
Since you didn't specify whether you use the storyboard or not, I did use it for the example below.
See this extremely simple demo I've just created:
This is the view hierarchy:
And these are the constraints for the button:
Create a button first and then then override the function scrollViewDidScroll():
import UIKit
class YourTableViewController: UITableViewController {
private let button = UIButton(type: UIButton.ButtonType.custom) as UIButton
override func viewDidLoad() {
let image = UIImage(named: "Image.png")
button.frame = CGRect(x: yourXpos, y: yourYPos, width: 60, height: 60)
button.setImage(image, for: .normal)
button.clipsToBounds = true
button.layer.cornerRadius = 30
button.addTarget(self, action: #selector(buttonClicked(_:)), for:.touchUpInside)
areaOfTableView.addSubview(button)
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = self.areaOfTableView.contentOffset.y
button.frame = CGRect(x: yourXpos, y: offset + yourYpos, width: btn.frame.size.width, height: btn.frame.size.height)
}
#objc private func buttonClicked(_ button: UIButton) {
// Action when you tapped the button
}
}

Trying to call a navigation bar function in another class pragmatically

I created a navigation bar which I'm trying to call in another view controller. I set it up by calling the methods which I separated into left, center and right buttons. In my other controller I call the navbarcontroller and try and call the method for which i setup the navigation toolbar. Nothing happens, however there is no crash.
import UIKit
class NavBarController : UIViewController{
var screenSize: CGRect!
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBarItems()
setupToolBarItems()
self.navigationController?.isToolbarHidden = false
self.view!.backgroundColor = .white
}
and my method for the navigation bar is this
func setupNavigationBarItems() {
setupCenterNavButton()
setupLeftNavButton()
setupRightNavButton()
}
func showCalendarController() {
let navController = CalendarController()
self.present(navController, animated: true, completion: nil)
} //connect bottom bar buttons to controller
func showEventsController() {
let navController = EventsController()
self.present(navController, animated: true, completion: nil)
} //connect bottom bar buttons to controller
func setupNavigationBarItems() {
setupCenterNavButton()
setupLeftNavButton()
setupRightNavButton()
} // top bar button setup
private func setupCenterNavButton() {
let buttonFrame = UIView(frame: CGRect(x: 0, y: 0, width: 165,
height: 20))
mainFeedButton.frame = CGRect(x: 0,y: 0, width: 80,height: 20) as
CGRect
mainFeedButton.backgroundColor = UIColor.blue
peekFeedButton.frame = CGRect(x: 85,y: 0, width: 80,height: 20) as
CGRect
peekFeedButton.backgroundColor = UIColor.blue
buttonFrame.addSubview(mainFeedButton)
buttonFrame.addSubview(peekFeedButton)
navigationItem.titleView = buttonFrame
} //center bar buttons / action setup
private func setupLeftNavButton() {
navigationItem.leftBarButtonItem = UIBarButtonItem(customView:
favoriteButton)
}// left bar buttons / action setup
private func setupRightNavButton() {
navigationItem.rightBarButtonItem = UIBarButtonItem(customView:
moreButton)
} //right bar buttons / action setup
lazy var mainFeedButton: UIButton! = {
let button = UIButton(type: .custom) // button type
button.setTitle("Main",for: .normal) //button title
button.sizeToFit() // size button to fit the title
var frame = button.frame //create frame to manipulate the body
button.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
button.addTarget(self, action:
#selector(self.showMainFeedController),
for: .touchUpInside)
return button
}() //mainFeed button connected to Feed Controller
lazy var peekFeedButton: UIButton! = {
let button = UIButton(type: .custom) //button type
button.setTitle("Spy",for: .normal) //button title
button.sizeToFit() // size button to fit the title
var frame = button.frame //create frame to manipulate the body
button.frame = CGRect(x: 20, y: 0, width: 100, height: 40)
button.addTarget(self, action:
#selector(self.showSpyFeedController),
for: .touchUpInside)
return button
}()//peekFeed button frame and action setup
lazy var favoriteButton: UIButton! = {
let button = UIButton(type: .system) //default button with blue
text
button.setImage(#imageLiteral(resourceName:
"star").withRenderingMode(.alwaysOriginal), for: .normal)
button.contentMode = .scaleAspectFit
button.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
button.addTarget(self, action: #selector(favoriteButton_tapped),
for: .touchUpInside)
return button
}() //favorites button frame and action setup
lazy var moreButton: UIButton! = {
let button = UIButton(type: .system) //default button with blue
text
button.setImage(#imageLiteral(resourceName:
"more").withRenderingMode(.alwaysOriginal), for: .normal)
button.contentMode = .scaleAspectFit
button.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
button.addTarget(self, action: #selector(moreButton_tapped),
for: .touchUpInside)
return button
}() //more button frame and action setup
func showMainFeedController() {
let navController = MainFeedController()
self.present(navController, animated: true, completion: nil)
} //mainFeed button connected to Feed Controller
func showSpyFeedController() {
let navController = SpyFeedController()
self.present(navController, animated: true, completion: nil)
}//peekFeed button connected to SpyFeedController
func favoriteButton_tapped(sender: UIButton) {
print("You touched this!")
}
func moreButton_tapped(sender: UIButton) {
print("You touched this!")
}
}
I then try and call the function by setupNavigationBarItems() like this
import UIKit
class EventsController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let navbar = NavBarController()
navbar.setupNavigationBarItems()
self.navigationController?.isToolbarHidden = false
self.view.backgroundColor = .white
}
}
I'm not sure if this a valid way. I'm still kinda new to all of this.
It's not clear what you expect to happen, but here's what does happen:
let navbar = NavBarController()
A completely new NavBarController object is created.
navbar.setupNavigationBarItems()
That NavBarController object's setupNavigationBarItems is called.
self.navigationController?.isToolbarHidden = false
self.view.backgroundColor = .white
Your code comes to an end. navbar was a local variable, so the NavBarController object vanishes in a puff of smoke. The end. This object was created and configured to no purpose.
I remember my first month in iOS way back 2015 :D, didn't have any knowledge in OOP, I didn't know too how to pass a data to another screen or class.
Anyways, you DO NOT create a new instance of your NavBarController class in your EventsController. If you want to talk to your NavBarController from your EventsController, then you will need a reference that is currently alive. You can also use delegate (search for that later).
So before you show or present your EventsController from your NavBarController, pass your current NavBarController instance to the next screen which is EventsController. BUT FIRST, you need to declare a variable in your EventsController, correct? :)
Declare a variable with a type of NavBarController inside your EventsController class, like so:
var navBarController: NavBarController!
Then in this piece of code of yours, pass your self (the NavBarController instance) to the EventsController class before showing or presenting, take note that you mistakenly gave a wrong name to your EventsController new instance, so I renamed it:
func showEventsController() {
let eventsController = EventsController()
eventsController.navBarController = self // THIS :)
self.present(eventsController, animated: true, completion: nil)
}
Lastly, instead of this:
let navbar = NavBarController()
navbar.setupNavigationBarItems()
Make use of your declared variable, like so:
self.navBarController.navbar.setupNavigationBarItems()
Hope this helps! :)

Why is UIView removed from heirarchy after presenting view controller?

Ok I just ran into something bizarre. I've got my app controller dependency injecting a view (header) into a view controller. That view controller presents another view controller modally and dependency injects it's own header for the presenting view controller to use. But when its presented the header from the first controller disappears.
The property is still set but it's been removed from the view hierarchy.
I've reproduced this issue in fresh singleview project:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 0, y: 20, width: 100, height: 50))
button.setTitle("Click Me!", for: .normal)
button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
button.backgroundColor = .black
button.setTitleColor(.lightGray, for: .normal)
self.view.addSubview(button)
}
func segue() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .lightGray
let firstVC = FirstViewController()
firstVC.sharedView = view
present(firstVC, animated: false)
}
}
class FirstViewController: UIViewController {
var sharedView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.sharedView)
let button = UIButton(frame: CGRect(x: 0, y: 200, width: 100, height: 50))
button.setTitle("Click Me!", for: .normal)
button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
button.backgroundColor = .black
button.setTitleColor(.lightGray, for: .normal)
self.view.addSubview(button)
}
func segue() {
let secondVC = SecondViewController()
secondVC.sharedView = self.sharedView
present(secondVC, animated: true)
}
}
class SecondViewController: UIViewController {
var sharedView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.sharedView)
let button = UIButton(frame: CGRect(x: 0, y: 200, width: 100, height: 50))
button.setTitle("Click Me!", for: .normal)
button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
button.backgroundColor = .black
button.setTitleColor(.lightGray, for: .normal)
self.view.addSubview(button)
}
func segue() {
self.dismiss(animated: true)
}
}
Can someone explain what's going on here? Why does the sharedView disappear from the FirstViewController?
At the doc of -addSubview(_:):
Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous
superview before making the receiver its new superview.
That should explain your issue.
I'd suggest instead that you create a method that generate a headerView (a new one each time) according to your customization style.
If you really want to "copy" the view, you can check that answer. Since UIView is not NSCopying Compliant, their trick is to "archive/encode" it since it's NSCoding compliant, copy that archive, and "unarchive/decode" the copy of it.

How can I get events to my (sub) UIView?

I'm new to Swift and have a hard time understand the event flow. The code below can be run directly in an xcode playground. I have a white UIView in the background. This view has a brown button and a red view as sub-views. Click on them and the events are logged in the controller, just as expected.
But the controller of this white view also adds another view, that has it's own controller class (SubviewController). SubviewController is green and has a blue subview with a black button. Question is... why don't I get any logs from the green, blue and black views/buttons?
import Foundation
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let playButton: UIButton = {
let playButton = UIButton(frame: CGRect(x: 155, y: 135, width: 160, height: 40))
playButton.setTitle("BROWN BUTTON", for: .normal)
playButton.backgroundColor = UIColor.brown
return playButton
}()
override func loadView() {
let viewWhite = UIView()
viewWhite.backgroundColor = UIColor.white
let viewRed = UIView()
viewRed.backgroundColor = UIColor.red
viewRed.frame = CGRect(x: 20, y: 20, width: 40, height: 10)
viewRed.clipsToBounds = true
let recognizer2 = UITapGestureRecognizer(target: self, action: #selector (self.handleTapRed(_:)))
viewRed.addGestureRecognizer(recognizer2)
let recognizer = UITapGestureRecognizer(target: self, action: #selector (self.handleTap(_:)))
viewWhite.addGestureRecognizer(recognizer)
playButton.addTarget(self, action: #selector (self.action) , for: .touchUpInside)
let catList = SubviewController()
viewWhite.addSubview(catList.view)
viewWhite.addSubview(playButton)
viewWhite.addSubview(viewRed)
self.view = viewWhite
}
func action() {
print("Brown button tapped")
}
func handleTap(_ sender:UITapGestureRecognizer){
print("WHITE VIEW (background view) TAPPED")
}
func handleTapRed(_ sender:UITapGestureRecognizer){
print("RED VIEW TAPPED")
}
}
class SubviewController: UIViewController {
let buttonBlack: UIButton = {
let button = UIButton(frame: CGRect(x: 40, y: 10, width: 170, height: 20))
button.backgroundColor = UIColor.black
button.setTitle("BLACK BUTTON", for: .normal)
return button
}()
let viewBlue: UIView = {
let v = UIView()
v.backgroundColor = UIColor.blue
v.frame = CGRect(x: 20, y: 40, width: 240, height: 60)
v.clipsToBounds = true
return v
}()
override func loadView() {
super.loadView()
self.view.backgroundColor = UIColor.green
buttonBlack.addTarget(self, action: #selector (self.blackKlick) , for: .touchUpInside)
self.view.addSubview(viewBlue)
self.view.frame = CGRect(x: 0, y: 40, width: 240, height: 60)
self.view.clipsToBounds = true
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector (self.handleTapGreen(_:))))
viewBlue.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector (self.handleTapBlue(_:))))
viewBlue.addSubview(buttonBlack)
}
func blackKlick() {
print("Black button tapped")
}
func handleTapBlue(_ sender:UITapGestureRecognizer){
print("BLUE VIEW TAPPED")
}
func handleTapGreen(_ sender:UITapGestureRecognizer){
print("GREEN VIEW TAPPED")
}
}
PlaygroundPage.current.liveView = TestViewController()
Thanks for any help!
This line in your current code:
let catList = SubviewController()
creates a local instance of SubvieController. As soon as you exit the loadView() func, that instance is gone.
So, you need a class-level variable to keep that instance around. Add this line:
class TestViewController : UIViewController {
var catList: SubviewController!
and then remove the let from the instantiation line in loadView():
catList = SubviewController()

Swift 3 add remove subview and its content dynamically

I am writing my first Swift app for quiz. Each question has random way to render on screen as below screenshot.
I am programming app without story board ie. programmatically. I want to create simple pagination flow for each question within single viewcontroller without using Collocationview, Tableview or navigation.
What I did so far? I have simple viewcontroller with UIView() added as subview. I am adding question components dynamically. Now once user click on continue I wanted remove subview and add new subview with new question. I am able to remove subview but contents on subview seems to be still there as I can see its overwriting.
To get more clarification please view my code.
import UIKit
class QuizController: UIViewController {
let subView = UIView()
var currentQuestion:Int = 1;
let questions = ["This is question 1", "Hello to question 2", "Question 3"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
setup_layout()
}
func setup_layout(){
let closeBtn = UIButton(frame: CGRect(x: 5, y: 10, width: 200, height: 50))
closeBtn.backgroundColor = UIColor.red
closeBtn.setTitle("Close", for: .normal)
closeBtn.addTarget(self, action: #selector(close), for: .touchUpInside)
view.addSubview(closeBtn)
//dynamic view
create_subview()
}
func nextQuestion(){
print("Show next")
if let viewWithTag = self.view.viewWithTag(currentQuestion) {
viewWithTag.removeFromSuperview()
currentQuestion += 1;
create_subview()
} else {
print("No!")
}
}
func create_subview(){
let heightOfView = view.frame.size.height
let widthOfView = view.frame.size.width
subView.tag = currentQuestion
subView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
subView.frame = CGRect(x: 0, y: 60, width: self.view.frame.width, height: heightOfView - 60)
self.view.addSubview(subView)
let txtLabel1 = UILabel(frame: CGRect(x: 35, y: 120, width: widthOfView , height: 20))
txtLabel1.text = questions[currentQuestion-1]
txtLabel1.font = txtLabel1.font.withSize(12)
subView.addSubview(txtLabel1)
let nextBtn = UIButton(frame: CGRect(x: 5, y: 300, width: 200, height: 50))
nextBtn.backgroundColor = UIColor.red
nextBtn.setTitle("Continue", for: .normal)
nextBtn.addTarget(self, action: #selector(nextQuestion), for: .touchUpInside)
subView.addSubview(nextBtn)
}
func close(){
self.dismiss(animated: true, completion: nil)
}
}
And this is what I see which I click continue.
NOTE: Initially thought using Collocation or Table view will be more appropriate as I can set to scroll horizontally for each question and fetch questions using REST API and place to each cell. But I want to present next screen to user only once then click on continue. I guess with collectionview user can move to next screen on swipe.
Just found the answer. I assumed removing subview will also remove all components on subview by itself ie. UILable and UIButton in my case.
I have to remove them separately.
for subview in self.subView.subviews {
subview.removeFromSuperview()
}
Now i can add tag to components and remove like this:
for subview in self.subView.subviews {
if (subview.tag == 1) {
subview.removeFromSuperview()
}
}
Why are you removing superviews and adding again and again??
Simply change UILabel.text :)

Resources