how to append child view controller in specific view with fixed height - ios

I'm getting problem in size while adding child view controller
XIB veiw :
class StoreDetailView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
func addChild(vc: UIViewController){
let v = StoreDetailView(nibName: "StoreDetailView", bundle: nil)
v.view.frame = CGRect(x: 0, y: 0, width: vc.view.frame.width, height: 80)
appendAbleView.addSubview(v.view)
vc.addChild(v)
heightOfAppendAbleView.constant = 80
_scrollView.updateContentView()
}
Output what i'm getting:
Height is not working here :
v.view.frame = CGRect(x: 0, y: 0, width: vc.view.frame.width, height: 80)
i need to show it in grey view which height 80 heightOfAppendAbleView.constant = 80

Only addChild is not enough. Let's try this extension
extension UIViewController {
// Add a child view controller, its whole view is embeded in the containerView
public func addController(controller: UIViewController, containerView: UIView) {
if let parent = controller.parent, parent == self {
return
}
addChild(controller)
controller.view.frame = CGRect.init(origin: .zero, size: containerView.frame.size)
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.addSubview(controller.view)
controller.didMove(toParent: self)
}
}

Related

How do you pass a gesture between subviews in UIKit?

I've got a ViewController with three subviews. I'm trying to get them to detect touches in their bounds from a starting point outside their bounds without the user lifting their finger (ie the user dragging into the view). I thought hitTest would do this but it only works for separate taps. I assume this is probably passing a gesture through instead but I've not found out how to implement this.
class SuperViewController: UIViewController {
var view01 = UIView(frame: CGRect(x: 0, y: 0, width: 1000,
height: 800))
var view02 = UIView(frame: CGRect(x: 0, y: 0, width: 600,
height: 400))
let view03 = UIView(frame: CGRect(x: 0, y: 0, width: 300,
height: 200))
override func viewDidLoad() {
super.viewDidLoad()
self.view = TestView()
view01.backgroundColor = .orange
view02.backgroundColor = .blue
view03.backgroundColor = .green
self.view.addSubview(view01)
self.view.addSubview(view02)
self.view.addSubview(view03)
}
}
Which produces this
And then I've subclassed UIView for the SuperViewController's view.
class TestView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.isUserInteractionEnabled, !isHidden, alpha > 0.01 else {return nil}
if self.point(inside: point, with: event) {
for subview in subviews.reversed() {
let hitView = subview.hitTest(point, with: event)
if hitView != nil {
hitView?.backgroundColor = .red
return hitView
}
}
return self
}
return nil
}
}
So each one turns red when the user taps. But ideally I want them to each respond with one drag from the top left corner of the screen to the other.
You can accomplish this with a UIPanGestureRecognizer.
Here's an example below:
class ViewController: UIViewController {
var view01 = UIView(frame: CGRect(x: 0, y: 0, width: 1000,
height: 800))
var view02 = UIView(frame: CGRect(x: 0, y: 0, width: 600,
height: 400))
let view03 = UIView(frame: CGRect(x: 0, y: 0, width: 300,
height: 200))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view01.backgroundColor = .orange
view02.backgroundColor = .blue
view03.backgroundColor = .green
self.view.addSubview(view01)
self.view.addSubview(view02)
self.view.addSubview(view03)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
self.view.addGestureRecognizer(gestureRecognizer)
}
#objc
private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let view = gestureRecognizer.view else {
return
}
let translation = gestureRecognizer.translation(in: view)
for subview in view.subviews.reversed() {
if let hitView = subview.hitTest(translation, with: nil) {
hitView.backgroundColor = .red
return
}
}
}
}

Creating custom UIView, or child VCs

So, i have the following case. I got a custom tab layout, ie a horizontal UIScollview with paging. In each page there are some controls and a UITablevie, "bundles" in a custom UIView.
I create a ContainerVC, which has the scrollview, and then I initialize the two UIViews and add them as subviews in ContainerVC. To do so, i have created a custom UIView class and have have set this UIView as the owner of the .xib file. I also add the .xib class as my custom UIView.
Although this works, at least UI wise, i have some functionality problems, like the following.
In the init method of each UIView I initialize the view with
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
and then set the UITableViewDelegate and UITableViewDateSource to self.
However, when the data that feed the table arrives in the view, and i reload the tableview, nothing happens (the reload method is run in the main Thread), until i scroll the UITableview.
Is this the correct course of action?
VC
func createUsageHistoryTabs() -> [Consumption] {
var tabs = [Consumption]()
let v1 = Consumption(pageIndex: 0, viewModel: viewModel)
let v2 = Consumption(pageIndex: 1, viewModel: viewModel)
tabs.append(v1)
tabs.append(v2)
return tabs
}
func setupScrollView() {
if(!hasBeenCreated) {
let tabs = createUsageHistoryTabs()
scrollview.frame = CGRect(x: 0, y: 0, width: container.frame.width, height: container.frame.height)
scrollview.contentSize = CGSize(width: view.frame.width * CGFloat(tabs.count), height: 1.0)
scrollview.isPagingEnabled = true
for i in 0 ..< tabs.count {
let slideView = tabs[i]
slideView.frame = CGRect(x: view.frame.width * CGFloat(i), y: scrollview.frame.origin.y, width: scrollview.frame.width, height: scrollview.frame.height)
scrollview.addSubview(slideView)
}
hasBeenCreated.toggle()
}
}
UIView:
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(pageIndex: Int, viewModel: UsageHistoryViewModel) {
self.init(frame: CGRect.zero)
self.pageIndex = pageIndex
self.viewModel = viewModel
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.fixInView(self)
setupView()
footer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFooterTap)))
viewModel?.cdrDelegate = self
tableView.register(EmptyCell.self, forCellReuseIdentifier: "EmptyCell")
tableView.register(UINib(nibName: "TVCell", bundle: nil), forCellReuseIdentifier: "TVCell")
tableView.allowsSelection = false
tableView.delegate = self
tableView.dataSource = self
}
In this func:
func setupScrollView() {
if(!hasBeenCreated) {
// this line creates a LOCAL array
let tabs = createUsageHistoryTabs()
scrollview.frame = CGRect(x: 0, y: 0, width: container.frame.width, height: container.frame.height)
scrollview.contentSize = CGSize(width: view.frame.width * CGFloat(tabs.count), height: 1.0)
scrollview.isPagingEnabled = true
for i in 0 ..< tabs.count {
let slideView = tabs[i]
slideView.frame = CGRect(x: view.frame.width * CGFloat(i), y: scrollview.frame.origin.y, width: scrollview.frame.width, height: scrollview.frame.height)
scrollview.addSubview(slideView)
}
hasBeenCreated.toggle()
}
}
You are creating a local array of Consumption objects. From each object, you add its view to your scrollView. As soon as that func exits, tabs no longer exists... it goes "out-of-scope".
You want to create a class-level var:
var tabsArray: [Consumption]?
and then modify that var instead of creating a local instance:
func setupScrollView() {
if(!hasBeenCreated) {
// this line creates a LOCAL array
tabsArray = createUsageHistoryTabs()
...
Your custom classes should then be available until you delete them (or tabsArray goes out-of-scope).
You may need to make some other changes, but that should, at least, be a start.
EDIT after comment discussion...
If the tableviews are working, but not "updating" until the are scrolled - you said reload is being called on the main thread - you might try either of these options (or both... experiment):
tableView.reloadData()
tableView.setNeedsLayout()
tableView.layoutIfNeeded()
tableView.reloadData()
tableView.beginUpdates()
tableView.endUpdates()

Swift add Custom View to screen with UIApplication.shared.keyWindow?.addSubview

hope this is an easy one...
I am in a empty new project.
I have added a custom view called MyCustomView:
import UIKit
public class MyCustomView: UIView{
private var littleView: UIView!
open class func show() -> UIView{
let bigView = MyCustomView()
bigView.configureView()
UIApplication.shared.keyWindow?.addSubview(bigView)
return bigView
}
private func configureView(){
let screenSize = UIScreen.main.bounds.size
self.frame = CGRect(x: 0,
y: 0,
width: screenSize.width,
height: screenSize.height)
littleView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
littleView.backgroundColor = .black
addSubview(littleView)
}
}
In the ViewController doing this:
override func viewDidLoad() {
let test = MyFirstView.show()
}
I hoped this will present the view, but I still have to use self.view.addSubview(test) to see it....
I thought with UIApplication.shared.keyWindow?.addSubview(bigView) and adding a subView to it, it should present the View.
What am I missing?
Add the subview in viewDidAppear
override func viewDidAppear() {
let test = MyFirstView.show()
}

How do I programmatically change the height of a navigationBar in Swift?

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let height = CGFloat(84)
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: height)
}
This code simply inserts space above the titleView. A custom titleView at point (0,0) has ~20 points of space above it. A height >40 starts to run off the navBar.
You can subclass UINavigationBar :
class CustomNavigationBar: UINavigationBar {
override func sizeThatFits(_ size: CGSize) -> CGSize {
let newSize :CGSize = CGSize(width: self.frame.size.width,height: 84)
return newSize
}
}
Then create the navigation controller and use the initialiser to use your custom navigation bar class.
let nav = UINavigationController(navigationBarClass:CustomNavigationBar.self,toolbarClass: nil)
All existing behavior for UINavigationBar is preserved and your custom height is adopted.
OR
Like you already tried :
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let height: CGFloat = 84 //whatever height you want
let bounds = self.navigationController!.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height + height)
}
OR :
You can try this solution Changing the height of the Navigation bar iOS Swift
you can use a custom view to replace the navigation bar.This is more easy and flexible. hide the navi bar and implement a custom view.
class ViewController : UIViewController {
var navBar: UINavigationBar = UINavigationBar()
override func viewDidLoad() {
super.viewDidLoad()
self.setCustomNavBarView()
}
func setCustomNavBarView() {
self.navBar.frame = CGRect(x: 0, y: 0, width: 350, height: 50) // Set you custom width and Height
self.navBar.backgroundColor = UIColor.gray
self.view.addSubview(navBar)
}
}
A simple tutorial on how to do that:
Hope this helps!!
class ViewController: UIViewController {
var navBar: UINavigationBar = UINavigationBar()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.setCustomNavBarView()
}
`func setCustomNavBarView() {
self.navBar.frame = CGRect(x: 0, y: 0, width: 350, height: 100) // Set you custom width and Height
self.navBar.backgroundColor = UIColor.gray
self.view.addSubview(navBar)
}
`
swift 3 updated code here

How to change the view's bounds within UIPresentationController?

I use subclass of UIPresentationController to present some controller on the screen. This is how I prepare it:
controller.transitioningDelegate = self
controller.modalPresentationStyle = .Custom
presentViewController(controller, animated: true, completion: nil)
But within controller there is a textField, and I have added there observer for UIKeyboardDidShowNotification. Is it possible to update view's frame when keyboard appear?
This is how it looks like:
I need to change the bounds of that view because of keyboard.
containerView?.setNeedsLayout() needs to be called after whatever I change.
It is relative easy.
First you will need to observe for keyboard changes in your presenter
Listen to notifications .UIKeyboardWillShow, .UIKeyboardDidShow, .UIKeyboardWillHide, .UIKeyboardDidHide
I would recommend making a KeyboardObserver class for this with in example a static instance and store the keyboard variables (frame, animation speed etc) in there and add a delegate on that class to inform you on keyboard changes.
You then end up with something like this
extension PresentationController: KeyboardManagerDelegate {
internal func keyboardManager(_ manager: KeyboardManager, action: KeyboardManager.KeyBoardDisplayAction, info: KeyboardManager.Info) {
guard let containerView = containerView else { return }
UIView.animate(withDuration: info.animationDuration, delay: 0, options: info.animationOptions, animations: {
containerView.setNeedsLayout()
containerView.layoutIfNeeded()
}, completion: nil)
}
}
Next you will need to override frameOfPresentedViewInContainerView.
Example:
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else {
return .zero
}
let desiredSize = CGSize(width: 540, height: 620)
let width = min(desiredSize.width, containerView.width)
let x = round((containerView.width - width) / 2)
if KeyboardManager.shared.isKeyboardVisible {
let availableHeight = containerView.height - KeyboardManager.shared.keyboardFrame.height
let height = availableHeight - 40
return CGRect(x: x, y: 25, width: width, height: height)
} else {
let height = min(desiredSize.height, containerView.height)
let y = round((containerView.height - height) / 2)
return CGRect(x: x, y: y, width: width, height: height)
}
}
At the end also implement a layout method to update the view
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
}

Resources