I want to add a swipe action to my app. Basically I have 5 view controllers and I main view controller. On my main view controller I have a view and I am calling the content from other 5 view controllers to that view. And I want to swipe those 5 view controllers.
My code:
import UIKit
class TabViewController: UIViewController {
#IBOutlet var contentView: UIView!
#IBOutlet var buttons: [UIButton]!
#IBOutlet var backgroundView: UIImageView!
var movingView = UIView()
var rifleViewController: UIViewController!
var pistolViewController: UIViewController!
var shotgunViewController: UIViewController!
var smgsViewController: UIViewController!
var sniperViewController: UIViewController!
var viewControllers: [UIViewController]!
var selectedIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
rifleViewController = storyboard.instantiateViewController(withIdentifier: "rifles")
sniperViewController = storyboard.instantiateViewController(withIdentifier: "snipers")
smgsViewController = storyboard.instantiateViewController(withIdentifier: "smgss")
shotgunViewController = storyboard.instantiateViewController(withIdentifier: "shotguns")
pistolViewController = storyboard.instantiateViewController(withIdentifier: "pistols")
viewControllers = [rifleViewController,
pistolViewController,
shotgunViewController,
smgsViewController,
sniperViewController]
buttons[selectedIndex].isSelected = true
didPressTab(buttons[selectedIndex])
let screenWidth = UIScreen.main.bounds.width
movingView = UIView(frame: CGRect(x: 0, y: 80, width: screenWidth / 5, height: 5))
movingView.backgroundColor = UIColor.white
backgroundView.addSubview(movingView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func didPressTab(_ sender: UIButton) {
let previousIndex = selectedIndex
selectedIndex = sender.tag
buttons[previousIndex].isSelected = false
let previousVC = viewControllers[previousIndex]
previousVC.willMove(toParentViewController: nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
sender.isSelected = true
let vc = viewControllers[selectedIndex]
addChildViewController(vc)
vc.view.frame = contentView.bounds
contentView.addSubview(vc.view)
vc.didMove(toParentViewController: self)
let newx = sender.frame.origin.x
UIView.animate(withDuration: 0.2) {
self.movingView.frame.origin.x = newx
}
}
}
You must go for UIPageViewController.
With the setViewControllers(_:direction:animated:completion:) you can set an array of your view controllers and also you can customise the animation.
Add on all of your viewController two Swipe Gesture Recognizer. Take care that you really drag and drop them onto the View Controllerin the hierarchy because else it might not work (For the most left and most right view controller you just have to add one swipe gesture recognizer). After that change for one of the the Swipe Gesture Recognizerper view controller the Swipe option in the attribute inspector from the standard Left to Right.
Create from each view controller to the next a Showseague and one back. Give them an Identifier you can remember. Then write something like that into each ViewController.swift and link the #IBAction with the two Swipe Gesture Recognizer of each corresponding view controller:
#IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
if sender.direction == UISwipeGestureRecognizerDirection.left {
performSegue(withIdentifier: "identifierOfSegue", sender: nil)
}
if sender.direction == UISwipeGestureRecognizerDirection.right {
performSegue(withIdentifier: "identifierOfOtheSegue", sender: nil)
}
}
I don't know if I understood your question, but you can add (from Interface Builder) a UIGestureRecognizer (for the swipe action) to the View and in the selector of that gesture, you present the View you'd like to show.
Related
I have a tab bar with two tab bar item and I add a central addButton on it which can lead me to the adding items viewcontroller. Here is the setup of the middle button.
func setupMiddleButton() {
let tabBarHeight = tabBar.frame.size.height
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: tabBarHeight*1.5, height: tabBarHeight*1.5))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = view.bounds.height - menuButtonFrame.height/2 - tabBarHeight - 8
menuButtonFrame.origin.x = view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.red
menuButton.layer.cornerRadius = menuButtonFrame.height/2
view.addSubview(menuButton)
let largeConfiguration = UIImage.SymbolConfiguration(scale: .large)
let addIcon = UIImage(systemName: "plus", withConfiguration: largeConfiguration)
menuButton.setImage((addIcon), for: .normal)
menuButton.addTarget(self, action: #selector(menuButtonAction(sender:)), for: .touchUpInside)
view.layoutIfNeeded()
}
#objc private func menuButtonAction(sender: UIButton) {
let addVC = AddViewController()
let navigationController = UINavigationController(rootViewController: addVC)
performSegue(withIdentifier: "addEventSegue", sender: sender)
}
and I did this in one of my tab bar view to reloadData, but I found the collectionView unable to reload after I end editing and dismiss the present modally
override func viewWillAppear(_ animated: Bool) {
lifeCollectionView.delegate = self
lifeCollectionView.dataSource = self
self.fetchData()
DispatchQueue.main.async {
self.lifeCollectionView.reloadData()
}
}
What should I do to reload the collection view after I dismiss that present modally? Thank you so much!
There are multiple ways to achieve that delegate , closure and modalPresentation Style
Way 1:
if you add navigationController.modalPresentationStyle = .fullScreen this will call viewWillAppear of the controller who presents controller and your reload method will get called
Way 2: Delegate & Protocol
Declare a protocol like the one below in Second Controller
protocol CallParent{
func reloadCollection()
}
Declare a property to hold the reference of view controller confirming to this protocol in Second Controller
weak var myParent : CallParent?
Now call reloadCollection before dismiss
if let parent = myParent {
parent.reloadCollection()
}
Confirm first controller with CallParent protocol
class FirstVC: UIViewController,CallParent{
Then while calling segue
#objc private func menuButtonAction(sender: UIButton) {
let addVC = AddViewController()
addVC.myParent = self
let navigationController = UINavigationController(rootViewController: addVC)
performSegue(withIdentifier: "addEventSegue", sender: sender)
}
Way 3 : In your presented controller call dismiss function like this
if let fvc = self.presentingViewController as? FirstController {
self.dismiss(animated: true) {
fvc.callReloadFunctionHere()
}
}
Way 4: Closure
class SecondViewController: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
In FirstController
#objc private func menuButtonAction(sender: UIButton) {
let addVC = AddViewController()
addVC.onViewWillDisappear = {
// reload collection view here
}
let navigationController = UINavigationController(rootViewController: addVC)
performSegue(withIdentifier: "addEventSegue", sender: sender)
}
Set your collection view datasource, delegate and reload only in viewDidLoad. You don't need to use viewWillAppear to reload your collectionView every time!
Use delegates to pass messages.
protocol ContentChangedDelegate: class {
func itemAdded()
}
Within AddViewController add the below variable
weak var delegate: ContentChangedDelegate? = nil
Set your the UIViewController subclass instance where the lifeCollectionView exists as delegate for AddViewController.
let addVC = AddViewController()
addVC.delegate = self
//push or present addVC
Conform your view controller to the ContentChangedDelegate protocol and within the itemAdded method you can reload your collection view.
Call delegate?.itemAdded() after adding your item in AddViewController. This would call the method in your listVC and it should update immediately.
I'm trying to embed a custom view in UIViewController. But when displayed, it's not clickable since it's out of the UIView frame.
viewDidLoad:
#IBOutlet var theVC_InVC_Test: UIView!
#IBOutlet var TableView: UITableView!
override func viewDidLoad() {
TableView.delegate = self
TableView.dataSource = self
func embed(_ viewController:UIViewController, inView view:UIView){
viewController.willMove(toParent: self)
viewController.view.frame = view.bounds
view.addSubview(viewController.view)
self.addChild(viewController)
viewController.didMove(toParent: self)
}
embed(sidemenutest1(), inView: theVC_InVC_Test)
}
sidemenutest1 UIViewController:
func popItOver(){
let PopOverVC = UIStoryboard(name:"Main",bundle: nil).instantiateViewController(withIdentifier: "CoinsPopUp") as! CoinsPopUpViewController
self.addChild(PopOverVC)
PopOverVC.view.frame = UIScreen.main.bounds
self.view.addSubview(PopOverVC.view)
PopOverVC.didMove(toParent: self)
}
#IBAction func storeAction(_ sender: Any) {
popItOver()
}
It's displayed fine, but it's not clickable... When I tried to click the subview buttons, the TableView is clicked and not the subview.
You shouldn't add the popover menu as a container view controller. It should be presented from the controller as a modal view controller instead and set its background view color to be the translucent gray color.
I have a main view controller, a container view and 2 child view controllers and i would like to be able to switch between the children (for example: when the application loads for the first time, i would like that the controller containing the MapView to be loaded and when i press the Search Bar found in the main view, the controller with the table to be loaded).
Here is my storyboard: https://i.stack.imgur.com/rDPMe.png
MainScreen.swift
class MainScreen: UIViewController {
#IBOutlet private weak var searchBar: UISearchBar!
#IBOutlet private weak var ContainerView: UIView!
//private var openSearchBar: Bool?
private var openMapView: Bool = true
private var openPlacesList: Bool = false
private var containerView: ContainerViewController!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//let containerView = segue.destination as? ContainerViewController
if containerView == nil{
containerView = segue.destination as? ContainerViewController
}
if openMapView == true{
containerView!.moveToMapView()
}
else if openPlacesList == true{
containerView!.MoveToOpenPlaces()
}
}
}
//search bar delegate functions
extension MainScreen: UISearchBarDelegate{
//detects when text is entered
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
openPlacesList = true
openMapView = false
containerView!.MoveToOpenPlaces()
}
}
ContainerViewController.swift:
class ContainerViewController: UIViewController {
private var childViewController: UIViewController!
private var first: UIViewController?
private var sec: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MainToMap"{
first = segue.destination as! MapViewController
self.addChild(first!)
self.view.addSubview(first!.view)
self.didMove(toParent: self)
}else{
sec = segue.destination as! PlacesListController
}
if(first != nil && sec != nil){
interchange(first!,sec!)
}
}
func interchange(_ oldVc: UIViewController,_ newVc: UIViewController ){
oldVc.willMove(toParent: nil)
self.addChild(newVc)
self.view.addSubview(newVc.view)
self.transition(from: oldVc, to: newVc, duration: 2, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
newVc.view.alpha = 1
oldVc.view.alpha = 0
}, completion: { (complete) in
oldVc.view.removeFromSuperview()
oldVc.removeFromParent()
newVc.willMove(toParent: self)
})
}
func moveToMapView(){
performSegue(withIdentifier: "MainToMap", sender: nil)
}
func MoveToOpenPlaces(){
performSegue(withIdentifier: "MainToSearches", sender: nil)
}
}
The problem is that when I press the search bar, it calls the method interchange and then it just gives a SIGABRT 1 error. I tried this tutorial: https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1 and many more but so far no luck. I am stucked here and don't know how i can solve this problem.
Stack: https://i.stack.imgur.com/Zqpm1.png
SIGABR 1 Error: https://i.stack.imgur.com/NBgEN.png
You appear to be trying to manually transition between child view controllers, but at the same time using segues (which do their own transitioning for you). Eliminate the segues (other than the initial embed segue, if you're using a storyboard with a "container view"), and just manually instantiate the child view controllers using their storyboard IDs. But don't use segues and then try to replace the child view controllers in prepare(for:sender:).
Also, when you use transition(from:to:duration:options:animations:completion:), you should not add the views the the view hierarchy yourself. That method does that for you (unless you use the showHideTransitionViews option, which tells the method that you're taking this over, something we don't need to do here). Likewise, when you use the transitionCrossDissolve option, you don't need to mess with alphas, either.
Thus, using the code snippet from that article you reference, you can do:
class FirstViewController: UIViewController {
#IBOutlet weak var containerView: UIView! // the view for the storyboard's "container view"
#IBOutlet weak var redButton: UIButton! // a button to transition to the "red" child view controller
#IBOutlet weak var blueButton: UIButton! // a button to transition to the "blue" child view controller
// tapped on "transition to red child view controller" button
#IBAction func didTapRedButton(_ sender: UIButton) {
redButton.isEnabled = false
blueButton.isEnabled = true
let oldVC = children.first!
let newVC = storyboard!.instantiateViewController(withIdentifier: "RedStoryboardID")
cycle(from: oldVC, to: newVC)
}
// tapped on "transition to blue child view controller" button
#IBAction func didTapBlueButton(_ sender: UIButton) {
blueButton.isEnabled = false
redButton.isEnabled = true
let oldVC = children.first!
let newVC = storyboard!.instantiateViewController(withIdentifier: "BlueStoryboardID")
cycle(from: oldVC, to: newVC)
}
func cycle(from oldVC: UIViewController, to newVC: UIViewController) {
// Prepare the two view controllers for the change.
oldVC.willMove(toParent: nil)
addChild(newVC)
// Get the final frame of the new view controller.
newVC.view.frame = containerView.bounds
// Queue up the transition animation.
transition(from: oldVC, to: newVC, duration: 0.25, options: .transitionCrossDissolve, animations: {
// this is intentionally blank; transitionCrossDissolve will do the work for us
}, completion: { finished in
oldVC.removeFromParent()
newVC.didMove(toParent: self)
})
}
func display(_ child: UIViewController) {
addChild(child)
child.view.frame = containerView.bounds
containerView.addSubview(child.view)
child.didMove(toParent: self)
}
func hide(_ child: UIViewController) {
child.willMove(toParent: nil)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
That yields:
I have a containerView added to a View Controller. I am hoping to on swipe of the containerView change the view in the container. However when I use the following in my swipe action function it adds the view to the whole page not just changing the view inside the container.
class SwipeDateViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet weak var swipeContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func swipeLeftHandler(_ sender: UISwipeGestureRecognizer) {
let viewController = self.storyboard!.instantiateViewController(withIdentifier: "swipeViewControllerStoryboard") as! SwipeViewController
self.swipeContainer.addSubview(viewController.view)
}
}
How do I just change the view in the container and not update the whole screen?
I think maybe you could add modify function in your custom vc.
Then just run function of it.
For example:
var customVC:EmbeddedViewController?
func addView() {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "EmbeddedViewController") as! EmbeddedViewController
self.addChild(vc)
vc.view.bounds = CGRect.init(x: 20, y: 40, width: 50, height: 60)
self.view.addSubview(vc.view)
vc.didMove(toParent: self)
customVC = vc
}
#IBAction func actionAddView(_ sender: Any) {
customVC?.changeColor(color: UIColor.black)
}
EmbeddedViewController
class EmbeddedViewController: UIViewController {
public func changeColor(color:UIColor) {
self.view.backgroundColor = color
}
}
I have already read this LINK , but not working for me. I want to show a viewController as a subview in another viewController.
Here is my code -
import UIKit
import CarbonKit
class ViewController: UIViewController, CarbonTabSwipeNavigationDelegate {
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let items = ["All", "WOMEN", "MEN", "KIDS", "HOME", "CITY"]
let carbonTabSwipeNavigation = CarbonTabSwipeNavigation(items: items, delegate: self)
carbonTabSwipeNavigation.insert(intoRootViewController: self)
}
func carbonTabSwipeNavigation(_ carbonTabSwipeNavigation: CarbonTabSwipeNavigation, viewControllerAt index: UInt) -> UIViewController {
// let screen = self.storyboard?.instantiateViewController(withIdentifier: "demo") as! demo
// showSubViewContrller(subViewController: vc)
// return screen
let storyBoard = getStoryBoardByIndentifier(identifier: "All")
let vc = storyBoard.instantiateViewController(withIdentifier: "AllViewController") as! AllViewController
showSubViewContrller(subViewController: vc)
return vc
}
//Subview Controller
func showSubViewContrller(subViewController:UIViewController) {
self.addChildViewController(subViewController)
subViewController.view.frame = containerView.frame
self.containerView.addSubview(subViewController.view)
subViewController.didMove(toParentViewController: self)
}
func getStoryBoardByIndentifier(identifier:String)->UIStoryboard {
return UIStoryboard.init(name: identifier, bundle: nil)
}
}
I have a NavigationBar and a tapBar. Would like to show the viewController inside the view in a container.
But when the view loads it's coverUp/hide the tabBar.
How to solve this and show the viewController in my specified container.
Project Link - GitHub
Somehow i am able to fix your issue with below changes:
Replace this method carbonTabSwipeNavigation.insert(intoRootViewController: self) with carbonTabSwipeNavigation.insert(intoRootViewController: self, andTargetView: containerView) in viewDidLoad
Note : Give UITaBar bottom constraint to SuperView not SafeArea:
Add below code in ViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tabbar.invalidateIntrinsicContentSize()
}
After doing this when you run you will UITabBar: