I am building an application where I want to display a floor plan where the image is 2952x3600, larger than the size of my view controller. I want the user to be able to pan the image and zoom in and out, similar to how a map behaves in Mapview.
I was able to obtain the desired result programmatically with the below code. However, I want to be able to add buttons to the image and it will be easier to do in the storyboard. How can I accomplish the same result in the Storyboard?
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(image: UIImage(named: "TorontoPATHNetworkMap_v.2018.08.pdf"))
scrollView = UIScrollView(frame: view.bounds)
scrollView.contentSize = imageView.bounds.size
scrollView.addSubview(imageView)
scrollView.delegate = self
scrollView.minimumZoomScale = 0.3
scrollView.maximumZoomScale = 5
view.addSubview(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
If you want to add other UI elements (buttons, labels, subviews, etc) and have them scale and move when the scroll view content is zoomed, you have a couple options:
1) Add your buttons in code as subviews of the image view. They will move / size along with the image view.
2) If you'd find it easier to layout your buttons visually, you can do your layout in Storyboard / Interface Builder. In IB, though, you cannot add subviews to a UIImageView, so you will need to embed the image view and buttons in a "container" UIView. Then in your controller class return that container view from viewForZooming
Edit
Here's an example of setting up the views in Storyboard / IB: https://github.com/DonMag/PDFImagePanZoom
Related
I can't believe something this easy when I did not use auto-layout is this hard with auto-layout. I put everything in a contentView so that it's a simultaneous zoom for both views (ImageView and UIView). The UIView for drawing should be the same size as the UIImageView and not bigger. I have this hierarchy at the moment.
UIScrollView
- ContentView
-- UIView (For drawing eg, drawView)
-- UIImage (For showing a background image to draw on)
The drawView is a view on top of the imageView, the problem now is as following:
Users can draw out of bounds of the UIImageView. This should only be possible drawing on the imageView.
Drawings are under the UIImageView, while the drawView is on top.
Code:
var afbeelding: UIImage?
#IBOutlet var imageView: UIImageView!
#IBOutlet var drawView: DrawingCanvas!
#IBOutlet var contentView: UIView!
var dag: Dag?
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
drawView.dag = dag
if let afbeelding = afbeelding {
imageView.image = afbeelding
}
scrollView.panGestureRecognizer.minimumNumberOfTouches = 2
scrollView.panGestureRecognizer.maximumNumberOfTouches = 2
scrollView.minimumZoomScale = 0.2;
scrollView.zoomScale = 1.0;
scrollView.maximumZoomScale = 5.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return contentView
}
Images:
Could anyone steer me in the right direction?
I'm not very familiar with Interface Builder but for your layer order problem I'd try swapping them; perhaps the order in which they're shown is the order in which they're added.
As for drawing outside the bounds, you'd need to look at the documentation for DrawingCanvas but perhaps its clipsToBounds property will work?
Users can draw out of bounds of the UIImageView. This should only be possible drawing on the imageView.
It seems like your image view has its contentMode set to .scaleAspectFit, but has all 4 sides pinned to the superview. This means that the image view spans the entire contentView, but the image itself is smaller.
I think the easiest way around this is to get the frame of the actual image using AVMakeRect(aspectRatio:insideRect:), then adjusting drawView's frame.
let rect = AVMakeRect(aspectRatio: image.size, insideRect: imageView.frame)
/**
Instead of constraining `drawView`'s edges, use x, y, width, and height.
This makes setting them easier.
You'll need to connect your constraints via an #IBOutlet (see https://stackoverflow.com/a/40584432/14351818)
*/
drawViewLeftConstraint.constant = rect.origin.x
drawViewTopConstraint.constant = rect.origin.y
drawViewWidthConstraint.constant = rect.width
drawViewHeightConstraint.constant = rect.height
Drawings are under the UIImageView, while the drawView is on top.
Actually, the image view is on top. The order is lowest -> highest, so just switch them.
I would like to have a horizontal scroll layout which displays images. It works fine if setup 0, 0, 0 and 0 the constraints of the UIScrollView. The problem is exactly when I change the constraints to make margins surrounded the UIScrollView. This is what happens:
First image in the UIScrollView
Second image in the UIScrollView
Third image in the UIScrollView
As you can see, each time you scroll, more off-center the current page is.
I have tried to subtract trailing and leading constrains constants to the width of the scrollLayout, play with frames and bouds but without success.
If I run this example in a smaller display like iphone 5S, the problem is more pointed.
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var pageController: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
let imagesArray = ["b_1", "b_2", "b_3", "b_4", "b_5"]
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.isPagingEnabled = true
self.pageController.numberOfPages = imagesArray.count
self.pageController.pageIndicatorTintColor = UIColor.blue
self.pageController.currentPageIndicatorTintColor = UIColor.gray
for i in 0...imagesArray.count - 1{
let imageView = UIImageView()
imageView.contentMode = .scaleToFill
imageView.image = UIImage(named: self.imagesArray[i])
let xPos = CGFloat(i)*self.view.bounds.size.width
imageView.frame = CGRect(x: xPos, y: 0, width: view.frame.size.width, height: self.scrollView.frame.size.height )
self.scrollView.contentSize.width = view.frame.size.width*CGFloat(i+1)
self.scrollView.addSubview(imageView)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let page = scrollView.contentOffset.x/scrollView.frame.width
self.pageController.currentPage = Int(page)
}
}
So, I would like to know how to always obtain the current image centered.
Thank you
EDITED with Rajesh results and view debug:
I would recommend using a UICollectionView in place of a UIScrollView - otherwise you will be building a lot of the basics from scratch. You can use a collection view that centers the images, make sure paging is enabled and you should get the interface you're looking for. Make sure to adopt / conform to the UICollectionViewDelegate & UICollectionViewDelegateFlowLayout protocols with your view controller & set those delegates on your collection view. Hope that helps! Best of luck.
I have a Scroll View set to a fixed height inside my View Controller. I want to use a navigation bar on top with large titles, so when i scroll the Scroll View, it should collapse like in a Navigation Controller. Is it possible to do this? My scene look like this:
The navigation bar has top/left/right 0 constraints agains the View. Currently it stays on top correctly, however it won't collapse on scroll as expected.
Do not use a "loose" navigation bar like this. Use a navigation controller, even if you do not intend to do any navigation. It gives you the desired behavior, for free.
In the end i created a custom view to replicate the Navigation Bar. Here you can see how it looks and read the steps below to replicate:
To setup your View Controller to be used with a custom Scroll View, first make sure you are using Freeform size for your controller. To do this, select Freeform in the size inspector and set the height to your new Scroll View's height:
Insert your Scroll View and setup 0 top/left/right/bottom constraints, so it will be the same size as your View Controller:
Add your content to your scroll view as usual
Now to create your custom Navigation Bar, add a View outside of your Scroll View and setup constraints like this:
Notice a few things:
the top constraint is aligned to the Superview instead of the Safe Area, so the view goes behind the status bar
The height is set to >= 44, so its a minimum height and can expand if the content is larger
On the Attribute Inspector, select clip to bounds, so your content inside the view won't overflow(like in CSS, overflow:hidden)
At this point you might see some errors in your Storyboard, but don't worry about it: its because you don't have any content in your View and it doesn't know how tall it should be
Set the View background to transparent and add a "Visual Effect View with Blur" inside, with 0 top/left/right/bottom constraints. This will blur the content behind the custom navigation bar
Now make sure that you check the Safe Area Layout Guide checkbox in your navigation bar view(its above the constraints setup):
This way you can add content inside the view that won't be behind the status bar, because its outside of the safe area. And it works with the notch too.
Add a label inside your view, set top and bottom constraints to Safe Area and make sure you have a fixed height constraint defined too:
Now you can also see that the errors in your Storyboard are gone :) At this point this is how everything should look like:
Now the coding part. In your ViewController, make outlets for both the ScrollView and the custom navigation bar. To do this, switch to the assistant editor(the venn-diagram symbol top right), select the element in your storyboard, hold down CTRL and drag inside your ViewController class:
Do the same for your View that is your navigation bar:
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var customNavigationBar: UIView!
Next, you need to add the UIScrollViewDelegate to your class, so you can listen to the scroll event and get the actual Y position of the current scroll offset using the scrollViewDidScroll function:
class ViewController: UIViewController, UIScrollViewDelegate {
You also need to setup the delegate in your viewDidLoad hook:
mainScrollView.delegate = self
Create a new function called scrollViewDidScroll to get the scroll position and you can use this to do various animations with other elements. In this case, if the scroll position reaches 44(this is the height i set for my custom navigation bar), it will animate to full opacity:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = self.mainScrollView.contentOffset.y
let barHeight = 44
if(y < barHeight) {
customNavigationBar.alpha = y/CGFloat(barHeight)
} else {
customNavigationBar.alpha = 1
}
}
You can use the same logic to animate the label inside the navigation bar, change its size etc...
The full ViewController:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var customNavigationBar: UIView!
override func viewDidLoad() {
super.viewDidLoad()
mainScrollView.delegate = self
customNavigationBar.alpha = 0
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = self.mainScrollView.contentOffset.y
let barHeight = 44
if(y < 44) {
customNavigationBar.alpha = y/CGFloat(barHeight)
} else {
customNavigationBar.alpha = 1
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var height = CGFloat()
if(scrollView.panGestureRecognizer.translation(in: scrollView.superview).y > 0) {
height = 130
}
else {
height = 44
}
UIView.animate(withDuration: 0.5) {
self.navBarHeightConstraint?.constant = height
self.view.layoutIfNeeded()
}
}
I have a view and within an image that works as a button. I would like to know if there is a way to lock the size of the button so that when I zoom in, the view remains small and does not enlarge with the view..
I thought it was hard to manager in the beginning. But finally, if you put the imageView in a UIScrollView, It's not hard to achieve.
The idea is move the buttonView outside of imageView during zooming and when zoom is over, put it back to imageView to pretend nothing happened. I know it's too verbose in programming but actually it works perfectly for your case.
var originalCenter : CGPoint! // The center of ButtonView in imageView.
//All functions are from the UIScrollViewDelegate.
func viewForZooming(in scrollView: UIScrollView) -> UIView?{
originalCenter = buttonView.center // remember the original position.
return imageView
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
buttonView.frame = imageView.convert(buttonView.frame, to: scrollView)
scrollView.addSubview(buttonView)//add to superView of imageImage.
}
func scrollViewDidZoom(_ scrollView: UIScrollView){
buttonView.center = imageView.convert(originalCenter, to: scrollView) //During Zooming, update the buttonView in ScrollView.
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat){
buttonView.frame = imageView.convert(buttonView.frame, from: scrollView)
imageView.addSubview(buttonView) //put it back.
}
I know it's better to use a parameter to control such operation. But I have-not found one according to public APIs. Maybe there is a better way, hope this one is your answer too.
Because you called transform for superView. It will make all subViews inside transform together.
You need to remake you views:
SuperView:
- Content view (the image view)
- Border view
- Button close
When you want to zoom the image, you only need to reset the superview frame.
You found a git SPUserResizableView.
I'm using a UITableViewController with static cells and I want to make it so that the cells do not take up the entirety of the view. I want something more akin to this image: https://i.stack.imgur.com/4nO09.png
I can't quite figure out how to do so. I've been able to change the height thanks to self.tableView.contentInset, but I'm not sure how to change the width.
How would I do this?
EDIT:
Here's my Code for Fay007 as well as an image.
import UIKit
class ContactFormViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Sets the background images
if let patternImage = UIImage(named: "Pattern") {
view.backgroundColor = UIColor(patternImage: patternImage)
}
}
override func viewWillAppear(_ animated: Bool) {
let numberOfRows: CGFloat = CGFloat(self.tableView.numberOfRows(inSection: 0))
let headerHeight: CGFloat = (self.view.frame.size.height - (self.tableView.rowHeight * numberOfRows)) / numberOfRows
self.tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, -headerHeight, 0)
}
http://imgur.com/a/Q2kfH
The first image I linked has its cells away from the left/right edges, as in my comment I explained I believe they did using autolayout. Since the tableview is a subview of the UIView of the UIViewController, I believe one would be able to assure that. however, when using a UITableViewController, which is required to use static cells in a UITableView, there is no UIView parent.
One easy way of doing this is as follows: In storyboard, or interface builder, add a UIView subview to the UITableviewcell. Create constraints that define your desired distance of this subview to the edges of the cell.
To add rounded corners, you can do so within awakeFromNib by setting the cornerRadius of the subview's layer property to your desired radius.