create scrollview programmatically in swift without outlet - swift

I have tried enough but i don't understand what is wrong with code, it was working fine when I created an outlet, but then I found out that i don't need an outlet, so want to create it just programmatically.
var BestPractices = UIScrollView(frame: CGRectMake(0, 94, 768, 924))
BestPractices.hidden = true
I cannot access the properties of "BestPractices" in viewController , while same code works fine in playground

This is an answer from this question.
.
class ScrollingViewController : UIViewController {
// Create a scrollView property that we'll set as our view in -loadView
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
override func loadView() {
// calling self.view later on will return a UIView!, but we can simply call
// self.scrollView to adjust properties of the scroll view:
self.view = self.scrollView
// setup the scroll view
self.scrollView.contentSize = CGSize(width:1234, height: 5678)
// etc...
}
func example() {
let sampleSubView = UIView()
self.view.addSubview(sampleSubView) // adds to the scroll view
// cannot do this:
// self.view.contentOffset = CGPoint(x: 10, y: 20)
// so instead we do this:
self.scrollView.contentOffset = CGPoint(x: 10, y: 20)
}

add this line to identify scroll bar at last of loadview().
scrollView.backgroundColor = UIColor.blue

Related

Identifying Objects in Firebase PreBuilt UI in Swift

FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.

How can you make a .xib file fill the whole screen width (Swift)

I want a UIScrollView which contains to other views. One is a UITableView and the other is a MKMapView. To do this I created two xib files. When I want to add these two to my scroll view I do it like this:
func setupScrollView() {
scrollView.isPagingEnabled = true
scrollView.contentSize = CGSize(width: self.view.bounds.width * 2, height: self.view.bounds.height)
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
}
func loadScrollViewViews() {
if let tableView = Bundle.main.loadNibNamed("ViewTableShow", owner: self, options: nil)?.first as? ViewTableShow {
scrollView.addSubview(tableView)
tableView.frame.size.width = self.view.bounds.size.width
tableView.frame.origin.x = 0
}
if let mapView = Bundle.main.loadNibNamed("ViewMapShow", owner: self, options: nil)?.first as? ViewMapShow {
scrollView.addSubview(mapView)
mapView.frame.size.width = self.view.bounds.size.width
mapView.frame.origin.x = self.view.bounds.width
}
}
First I call setupScrollView and then loadScrollViewViews in the viewDidLoad function in my ViewController class. self.view.bounds.size.width gives me the correct size of the screen width but it doesn't seems to set the width for the two views properly.
Here is what it looks like right know. I want to cover the whole screen width. Does anyone know how to do it?
Try adding some constraints to your scrollView

Button anchored to InputAccessoryView working only in its frame

Setup
I implemented an inputAcessoryView in a UIViewController via the following:
override var inputAccessoryView: UIView? {
get {
return bottomBarView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
fileprivate lazy var bottomBarView: UIView = {
let view = UIView()
let separatorLineView = UIView()
view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height / 9)
view.backgroundColor = .white
/...
separatorLineView.backgroundColor = UIColor(r: 220, g: 220, b: 220, a: 1)
separatorLineView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(separatorLineView)
separatorLineView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
separatorLineView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
separatorLineView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
separatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
view.addSubview(self.photoButton)
self.photoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
self.photoButton.centerYAnchor.constraint(equalTo: separatorLineView.centerYAnchor, constant: -12).isActive = true
self.photoButton.widthAnchor.constraint(equalToConstant: view.frame.width / 4.5).isActive = true
self.photoButton.heightAnchor.constraint(equalToConstant: view.frame.width / 4.5).isActive = true
return view
}()
photoButton implements an action on .touchUpInside.
The issue
The button is only clickable in the part included in inputAccessoryView's frame: if I click on it on the part that's outside, nothing happens.
Is there any quick way to make it work like so ?
I have tried moving the photoButton out of bottomBarView and adding it to the view instead however:
I need photoButton to be "anchored" to the inputAccessoryView, but it seems inputAccessoryView doesn't have anchor properties from within the view ? (I can only access their frame properties via bang unwrapping)
When I do so, photoButton is partly hidden behind inputAccessoryView and I can't bring it forward with view.bringSubview(ToFront:)
Thanks in advance.
The issue you have is that the inputAccessoryView (bottomBarView for you) has no height. All of the subviews you're adding are therefore outside of its bounds. If you were to set clipsToBounds = true, you would see that everything disappears.
The issue with this, as you're seeing, is that those subviews aren't getting touch events since they're outside the bounds of their superview. From Apple's docs:
If a touch location is outside of a view’s bounds, the hitTest:withEvent: method ignores that view and all of its subviews. As a result, when a view’s clipsToBounds property is NO, subviews outside of that view’s bounds are not returned even if they happen to contain the touch.
Understanding Event Handling, Responders, and the Responder Chain
The solution is to get the inputAccessoryView to be the correct height. self.view.frame.height / 9 is 0 in your example, since the frame has not been set by the time you're using it.
Instead, create a UIView subclass, with the following important parts:
class BottomBarView: UIView {
init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
let someView = UIView()
addSubview(someView)
someView.translatesAutoresizingMaskIntoConstraints = false
someView.topAnchor.constraint(equalTo: topAnchor).isActive = true
someView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
someView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
someView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
// In this case, the top, bottom, and height constraints will
// determine the height of the inputAccessoryView
someView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: 0)
}
}
Try adding a button. You'll see that now it gets a touch event, since it's within the bounds of it's superview now.
I just had this problem myself in my own work over the past couple days.
You need to implement your own hitTest UIResponder method for your inputAcessoryView. Something that might look like this:
override func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView? {
if self.photoButton.frame.contains(point) {
return self.photoButton
}
return super.hitTest(point, with: event)
}
More information can be seen in this related question.

UINavigationBar bar height is not changing

I have a custom UINavigationController which I Simply resize the height but I get no result:
actually my main code is this: ( this will get perfect height according tu device size )
class NavigationController1: UINavigationController {
let Theframe = UIScreen.main.bounds
override func awakeFromNib() {
self.navigationBar.frame = CGRect.init(x: self.navigationBar.frame.origin.x*Theframe.width/375, y: self.navigationBar.frame.origin.y*Theframe.height/667, width:self.navigationBar.frame.width*Theframe.width/375, height: self.navigationBar.frame.height*Theframe.height/667)
}
}
but the upper code for height isn't working neither.
I solved the issue by putting in viewWillLayoutSubviews()
final code:
import UIKit
class NavigationController1: UINavigationController {
let Theframe = UIScreen.main.bounds
override func viewWillLayoutSubviews() {
self.navigationBar.frame = CGRect.init(x: self.navigationBar.frame.origin.x, y: self.navigationBar.frame.origin.y, width:self.navigationBar.frame.width, height: 200)
}
}
It could be override and redrawn because of the sIze inspector. Put the same requirements there so you can ensure its not overriding the custom code.

Propagation of UITouch events

I have views arranged as shown in attached figure.
MAINVIEW is an added subview to UISCROLLVIEW
class NotewallController:UIViewController, UIScrollViewDelegate {
var bgImage:UIImageView?
var bgScrollView:UIScrollView?
var masterView:UIView?
override func viewDidLoad() {
self.bgScrollView = UIScrollView(frame: self.view.bounds)
self.bgScrollView!.contentSize = CGSizeMake(1000, 1000)
self.bgScrollView!.delegate = self
self.bgScrollView!.minimumZoomScale = 1.0
self.bgScrollView!.maximumZoomScale = 4.0
self.view.addSubview(self.bgScrollView!)
masterView = UIView(frame: CGRectMake(0,0,1000,1000))
masterView!.backgroundColor = UIColor.clearColor()
masterView!.userInteractionEnabled = true
bgScrollView!.addSubview(masterView!)
let note = WALLVIEW(frame: CGRectMake(20, 20, 30, 30))
self.masterView!.addSubview(note)
}
WALLVIEW is a subclass of UIVIEW
class WallView:UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let bgImageView = UIImageView(frame: frame)
bgImageView.image = UIImage(named: "stickynote.png")
bgImageView.userInteractionEnabled = true
self.addSubview(bgImageView)
let tap = UITapGestureRecognizer(target: self, action: "WallNoteTapped:")
bgImageView.addGestureRecognizer(tap)
}
My issue is in WALLVIEW. As you can notice, I have a TAP GESTURE in WALLVIEW. When the program is executed, a tap of WALLVIEW object doesn't call the "WallNoteTapped:" method. Suspecting the touch events are not passed to WALLVIEW object "note".
I am not able to understand why "WallNoteTapped:" method is not triggered.
Thanks
EDIT:
I made WALLVIEW subclass of UIImageView rather than UIView and it worked finally.
Adding an UITapGestureRecognizer cancel te touches in its subviews.
Try to set cancelsTouchesInView to false.