I have example project on GitHub you can see on ->
https://github.com/LetSwiftDev/ContainerViewInsideScroll/tree/master
I have two, view controller, one is normal view controller named oneViewController second is twoViewController inside have UIScrollView and when I clicked OPEN TWO button going to twoViewController inside ContainerView but;
My twoViewController have different height size like 1500 and inside have scrollview and don't scroll to bottom? I didn't see middle side, bottom side labels.
How can I fix it?
Any idea?
Also my centerViewController codes under below. I'm controlling to past view controllers with centerViewController
import UIKit
open class centerViewController: UIViewController {
fileprivate weak var viewController : UIViewController!
fileprivate var containerViewObjects = Dictionary<String,UIViewController>()
open var currentViewController : UIViewController{
get {
return self.viewController
}
}
fileprivate var segueIdentifier : String!
#IBInspectable internal var startUp : String!
override open func viewDidLoad() {
super.viewDidLoad()
}
open override func viewDidAppear(_ animated: Bool) {
if let identifier = startUp{
segueIdentifierReceivedFromParent(identifier)
}
}
override open func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func segueIdentifierReceivedFromParent(_ identifier: String){
self.segueIdentifier = identifier
self.performSegue(withIdentifier: self.segueIdentifier, sender: nil)
}
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == segueIdentifier{
if viewController != nil{
viewController.view.removeFromSuperview()
viewController = nil
}
if ((self.containerViewObjects[self.segueIdentifier] == nil)){
viewController = segue.destination
self.containerViewObjects[self.segueIdentifier] = viewController
}else{
for (key, value) in self.containerViewObjects{
if key == self.segueIdentifier{
viewController = value
}
}
}
self.addChildViewController(viewController)
viewController.view.frame = CGRect(x: 0,y: 0, width: self.view.frame.width,height: self.view.frame.height)
self.view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
}
}
}
Here is your demo with solution. You can download it from here
https://www.dropbox.com/s/zvrjd0gp06xe5nw/ContainerViewInsideScroll-master%202.zip?dl=0
The thing is You have to learn autolayout for Scrollview.
I am just giving tips for the given constraint to scrollview.
First take scrollview and fit to the screen and give constraint as follows
Now take a view inside scrollview and fit to scrollview and give constraint like
Still its giving error of constraint. So now, give equal width of view to scrollview
and now you can take as many control as you want and give constraints as normal as we give to other controls.
Hope you understand well.
Enjoy Coding.
The reason is that scrollView is too long and hidden below the screen and contentSize isn't configured properly.
A lazy solution is to set them properly in code when twoViewController appears.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let width = self.scrollView.frame.width
scrollView.frame = CGRect(origin: scrollView.frame.origin,
size: CGSize(width: width, height: CGFloat(500)))
// Make sure this height is longer than scrollView.
scrollView.contentSize = CGSize(width: width, height: CGFloat(2000))
}
The proper solution (and correct way to implement a scrollview) is to fix scrollable content size ambiguity (auto layout errors in storyboard) like what #Jitendra Modi mentioned.
Related
I have a storyboard in which there is a separate view controller for a UIView. In that UIView I have to load 10 buttons in a single row in the case of IPad else I have to load 10 buttons in two rows. I have tried using 2 separate xib files and loading them statically but I want to do it programatically using view controllers.
I have added the screenshot of my storyboard.
Can anybody guide me on changing the views dynamically.Here is the code of the new ViewController for the buttons.
import Foundation
class TNPSRatingsViewController: UIViewController {
var selectedScoreButton: TNPSScoreButton?
var scoreSelectedOnce = false
#IBOutlet var buttons: [TNPSScoreButton]!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.modifyPositionOfTNPSButton()
}
override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: nil, completion: ({ [unowned self] _ in
self.modifyPositionOfTNPSButton()
}))
}
// MARK: - Private functions
fileprivate func modifyPositionOfTNPSButton() {
if UIApplication.shared.delegate?.window??.traitCollection.horizontalSizeClass == .compact {
//
//
} else {
//
//
}
}
}
Thanks in Advance
How about using a UICollectionView?
on iPad, use 1 row of 10 items
on iPhone use 2 rows of 5 items
You can stick to view controller containment, or implement the uicollectionview directly with your first screen being the delegate/datasource.
Try making a class that will tell that current device is iPad or iPhone
class Env
{
//Check if its iPad
static var iPad: Bool
{
return UIDevice.current.userInterfaceIdiom == .pad
}
}
Now While you are require to add subview in containerView
just make use of that class as:
class ViewController: UIViewController {
var myCustomContainerView = UIView()
var subviewCreatedXIBForiPad = UIView()
var subviewCreatedXIBForiPhone = UIView()
override func viewDidLoad() {
super.viewDidLoad()
Env.iPad ? myCustomContainerView.addSubview(subviewCreatedXIBForiPad) : myCustomContainerView.addSubview(subviewCreatedXIBForiPhone)
}
}
I have watched this tutorial
Snapchat Like Layout using View Controller Theme
then I embed one of the three controllers in Navigation Controller but it didn't work then i have tried to add it on the view controller how had the scroll view also didn't work + gives me overlap I didn't tried it in code tho but I don't think this is the cause , this is the code
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let V1 = self.storyboard?.instantiateViewController(withIdentifier: "V1") as UIViewController!
self.addChildViewController(V1!)
self.scrollView.addSubview((V1?.view)!)
V1?.didMove(toParentViewController: self)
V1?.view.frame = scrollView.bounds
let V2 = self.storyboard?.instantiateViewController(withIdentifier: "V2") as UIViewController!
self.addChildViewController(V2!)
self.scrollView.addSubview((V2?.view)!)
V2?.didMove(toParentViewController: self)
V2?.view.frame = scrollView.bounds
var V2Frame: CGRect = V2!.view.frame
V2Frame.origin.x = self.view.frame.width
V2?.view.frame = V2Frame
let V3 = self.storyboard?.instantiateViewController(withIdentifier: "V3") as UIViewController!
self.addChildViewController(V3!)
self.scrollView.addSubview((V3?.view)!)
V3?.didMove(toParentViewController: self)
V3?.view.frame = scrollView.bounds
var V3Frame: CGRect = V3!.view.frame
V3Frame.origin.x = 2 * self.view.frame.width
V3?.view.frame = V3Frame
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 3, height: self.view.frame.height)
self.scrollView.contentOffset = CGPoint(x: self.view.frame.width * 1 , y: self.view.frame.height)
}
how the code works ??
1- first I have added scroll view to a view controller
2- then inside the scroll view I have added 3 view controller and all the 3 controllers has own class like normal view controllers
3- the scroll View added the 3 controllers on its view by this code on the top
all the 3 controllers inheriting from own class and everything is wor fine
if anyone can help or need more description just ask
try below
Create ViewController in Storyboard or XIB and add ScrollView and PageControl
My Class as below:
class ScrollContentViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.pageControl.currentPage = 0
self.pageControl.numberOfPages = 4
var originX = CGFloat(0)
let width = scrollView.frame.size.width
let height = scrollView.bounds.size.height
for x in 0 ..< 4 {
let V1 = UIViewController()
self.scrollView.addSubview(V1.view)
V1.view.frame = CGRect(x: originX, y: 0, width: width, height: height)
V1.view.backgroundColor = x % 2 == 0 ? UIColor.lightGray : UIColor.darkGray
self.addChildViewController(V1)
originX += width
}
self.scrollView.contentSize = CGSize(width: originX, height: height)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
self.pageControl.currentPage = x
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I have a UIViewController (Main Menu) that is displayed after my other UIViewController (Loading Screen) finishes downloading/syncing data from the web. My Main Menu is having problems as it doesn't always display all the UIButtons that are on the view when it shows up! There is an inconsistancy, 1 out of every 10 times the Main Menu loads, all the UIButtons will appear.
Here's how the view should look:
Here's how the view usually shows up:
I can put my finger where the UIButtons should be and move my finger away and they'll appear. I can also tap where they should be and my segues will still trigger, but the UIButtons dont just automatically show up. It looks like
I attempted to add a MainMenuView.setNeedsDisplay() to the ViewDidLoad function and it didn't help. I also created a test UIButton on the view that triggers a MainMenuView.setNeedsDisplay() command but the hidden UIButtons remain hidden.
If I leave the screen idle for 30 or so seconds, all the UIButtons will randomly appear without me having to manually make them appear.
The Main Menu view is normal and all UIButtons are visible if I segue to it from any part of the app aside from my Loading Screen.
Edit: Main Menu Code - (references are Strong)
import UIKit
import CoreData
class MainMenuViewController: UIViewController {
// MARK: References
#IBOutlet var btnGasManifolds: UIButton!
#IBOutlet var btnAirCompressors: UIButton!
#IBOutlet var btnVacuumPumps: UIButton!
#IBOutlet var btnUnitConversion: UIButton!
#IBOutlet var btnCFMCalculator: UIButton!
#IBOutlet var mainMenuView: UIView!
var userData = [NSManagedObject]()
// MARK: Functions
override func viewDidLoad() {
var name = String()
for duserData in userData {
name = duserData.value(forKey: "nameFull") as! String
}
print("Main Menu\n->\(name)")
backgroundSetup()
}
override func viewDidAppear(_ animated: Bool) {
mainMenuView.setNeedsDisplay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnTest(_ sender: Any) {
mainMenuView.setNeedsDisplay()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
if segue.identifier == "settingsSegue" {
let destination = segue.destination as! SettingsViewController
destination.userData = userData
}
}
// Background Setup
func backgroundSetup() {
let background = UIImage(named: "Data Screens")
var imageView : UIImageView!
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = UIViewContentMode.scaleAspectFill
imageView.clipsToBounds = true
imageView.image = background
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubview(toBack: imageView)
}
}
The code which creates and shows the buttons after fetching the data from server needs to run on Main UI thread. If the code is running the background thread it will not properly display the UI elements and will take some time or manual scroll before showing the controls.
Run the code on Dispatch main queue.
dispatch_async(dispatch_get_main_queue(),{
//do some work
})
OR
Override one of the methods from ViewWillLayoutSubviews and ViewDidLayoutSubviews accordingly and run your code in these methods.
Problem relates to this.
I'm trying to use a custom view that I made that has two TextField inputs and a button, I made it using IB its xib, I've placed it in my story board and have it set to be at the bottom of my view.
The problem comes in when I want to make ContactInfoView the keyboards accessory view.
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet var cameraPreview: UIView!
#IBOutlet weak var ContactInfoView: KeyboardAccessoryView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
//cameraPreviewLayer!.frame = cameraPreview.bounds
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillAppear"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide"), name: UIKeyboardWillHideNotification, object: nil)
ContactInfoView.NameInput.inputAccessoryView = ContactInfoView
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
// keyboard stuff
var accessoryView = KeyboardAccessoryView(frame: CGRectZero);
override var inputAccessoryView: KeyboardAccessoryView {
return accessoryView
}
override func becomeFirstResponder() -> Bool {
return true
}
override func canBecomeFirstResponder() -> Bool {
return true
}
func keyboardWillAppear() {
print("Keyboard appeared")
}
func keyboardWillHide() {
print("Keyboard hidden")
}
}
ContactInfoView.NameInput.inputAccessoryView = ContactInfoView isn't placing the view on top of the keyboard.
I fooled out around with the code and it started to crash relating to the link provided, But trying that solution didn't work either.
According to the comment in the UITextView.h file:
#property (nullable, readwrite, strong) UIView *inputView;
#property (nullable, readwrite, strong) UIView *inputAccessoryView;
Presented when object becomes first responder. If set to nil, reverts to following responder chain. If set while first responder, will not take effect until reloadInputViews is called.
You should call reloadInputViews method after setting input.. property.
Yes, This Looks Little Tricky since everything seems fine and same as everyone and the Xcode Auto Suggestion and Completion Tools Helps you to complete the predicted "override" Functions..
Here Is A simple Mistakes that I made While Showing "inputAccessoryView"
I Entered Xcode Suggested Text For Completing "inputAccessoryView" And "inputAccessoryView" for Override Function.
But For Adding "override var inputAccessoryView: UIView?" Func its okay To Select from Xcode Suggestion / Auto Completion
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
For "override var canBecomeFirstResponder : Bool" you should make sure that
you type ": Bool" and not the "() -> Bool", Like I made mistakes couple of time.
So Please If you want You can type yourself or just Copy Below Code Paste in your Class Anywhere and it will work Smoothly.
override var inputAccessoryView: UIView? {
get {
let inputContainerView = UIView()
inputContainerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50)
inputContainerView.backgroundColor = UIColor.gray
let textfield = UITextField()
textfield.text = "asasfdfs df sdf sdf ds f"
textfield.frame = CGRect(x: 0, y: 0, width: inputContainerView.frame.size.width, height: 50)
inputContainerView.addSubview(textfield)
// You can add Any Other Objects Like UIButton, Image, Label //you want And Constraints too.
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
In the code above, you are assigning the property InputAccessoryView not the property inputAccessoryView. Case is important.
Assuming everything else is setup correctly, this should get it to work, but there is something I don't understand. Why is your text field/text view a property of your input accessory view? If NameInput is a subview of ContactInfoView, then setting ContactInfoView.NameInput.inputAccessoryView will not work.
I would like to make a UI that have label, table view and one button click. When click on the button, we pop up a half screen view that have lots of buttons. I want user can still click on the rest of the screen also.
So i use the approach that suggest in the post
How To Present Half Screen Modal View?
Method 2: to animate a UIView which is of size half of the existing view.
Then you have to simply follow animation of the UIView.
Here as it is just a UIView that will be added as subview to existing view, you will be able to touch the rest of the screen.
As i am newbie to the ios and swift, I would like to get some suggestions.
Now i am successfully add as subview and show in the half of the screen.
How can i implement to let subview click button result show on parent view label text?
I am thinking about parent.xib and subview.xib have the same UIVeiwController.swift. Then i can #IBOutlet and #IBAction to the same controller swift file and update the result. But don't know it is the accpetable way to do?
If not, how can the subViewController send result/event to the parent view and update in the parent view component?
You could use delegation. This keeps your view controllers decoupled, i.e. prevents the child from having a reference to its parent, which allows other view controllers to interact with the modal view controller in the same way.
class ParentViewController : UIViewController, ModalViewControllerDelegate {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
modalViewContorller.delegate = self
self.presentViewController( modalViewContorller, animated: true, completion: nil )
}
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String ) {
self.label.text = result
}
}
protocol ModalViewControllerDelegate {
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String )
}
class ModalViewController: UIViewController {
var delegate: ModalViewControllerDelegate?
#IBAction func buttonClicked( sender: AnyObject? ) {
delegate?.modalViewControllerDidProduceResult( self, result: "Hello!" )
}
}
You could also use a closure, which in Swift provides a more concise syntax.
class ParentViewController : UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
self.presentViewController( modalViewContorller, animated: true, completion: nil )
modalViewContorller.resultBlock = { (result: String) in
self.label.text = result
}
}
}
class ModalViewController: UIViewController {
var resultBlock: ((String) -> ())?
#IBAction func buttonClicked( sender: AnyObject? ) {
self.resultBlock?( "Hello!" )
}
}