Add view over tableview (UITableViewController) - iphone

Situation: I've got a UITableViewController loading some data asynchronously from a service. During this time I would like to place a full screen (except navigation bar) view over the table view showing my custom indicator and text.
Problem: The problem I'm facing is that when my custom view (it has a red background) is placed over the UITableView the lines of the table view are shown trough my custom view (see image below).
What I tried:
I tried to use insertBelow and above, didn't work. I also tried to do: tableview.Hidden = true, but this also hides the custom view for some reason as seen on image 2.
Image1: For some reason I can see the lines threw my view.
Image 2: Tableview + custom view gone when hidden = true used.
My code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
UIView view = new UIView (new RectangleF (0, 0, this.TableView.Frame.Width, this.TableView.Frame.Height));
view.BackgroundColor = UIColor.Red;
this.TableView.AddSubview (view);
TableView.Source = new SessionTableViewSource ();
}

You can use self.navigationController.view as view for adding subview.

The issue is that the View of a UITableViewController is a UITableView, so you cannot add subviews to the controller on top of the table.
I'd recommend switching from a UITableViewController to a simple UIViewController that contains a UITableView. This way the controller main view is a plain UIView that contains a table, and you can add subviews to the main UIView and they will be placed on top of the table view.

You can try to add the view to the window instead of nesting it in the table view like this:
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: overlayview];

UIWindow* window = [[UIApplication sharedApplication].delegate.window;
[window addSubview: your-overlayview];

Swift / Storyboard Solution
Note: The code below assumes one has a custom view (ratingView in my case) that is to be presented over a UITableView.
I've read many answers to this and similar questions on SO. The other answers from these sources worked to varying degrees for me (e.g.,view loaded but not shown or not accessible,...). I am using Swift 2.0+ and I am sharing the complete solution for doing this using a UITableViewController.
Create an outlet to the Navigation Bar and the view, which you want to bring over the tableview.
//MARK:Outlets
#IBOutlet weak var navBar:UINavigationBar!
#IBOutlet var ratingView: MNGStarRating!
In my case I also wanted to animate the view over the tableview so I used a class variable to hold a reference to the inflection point and a point above the scene (off-screen).
var centerYInflection:NSLayoutConstraint!
var aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)
Then in viewDidLoad I called a function (configureRatingViewAutoLayout) which configures and adds the constraints for the new view to be animated over the tableview.
func configureRatingViewAutoLayout() {
//REQUIRED
self.navBar.superview?.addSubview(self.ratingView)
var newConstraints:[NSLayoutConstraint] = []
newConstraints.append(self.ratingView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor,constant: 10))
newConstraints.append(self.ratingView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor,constant: 10))
newConstraints.append(self.ratingView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor))
//hides the rating view above the scene
self.centerYInflection = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor, constant: self.aPointAboveScene)
//the priority must be set below 1000 if you intend to change it after it has been added to a view
self.centerYInflection.priority = 750
newConstraints.append(self.centerYInflection)
//constraints must be added to the container view of the two items
self.ratingView.superview?.addConstraints(newConstraints)
}
Nota Bene - On a UITableViewController; the self.view is the
self.tableView. They point to the same thing so I guess one could also
use the self.tableView reference above.
Sometime later... In response to a UIControl event I call this method.
#IBAction func toggleRatingView (sender:AnyObject?){
//REQUIRED
self.ratingView.superview?.layoutIfNeeded()
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.37, initialSpringVelocity: 0.99, options: [.CurveEaseOut], animations: { () -> Void in
if CGRectContainsRect(self.view.frame, self.ratingView.frame) {
//in frame ~ animate away
//I play a sound to alert the user something is happening
self.centerYInflection.constant = self.aPointAboveScene
self.centerYInflection.priority = UILayoutPriority(950)
//I disable portions of the UI
self.disableUIElements(nil)
} else {
//out of frame ~ animate in
//I play a different sound here
self.centerYInflection.constant = 0
self.centerYInflection.priority = UILayoutPriority(950)
//I enable the UI fully
self.enableUIElements(nil)
}
//REQUIRED
self.ratingView.superview?.setNeedsLayout()
self.ratingView.superview?.layoutIfNeeded()
}) { (success) -> Void in
//do something else
}
}
These helper methods can be configured to control access to elements in your scene during the presentation of the view.
func disableUIElements(sender:AnyObject?) {
//UI
}
func enableUIElements(sender:AnyObject?) {
//UI
}
Caveats
My view is a custom view in the Storyboard (sitting outside of the
tableview but connected to the TableView Controller). The view has a
required user runtime attribute defined layer.zPosition with a Number value set to 2 (this ensures that it presents in front of the
UITableView).
One could also try playing around with bringSubviewToFront:
and sendSubviewToBack: methods if you don't want to set the zPosition
(I think zPosition is simpler to use)

Try this to hook a button at bottom of the UITableViewController
declare button as a variable:
var submitButton: UIButton!
and in viewDidLoad:
submitButton = UIButton(frame: CGRect(x: 5, y: UIScreen.main.bounds.size.height - 50, width: UIScreen.main.bounds.size.width - 10, height: 50))
submitButton.backgroundColor = UIColor.init(red: 180/255, green: 40/255, blue: 56/255, alpha: 1.0)
submitButton.setTitle("Submit", for: .normal)
submitButton.titleLabel?.font = UIFont(name: "Arial", size: 15)
submitButton.titleLabel?.textColor = .white
submitButton.addTarget(self, action: #selector(submit), for: .touchUpInside)
submitButton.layer.cornerRadius = 5
self.view.addSubview(submitButton)
and implement this method:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
submitButton.frame = CGRect.init(x: submitButton.frame.origin.x, y: UIScreen.main.bounds.size.height + scrollView.contentOffset.y - 50, width: submitButton.frame.width, height: submitButton.frame.height)
}

This works for me:
if let myTopView = Bundle.main.loadNibNamed("MyTopView", owner: self, options: nil)?.first as? MyTopView {
if let view = UIApplication.shared.keyWindow{
view.addSubview(myView);
myTopView.translatesAutoresizingMaskIntoConstraints = false
myTopView.topAnchor.constraint(equalTo: view.topAnchor ).isActive = true
myTopView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
myTopView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myTopView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}

Related

Working with 2 UINavigationControllers inside a ViewController as Child causes only 1 to respond, the other does not respond

I'm working with a project where I start with a ContainerViewController having 2 controllers added as a child:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
window?.rootViewController = ContainerViewController()
window?.makeKeyAndVisible()
}
The ContainerViewController:
class ContainerViewController: UIViewController {
var panGestureRecognizer: UIPanGestureRecognizer!
private var menuWidth: CGFloat = UIScreen.main.bounds.width - 50
let menuController = SideMenuViewController()
let mainController = MainViewController()
override func viewDidLoad() {
super.viewDidLoad()
addChildVCs()
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(panGestureRecognizer)
}
private func addChildVCs() {
addChild(menuController)
menuController.view.frame = CGRect(x: 0 - view.frame.size.width, y: 0, width: menuWidth, height: view.frame.size.height)
view.addSubview(menuController.view)
menuController.delegate = self
menuController.didMove(toParent: self)
addChild(mainController)
mainController.delegate = self
view.addSubview(mainController.view)
mainController.didMove(toParent: self)
}
With this panGesture I just show/hide a SideMenu, nothing strange here.
Inside this MainViewController, I have a TabBarController named mainController and another UINavigationController that store user conversations, named ConversationViewController(), both added as a child like this:
class MainViewController: UIViewController {
let mainController = MainTabController()
let conversationNavigationController = UINavigationController(rootViewController: ConversationViewController())
override func viewDidLoad() {
super.viewDidLoad()
addChildVCs()
}
private func addChildVCs() {
addChild(mainController)
mainController.menuDelegate = self
view.addSubview(mainController.view)
mainController.didMove(toParent: self)
addChild(conversationNavigationController)
conversationNavigationController.view.frame = CGRect(x: view.frame.size.width, y: 0, width: view.frame.size.width, height: view.frame.size.height)
view.addSubview(conversationNavigationController.view)
conversationNavigationController.didMove(toParent: self)
}
The conversationNavigationController origin.x point is starting at the end of the mainController (tabBar) so I can panGesture to the right and show/hide this other UINavigationController.
The problem is when I start the execution I can interact with the mainController(tabBarController) but when I swipe to the right and I have the conversationNavigationController on screen, it doesn't respond to any actions.
Basically I'm working with 2 navigationControllers at the same time but I don't understand why 1 is working and the other one does not respond to any actions.
Any clue on what's the problem? I can provide more code if needed!
Thanks in advance
If you just add your child view controller's content views to your MainViewController's content view, Auto Layout doesn't know what to do with them. At the drop of a hat, Auto Layout will resize your child view controllers in unexpected and undesired ways.
I'm guessing that your view controllers' view frames are getting messed up. It might be that there is some other problem with your view controllers that's preventing them from responding to events, but first I would rule out view layout problems.
I would suggest adding container views to your main view controller, including Auto Layout constraints to put them where you want them.
Then add your child view controllers' views as subviews of those container views, and anchor all 4 edges of your child view controller's view to the edges of their parent views.
When you're first working on it, it is worth adding a borderWidth and borderColor to your different views' layers so you can see what's going on.

Swift sub view hold old values (stacking new values on top of it)

[solved :)
by adding self.progressScrollView?.removeFromSuperview() ]
I am new to swift.
and having below issue :( please help me.
every time I come back to this main screen (using back button)
my scroll view's content are showing new values on top of old values
for example.
I have 4 items(a,b,c,d) in an array.
and main page show in random order
a
b
c
d
after that, i went to another page and came back to this screen.
it shows new values on top of old values
a -> b (this value is on top of 'a')
b -> d (this value is on top of 'b')
c -> c (this value is on top of 'c')
d -> a (this value is on top of 'd')
what I did?
I re-initialize my array in viewdidappear()
added below in gotopage()
dismiss(animated: true, completion: nil)
add removefromsuperview & view.addsubview in self.displayProgressBar()
(what #sandeep suggested.. but doesn't work)
my flow:
1.call displayprogressbar in viewdidload
self.displayProgressBar()
class ViewController: UIViewController ,ChartViewDelegate, UIScrollViewDelegate {
...
var progressScrollView: UIScrollView!
...
override func viewDidLoad() {
super.viewDidLoad()
self.getfirebasedata()
2.add progress bars and labels dynamically based on the list size.
func getfirebasedata(...){
...
self.displayProgressBar(...)
}
func displayProgressBar(...){
self.progressScrollView?.removeFromSuperview() //this is the fix
self.progressScrollView = UIScrollView()
self.progressScrollView.isScrollEnabled = true
self.progressScrollView.frame = CGRect(x: 0, y: 300,
width: self.view.frame.size.width, height: 200)
self.progressScrollView.contentSize = CGSize(width: self.view.frame.size.width,
height: self.view.frame.size.height)
let axisXstartPoint = ( self.view.frame.size.width - 200 ) / 2
let progressView = UIProgressView(progressViewStyle: .bar)
...
let label = UILabel()
label.frame = CGRect(x: axisXstartPoint, y: CGFloat(axisY-15), width: 200, height: 20)
label.textAlignment = .left
label.text = book.bookName
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tapBookName)
progressScrollView.addSubview(label)
progressScrollView.addSubview(progressView)
self.view.addSubview(progressScrollView)
}
when I go to another page I use this method
#objc #IBAction func goToAddPage(sender: UITapGestureRecognizer) {
let label = sender.view as? UIButton
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let addViewController = storyBoard.instantiateViewController(withIdentifier: "add") as! AddViewController
addViewController.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(addViewController, animated: true)
dismiss(animated: true, completion: nil)
}
4.this is my storyboard
Not sure from where you call displayProgressBar as its not clear from your question, what you can do is add these statement as either 1st statment in displayProgressBar or as a statement before calling displayProgressBar
Approach 1:
self.progressScrollView?.removeFromSuperview() //? because for the first time implicit optional will be nil
self.progressScrollView = UIScrollView()
In ViewDidLoad you have self.view.removeFromSuperview() which I have no idea why? Makes no sense to me why would you try to remove ViewController's view? May be you were trying to remove subviews I assume, if yes, thats not the proper way to remove subviews, you iterate over subViews and call removeFromSuperview on each subView, but I dont think you need that, clearly from your code you hold a reference to progressScrollView somewhere in your code, just remove it from subview and recreate the progressScrollView as
progressScrollView = UIScrollView()
And if you are not holding a reference to progressScrollView and instead you were creating it as only local variable in displayProgressBar method, hold a reference to this view, to do that declare a instance variable var progressScrollView: UIScrollView!
Approach 2:
And if you dont prefer creating progressScrollView() again and again, you can always iterate over its subviews and remove them from superview one by one
for view in progressScrollView.subviews {
view.removeFromSuperview()
}
Make sure you run self.view.addSubview(progressScrollView) only once (like in ViewDidLoad or somewhere) if you decide to go with approach 2.
In either case you need to hold a reference to progressScrollView

How to modify slide transition

I had asked a question similar to this once before but I didn't realize and don't think I can use the same method due to the nuances of the two methods. (How to stop slide transition into Second VC before it covers UIView?)
I ultimately learned how to do the transition using APPCoda's lesson(http://www.appcoda.com/custom-segue-animations/)
The original question resulted in an answer providing a solution using container views and hard coded views. What I am wondering is if I can get the same effect using two separate view controllers and linking them through a segue with a gesture recognizer.
What I would like to accomplish is:
Have my initial view controller
Tap Button and have Second View Controller Overlap the first View Controller Partially (By partially I mean I have a UIView on the first View Controller that I want to remain visible. So the top of the second view controller will slide up until it hits the bottom of the UIView).
What I currently have is the original view controller being pushed up and out of the screen by the second view controller sliding up from the bottom of the screen.
Code that handles the transition using a segue from one VC to the sliding VC:
import Foundation
class CustomSegueToSecondVC: UIStoryboardSegue
{
override func perform() {
let originalVC = self.sourceViewController .view as UIView!
let slidingVC = self.destinationViewController.view as UIView!
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
slidingVC.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(slidingVC, aboveSubview: originalVC)
UIView.animateWithDuration(0.4, animations: { () -> Void in
originalVC.frame = CGRectOffset(originalVC.frame, 0.0, -screenHeight)
slidingVC.frame = CGRectOffset(slidingVC.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
}
}
};

Custom views in Horizontal scroll view. Is it possible?

I am designing an app which has a screen in which I have a horizontal scroll view which I fill with UIViews dynamically depending upon the number of data I have in my array . I did the same via programmatically. I have mentioned my approach below.
1) I put a Scroll view for scrolling horizontally and created a reference for that in my class.
2) I programatically added views as per my code -
var imagevieww = UIImageView()
#IBOutlet weak var hrzntlscrl: UIView!
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad()
{
super.viewDidLoad()
let viewcount = 15
for var i = 0; i < viewcount; i++
{
let viewnew = UIView(frame: CGRectMake( hrzntlscrl.frame.origin.x+110*CGFloat(i), 0, 100.0, hrzntlscrl.frame.height))
viewnew.backgroundColor = UIColor.orangeColor()
imagevieww = UIImageView(frame: CGRectMake(0, 10, 100.0, 50))
imagevieww.backgroundColor = UIColor.blackColor()
viewnew.addSubview(imagevieww)
scrollview.addSubview(viewnew)
}
}
So I just wanted to know that instead of creating a view and the corresponding subviews eg. here imageview and setting their location and frame size programatically , Can I have a standard custom view designed in my IB and use any reference of that in my for loop instead of creating one programmatically? If we can do that,can you please give me some steps.
Yes. This is possible. You can instantiate a class from a nib with
let customView: CustomView = NSBundle.mainBundle().loadNibNamed("CustomViewNibName", owner: self, options: nil)[safe: 0] as? CustomView
You would also need to set the content size of the horizontal scroll view to the combined width of all the views.
But I think that your use case would be better served by using a UICollectionView instad of a scroll view, UICollectionView does all this and more in a much simpler implementation.

Where to add subviews in a viewcontrollers lifecycle

For me it make sense to add subviews in viewDidLoad, because it gets call when the view load, but i can't do that with my subviews. I have subviews which frame depends on another already added subviews height and width, and height and width is not correct before viewDidAppear. So i add my subviews in viewDidAppear, but viewDidAppear gets call everytime the view appears which is bad. So for my solution right now i add my subviews in viewDidAppear and when view disappear I remove the subviews in viewDidDisappear. Is there another way to archieve this without I manual removing subviews from superview.
Example
override func viewDidAppear(animated: Bool) {
print(PercentageView.frame.width)
print(PercentageView.frame.height)
CreateInfoRecipeLabels()//Add subviews
CreateCircleDiagram() //Add subviews
}
override func viewDidDisappear(animated: Bool) {
//When view disappear deallocate subviews
for view in PercentageView.subviews {
view.removeFromSuperview()
}
}
UPDATE - Tried this instead, but it is like it can't add subview before there is a frame?
#IBOutlet weak var RecipeInfoContain: UIView!
var MinLabel: SMIconLabel?
var Min = "15"
override func viewDidLoad() {
super.viewDidLoad()
RecipeInfoContain.addSubview(MinLabel!)
// Do any additional setup after loading the view.
}
override func viewWillLayoutSubviews() {
MinLabel = SMIconLabel(frame: CGRectMake(RecipeInfoContain.frame.width/4 - 50, RecipeInfoContain.frame.height/2 - 10, 100, 20))
MinLabel!.text = Min + " min"
//MinLabel.backgroundColor = UIColor.redColor()
MinLabel!.font = UIFont(name: "OpenSans", size: 11)
MinLabel!.textColor = UIColor.whiteColor()
MinLabel!.icon = UIImage(named: "Clock")
MinLabel!.clipsToBounds = true
MinLabel!.iconPadding = 5
MinLabel!.iconPosition = .Left
MinLabel!.textAlignment = .Left
}
Two thoughts:
If you use autolayout to dictate the frame of the subviews, then you can add them in viewDidLoad and the autolayout engine will take care of sizing them appropriately.
Just set translatesAutoresizingMaskIntoConstraints for the subviews to false and then add the appropriate constraints rather than setting frame values manually.
If you really want to manually adjust the frames, you can add the subviews in viewDidLoad, but then adjust the frames in viewWillLayoutSubviews.