how to fix view under navigation bar swift - swift

i make popup using EzPopup library, i get problem when i put popup under navigation bar.
this my code
#IBAction func showTopRightButton(_ sender: Any){
guard let pickerVC = pickerVC else { return }
pickerVC.delegate = self
let popupVC = PopupViewController(contentController: pickerVC, position: .topRight(CGPoint(x: 0, y: navigationController!.navigationBar.frame.height+20)), popupWidth: 100, popupHeight: 200)
popupVC.cornerRadius = 5
present(popupVC, animated: true, completion: nil)
}
i got a problem like my image
enter image description here
how to make same in iphone x and iphone x

In iPhone X portrait mode, the status bar is taller — 44 pts, not 20 pts
You need to add the statusBar Frame height also. Try below code:
//1.0 Get the Top bar height
let topBarHeight = UIApplication.shared.statusBarFrame.size.height + (self.navigationController?.navigationBar.frame.height ?? 0.0)
let popupVC = PopupViewController(contentController: pickerVC, position: .topRight(CGPoint(x: 0, y: topBarHeight)), popupWidth: 100, popupHeight: 200)
You can read more information on UILayout here.

Related

Zoom to location on scrollview on iPhone (portrait); not quite right

In my app the user can add images (a map) and then add pins (small images + attached note) to that image. The map is inside a scrollview. Adding the pins etc works fine, with one small exception: on Iphones in portrait mode (or more precise traitcollection == .compact) I can't get the pin to be and centered and a bit zoomed out. All of this is in a custom splitview controller; notes in the left viewcontroller, map (images + pin) in the right viewcontroller.
On iPad/iPhone in landscape this works just fine by calling:
mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
mapImageScrollView.setZoomScale(1.0, animated: true)
with the following result
On the iPhone in portrait mode the user has to click the double arrow button to navigate to the right viewcontroller with the map. using the same to lines of code as above will result in the map being zoomed in, but NOT centered on the pin.
The best I could get it is by using the following code (note that for this to work I have to set the Zoomscale before I zoom!)
mapImageScrollView.setZoomScale(0.85, animated: false)
mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
This results in centered, but NOT zoomed out
I think it is due to in the second case the right viewcontroller hasn't appeared yet (will do so only after the user taps the double arrow button), but I can't seem to find a better solution ...
Any help appreciated!
The code I use to add the pin to the map:
private func addMapImageLocationPin() {
if let location = annotation?.location {
//Activate the corresponding Map Image and update picker
selectedMapImage = location.map
mapImagePickerView.selectRow(mapImagesController.getRowFor(location.map), inComponent: 0, animated: true)
//Instantiate new Pin with annotation
mapImageLocationPin = MapImageLocationPin(with: annotation!, andMapImageSize: mapImageView.image!.size)
mapImageView.addSubview(mapImageLocationPin!)
//Desired height and width
let desiredWidth: CGFloat = 160
let desiredHeight: CGFloat = 80
//compensate for scrollView zoomscale, but never bigger than desired width & height!
let minZoomScale = mapImageScrollView.minimumZoomScale
let width = min(desiredWidth, desiredWidth / minZoomScale)
let height = min(desiredHeight, desiredHeight / minZoomScale)
//center the pin image horizontal on location
let y = location.coordinateY
let x = location.coordinateX - (width / 2)
//position (frame) the pin
mapImageLocationPin?.frame = CGRect(x: x, y: y, width: width, height: height)
//zoom to the pin, different approach for smaller screen sizes
if traitCollection.horizontalSizeClass == .compact {
mapImageScrollView.setZoomScale(0.85, animated: false)
mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
} else {
mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
mapImageScrollView.setZoomScale(1.0, animated: true)
}
}
}
At what point in the layout cycle is addMapImageLocationPin being called? If the child view isn't loaded immediately in some instances then you need to ensure that you call the positioning code after the main view has been laid out, such as in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
}

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 to capture image of entire view's contents when bigger than screen

I need my app to render everything that a view controller has the potential to display (including off-screen content) except for the top and bottom navigation bars.
The first image, below, shows the view controller at runtime. The action menu triggers the following code which is adapted the code sample from the answer given here :
#IBAction func actionMenu(_ sender: Any) {
let activityItems = [generateImageOfTableView(tblview: tourneyEntrants)]
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
activityController.popoverPresentationController?.sourceView = self.view
activityController.popoverPresentationController?.sourceRect = self.view.frame
self.present(activityController, animated: true, completion: nil)
}
func generateImageOfTableView(tblview: UITableView) -> UIImage {
var image = UIImage()
UIGraphicsBeginImageContextWithOptions(tblview.contentSize, false, UIScreen.main.scale)
// save initial values
let savedContentOffset = tblview.contentOffset;
let savedFrame = tblview.frame;
let savedBackgroundColor = tblview.backgroundColor
// reset offset to top left point
tblview.contentOffset = CGPoint(x: 0, y: 0);
// set frame to content size
tblview.frame = CGRect(x: 0, y: 0, width: tblview.contentSize.width, height: tblview.contentSize.height);
// remove background
tblview.backgroundColor = UIColor.clear
// make temp view with scroll view content size
// a workaround for issue when image on ipad was drawn incorrectly
let tempView = UIView(frame: CGRect(x: 0, y: 0, width: tblview.contentSize.width, height: tblview.contentSize.height))
// save superview
let tempSuperView = tblview.superview
// remove scrollView from old superview
tblview.removeFromSuperview()
// and add to tempView
tempView.addSubview(tblview)
// render view
// drawViewHierarchyInRect not working correctly
tempView.layer.render(in: UIGraphicsGetCurrentContext()!)
// and get image
image = UIGraphicsGetImageFromCurrentImageContext()!
// and return everything back
tempView.subviews[0].removeFromSuperview()
tempSuperView?.addSubview(tblview)
// restore saved settings
tblview.contentOffset = savedContentOffset;
tblview.frame = savedFrame;
tblview.backgroundColor = savedBackgroundColor
UIGraphicsEndImageContext()
return image
}
The second image, below, shows the image captured from this code.
There are two problems with it.
The first is that it is ignoring the text field and label above the table. I know that the code doesn't look for this, so I am looking for some guidance on how to capture the superview's contents (minus the navigation bars).
Second, the table view contains 18 columns of numbers but these aren't captured. So, the code copes with the height of the table being beyond the screen but not with the width. I've looked at whether auto layout maybe causing this, but cannot see anything obvious.

Star Rating Control in UIAlertController subview is clipped when centred - Swift

I am using Cosmos Star Rating Control and I am embedding it as a subview of a UIAlertController. I struggled to get this to look correctly, however I managed to get the look I was going for with it being centred and all, but now I believe the view for the Star Control is being clipped, as it does not allow selection of certain areas on certain stars, also when turning on clipToBounds = true you can blatantly see that it's being clipped. I am just not sure why or how to solve this. The code and some screenshots are below. I also tried using a custom alert controller that allows you to add a custom content view, but still suffered the same issue.
#IBAction func rateButtonClicked(sender: AnyObject?) {
//Alert for the rating
let alert = UIAlertController(title: "\n\n", message: "", preferredStyle: UIAlertControllerStyle.ActionSheet)
let customView = UIView(frame: CGRect(x: 0, y: 0, width: alert.view.frame.width, height: alert.view.frame.height))
//The x/y coordinate of the rating view
let xCoord = alert.view.frame.width/2 - 95 //(5 starts multiplied by 30 each, plus a 5 margin each / 2)
let yCoord = CGFloat(25.0)
ratingView.rating = 0.0
ratingView.settings.starSize = 30
ratingView.settings.emptyBorderColor = UIColor.blackColor()
ratingView.settings.updateOnTouch = true
ratingView.frame.origin.x = xCoord
ratingView.frame.origin.y = yCoord
customView.addSubview(ratingView)
alert.addAction(UIAlertAction(title: "Save Rating", style: UIAlertActionStyle.Default, handler: ratingCompletionHandler))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Destructive, handler: nil))
alert.view.addSubview(customView)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(alert, animated: true, completion: nil)
})
}
Image with clipToBounds Turned on:
Image with clipToBounds Turned off:
After some playing around, I noticed that the CosmosView was using intrinsic content size and only would be as big as the content it holds(the stars), which I think was causing the issue, so a bit of an ugly work around I used, that works for now, was just to assign it a new frame of a custom size like so:
//Make a custom frame
ratingView.frame = CGRectMake(0, 0, 200.0, 60.0)
ratingView.frame.origin.x = xCoord
ratingView.frame.origin.y = yCoord
This still has the stars centred as above, but now they are all clickable and provide the correct rating value when clicked.
Try this simple way that may help you:
let customView = UIView(frame: CGRect(x: 0, y: 0, width: alert.view.frame.width, height: alert.view.frame.height))
//add this line here
customView.sizeToFit()
//The x/y coordinate of the rating view
let xCoord = customView.view.frame.width/2 - 95 //(5 starts multiplied by 30 each, plus a 5 margin each / 2)
let yCoord = CGFloat(25.0)
If preferredStyle is .alert and not .actionSheet. Based on the fact that we cannot legitimately know the sizes before the alert controller (view) is shown, I'd opt to do this (in Swift 3):
DispatchQueue.main.async {
self.present(alert, animated: true, completion: {
if let sv = alert.view.subviews[0] {
let w = ratingView.intrinsicContentSize.width
let h = ratingView.intrinsicContentSize.height
ratingView.frame = CGRect(x: sv.bounds.width/2 - w/2, y: sv.bounds.height/2 - h/2, width: w, height: h)
sv.addSubview(ratingView)
}
})
}
Note: Might have to make adjustments for .actionSheet.

UIScrollView with "Circular" scrolling

I am trying to make "Circular" scrolling in my UIScrollView, but unsuccessful.
What I want to do:
if uiscrollview reaches end, it should move to start
if uiscrollview at start and moving back, it should move to end
Appending scrollview isn't good way in my situation (other methods should get "page id")
Have you any ideas?
I've implemented this method, but it requires paging enabled. Lets assume you have five elements A,B,C,D and E. When you set up your view, you add the last element to the beginning and the first element to the end, and adjust the content offset to view the first element, like this E,[A],B,C,D,E,A. In the UIScrollViewDelegate, check if the user reach any of the ends, and move the offset without animation to the other end.
Imagine the [ ] indicates the view being shown:
E,A,B,C,[D],E,A
User swipes right
E,A,B,C,D,[E],A
User swipes right
E,A,B,C,D,E,[A]
Then, automatically set the content offset to the second element
E,[A],B,C,D,E,A
This way the user can swipe both ways creating the illusion of an infinite scroll.
E,A,[B],C,D,E,A
Update
I've uploaded a complete implementation of this algorithm. It's a very complicated class, because it also has on-click selection, infinite circular scroll and cell reuse. You can use the code as is, modify it or extract the code that you need. The most interesting code is in the class TCHorizontalSelectorView.
Link to the file
Enjoy it!
Update 2
UICollectionView is now the recommended way to achieve this and it can be used to obtain the very same behavior. This tutorial describes in details how to achieve it.
Apple has a Street Scroller demo that appears to have exactly what you want.
There's also a video from WWDC 2011 that demos their demo. ;) They cover infinite scrolling first in the video.
Try to use following code..
Sample code..
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
if (scrollView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image n(last image).
// reposition offset to show image 10 that is on the right in the scroll view
[scrollView scrollRectToVisible:CGRectMake(4000,0,320,480) animated:NO];// you can define your `contensize` for scrollview
}
else if (scrollView.contentOffset.x == 4320) {
// user is scrolling to the right from image n(last image) to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[scrollView scrollRectToVisible:CGRectMake(320,0,320,480) animated:NO];
}
}
Hope, this will help you...
With reference from #redent84, find the code logic.
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
scrollView.contentSize = CGSize(width: scrollView.frame.width * CGFloat(imagesArray.count), height: scrollView.frame.height)
scrollView.isPagingEnabled = true
// imagesArray.insert(imagesArray.last as? UIImage, at: 0)
// imagesArray.insert(imagesArray.first as? UIImage, at: imagesArray.count - 1)
var testArr = ["A", "B", "C", "D"]
let firstItem = testArr.first
let lastItem = testArr.last
testArr.insert(lastItem as! String, at: 0)
testArr.append(firstItem as! String)
print(testArr)
for i in 0..<imagesArray.count{
let imageview = UIImageView()
imageview.image = imagesArray[i]
imageview.contentMode = .scaleAspectFit
imageview.clipsToBounds = true
let xPosition = self.scrollView.frame.width * CGFloat(i)
imageview.frame = CGRect(x: xPosition, y: 0, width: self.scrollView.frame.width, height: self.scrollView.frame.height)
print(imageview)
scrollView.addSubview(imageview)
print("Imageview frames of i \(i) is \(imageview.frame)")
}
newStartScrolling()
}
func newStartScrolling()
{
ViewController.i += 1
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: true)
print("X is \(x) and i is \(ViewController.i)")
if ViewController.i == imagesArray.count - 1 {
ViewController.i = 0
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
//scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: false)
print("X (rotate) is \(x) and i is \(ViewController.i)")
scrollView.contentOffset.x = x
self.newStartScrolling()
}
}
func backScrolling() {
ViewController.i -= 1
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: true)
print("X is \(x) and i is \(ViewController.i)")
if ViewController.i <= 0 {
ViewController.i = imagesArray.count - 1
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: false)
print("X (rotate) is \(x) and i is \(ViewController.i)")
self.backScrolling()
//scrollView.contentOffset.x = x
return
}
}