I have a following scrollview code
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
var imageArray = [UIImage(named: "mainScrollView1"), UIImage(named: "mainScrollView2"), UIImage(named: "mainScrollView3"),]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
configurePageControl()
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
for var i = 0; i < imageArray.count; i++ {
if i == 0 {
var imageView = UIImageView(frame: CGRectMake(0, 0, scrollView.frame.size.width, 160))
imageView.image = imageArray[i]
scrollView.addSubview(imageView)
} else {
var float: CGFloat = CGFloat(i)
var imageView = UIImageView(frame: CGRectMake(scrollView.frame.size.width * float, 0, scrollView.frame.size.width, 160))
imageView.image = imageArray[i]
scrollView.addSubview(imageView)
}
}
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*3, scrollView.frame.size.height)
}
func configurePageControl() {
self.pageControl.numberOfPages = 3
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.redColor()
self.pageControl.pageIndicatorTintColor = UIColor.blackColor()
self.pageControl.currentPageIndicatorTintColor = UIColor.whiteColor()
self.view.addSubview(pageControl)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
}
the problem is that images inside scroll a static ones, they do not change their size for different screen sizes and because of that it looks like this
as you see first image do not expands to fill whole screen width and because of this part of second image can be seen
How can this be fixed ?
You should update the image size after the view layout all subviews. You should do that in viewDidLayoutSubviews.
Something like this:
var imageArray = [UIImage(named: "mainScrollView1"), UIImage(named: "mainScrollView2"), UIImage(named: "mainScrollView3"),]
var imageViews:[UIImageView] = []
override func viewDidLoad() {
super.viewDidLoad()
configurePageControl()
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
for image in imageArray {
var imageView = UIImageView(image: image)
scrollView.addSubview(imageView)
imageViews.append(imageView)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for (index,imageView) in imageViews.enumerate() {
imageView.frame = CGRectMake(CGFloat(index)*scrollView.frame.size.width, 0, scrollView.frame.size.width, 160)
}
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*3, scrollView.frame.size.height)
}
Related
UIKit/Programmatic UI
I have an UIScrollView with an UIImageView inside. The image is set by user selection and can have all kinds of sizes. What I want is that the image initially fills the screen (view) and then can be zoomed and scrolled all the way to the edges of the image.
If I add the ImageView directly to the view (no scrollView), I get it to fill the screen with the following code:
mapImageView.image = ProjectImages.projectDefaultImage
mapImageView.translatesAutoresizingMaskIntoConstraints = false
mapImageView.contentMode = .scaleAspectFill
view.addSubview(mapImageView)
Now the same with the scrollView and the embedded imageView:
view.insertSubview(mapImageScrollView, at: 0)
mapImageScrollView.delegate = self
mapImageScrollView.translatesAutoresizingMaskIntoConstraints = false
mapImageScrollView.contentMode = .scaleAspectFill
mapImageScrollView.maximumZoomScale = 4.0
mapImageScrollView.pinToEdges(of: view, safeArea: true)
mapImageView.image = ProjectImages.projectDefaultImage
mapImageView.translatesAutoresizingMaskIntoConstraints = false
mapImageView.contentMode = .scaleAspectFill
mapImageScrollView.addSubview(mapImageView)
And now, if the image's height is smaller than the view's height, the image does not fill the screen and I'm left with a blank view area below the image. I can zoom and scroll ok, and then the image does fill the view.
Adding contsraints will fill the view as I want, but interferes with the zooming and scrolling and prevents me getting to the edges of the image when zoomed in.
How to set this up correctly ?
You might find this useful...
It allows you to zoom an image in a scrollView, starting with it centered and maintaining aspect ratio.
Here's a complete implementation. It has two important variables at the top:
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFill
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
Everything is done via code - no #IBOutlet or other connections - so just create add a new view controller and assign its custom class to ZoomAspectViewController (and edit the name of the image you want to use):
class ZoomAspectViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
var imageViewBottomConstraint: NSLayoutConstraint!
var imageViewLeadingConstraint: NSLayoutConstraint!
var imageViewTopConstraint: NSLayoutConstraint!
var imageViewTrailingConstraint: NSLayoutConstraint!
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFit
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
guard let img = UIImage(named: "myImage") else {
fatalError("Could not load the image!!!")
}
scrollView = UIScrollView()
imageView = UIImageView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
scrollView.addSubview(imageView)
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: scrollView.topAnchor)
imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imageViewTopConstraint,
imageViewBottomConstraint,
imageViewLeadingConstraint,
imageViewTrailingConstraint,
])
scrollView.delegate = self
scrollView.minimumZoomScale = 0.1
scrollView.maximumZoomScale = 5.0
imageView.image = img
imageView.frame.size = img.size
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.updateMinZoomScaleForSize(size, shouldSize: (self.scrollView.zoomScale == self.scrollView.minimumZoomScale))
self.updateConstraintsForSize(size)
}, completion: {
_ in
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateMinZoomScaleForSize(scrollView.bounds.size)
updateConstraintsForSize(scrollView.bounds.size)
if fitMode == .scaleAspectFill {
centerImageView()
}
}
func updateMinZoomScaleForSize(_ size: CGSize, shouldSize: Bool = true) {
guard let img = imageView.image else {
return
}
var bShouldSize = shouldSize
let widthScale = size.width / img.size.width
let heightScale = size.height / img.size.height
var minScale = min(widthScale, heightScale)
let startScale = max(widthScale, heightScale)
if fitMode == .scaleAspectFill && !allowFullImage {
minScale = startScale
}
if scrollView.zoomScale < minScale {
bShouldSize = true
}
scrollView.minimumZoomScale = minScale
if bShouldSize {
scrollView.zoomScale = fitMode == .scaleAspectFill ? startScale : minScale
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
updateConstraintsForSize(scrollView.bounds.size)
}
func centerImageView() -> Void {
let yOffset = (scrollView.frame.size.height - imageView.frame.size.height) / 2
let xOffset = (scrollView.frame.size.width - imageView.frame.size.width) / 2
scrollView.contentOffset = CGPoint(x: -xOffset, y: -yOffset)
}
func updateConstraintsForSize(_ size: CGSize) {
let yOffset = max(0, (size.height - imageView.frame.height) / 2)
imageViewTopConstraint.constant = yOffset
imageViewBottomConstraint.constant = yOffset
let xOffset = max(0, (size.width - imageView.frame.width) / 2)
imageViewLeadingConstraint.constant = xOffset
imageViewTrailingConstraint.constant = xOffset
view.layoutIfNeeded()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Edit
As an example, I used this image (2560 x 1440):
and I get this result on launch:
and maximum zoom in (5.0) scrolled to top-center:
Edit 2
Same image, at launch, with:
var fitMode: UIView.ContentMode = .scaleAspectFill
instead of .scaleAspectFit:
I've found this solution that works for me, when setting and changing the image, I calculate the minimum needed zoom scale and set it on the scrollView:
var selectedMapImage: MapImage? {
didSet {
mapImageView.image = mapImagesController.getImageForMapImage(selectedMapImage!)
mapImageScrollView.minimumZoomScale = view.bounds.height / mapImageView.image!.size.height
mapImageScrollView.setZoomScale(mapImageScrollView.minimumZoomScale, animated: true)
mapImageScrollView.scrollRectToVisible(view.bounds, animated: true)
}
}
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
var images: [String] = ["0","1","2"]
var frame = CGRect(x:0,y:0,width:0,height:0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
pageControl.numberOfPages = images.count
for idex in 0..<images.count {
frame.origin.x = scrollView.frame.size.width
* CGFloat(index)
// the code above me shows a error
frame.size = scrollView.frame.size
let imgView = UIImageView(frame: frame)
imgView.image = UIImage(named: images[index])
this code above as well
self.scrollView.addSubview(imgView)
}
scrollView.contentSize = CGSize(width:(scrollView.frame.size.width * CGFloat(images.count)), height: scrollView.frame.size.height)
scrollView.delegate = self
}
Have you tried replacing the actual loop by this loop :
for index in 0..<images.count {
...
Trying to connect my pagecounter to my scroll view, but for some reason it does not change when the image is scrolled.
Wrote code calculating the image size change in the scroll view.
#IBOutlet weak var myScrollView: UIScrollView!
#IBOutlet weak var pageCounter: UIPageControl!
var page = 0
var myImages = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var posX: CGFloat = 0
let scrollWidth = myScrollView.frame.size.width
let scrollHeight = myScrollView.frame.size.height
myScrollView.isPagingEnabled = true
myImages = [#imageLiteral(resourceName: "61763233_2273795276270684_2413481963462590464_o"), #imageLiteral(resourceName: "64431295_2284501085200103_3314220009403711488_n"), #imageLiteral(resourceName: "hrc1"), #imageLiteral(resourceName: "66263822_2295361957447349_1103112725925462016_n"), #imageLiteral(resourceName: "62022794_2274568069526738_785458383527346176_o")]
view.setNeedsLayout()
view.layoutIfNeeded()
pageCounter.numberOfPages = myImages.count
pageCounter.pageIndicatorTintColor = UIColor.gray
pageCounter.currentPageIndicatorTintColor = UIColor.blue
for img in myImages {
let imgView = UIImageView(frame: CGRect(x: posX, y: 0, width: scrollWidth, height: scrollHeight))
imgView.image = img
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
myScrollView.addSubview(imgView)
myScrollView.contentSize = CGSize(width: scrollWidth * CGFloat(myImages.count), height: scrollHeight)
posX = posX + scrollWidth
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = myScrollView.frame.size.width
let getpage = round(myScrollView.contentOffset.x / pageWidth)
let currentPage = Int(getpage)
page = currentPage
pageCounter.currentPage = page
}
The page counter should change after every scroll.
set your PageCounter Inside your ScrollView
example:
where pageCounter is your object and scrollView is your scrollView
NSLayoutConstraint.activate([
pageCounter.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 15),
pageCounter.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 30),
pageCounter.heightAnchor.constraint(equalToConstant: 40),
pageCounter.widthAnchor.constraint(equalToConstant: 40)
])
"myScrollview.delegate = self"
this line of code was missing. Making it impossible to connect with the page control
I made a program from one youtube channel and ran into a problem. I think that it is related to the layout. On different devices displayed differently. And can someone tell me how to fix what text will fit onto another one and how to make the image appear on the whole my CustomView.
import UIKit struct scrollViewDataStruct {
let title: String?
let image: UIImage?
} class ScrollController: UIViewController, UIScrollViewDelegate{
#IBOutlet weak var scrollView: UIScrollView!
var scrollViewData = [scrollViewDataStruct]()
var viewTagValue = 10
var tagValue = 100
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollViewData = [scrollViewDataStruct.init(title: "There was written a very large line that climbs to another line", image: #imageLiteral(resourceName: "knowledge_graph_logo")),
scrollViewDataStruct.init(title: "Second", image: #imageLiteral(resourceName: "knowledge_graph_logo"))]
scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(scrollViewData.count)
var i = 0
for data in scrollViewData {
let view = CustomView(frame: CGRect(x: 10 + (self.scrollView.frame.width * CGFloat(i)), y: 200, width: self.scrollView.frame.width - 75, height: self.scrollView.frame.height - 90))
view.imageView.image = data.image
view.tag = i + viewTagValue
self.scrollView.addSubview(view)
let label = UILabel(frame: CGRect.init(origin: CGPoint.init(x: 0, y: 20), size: CGSize.init(width: 0, height: 40)))
label.text = data.title
label.font = UIFont.boldSystemFont(ofSize: 30)
label.textColor = UIColor.black
label.sizeToFit()
label.tag = i + tagValue
if i == 0 {
label.center.x = view.center.x
} else {
label.center.x = view.center.x - self.scrollView.frame.width / 2
}
self.scrollView.addSubview(label)
i += 1
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == scrollView {
for i in 0..<scrollViewData.count {
let label = scrollView.viewWithTag(i + tagValue) as! UILabel
let view = scrollView.viewWithTag(i + viewTagValue) as! CustomView
var scrollContentOffset = scrollView.contentOffset.x + self.scrollView.frame.width
var viewOffset = (view.center.x - scrollView.bounds.width / 4) - scrollContentOffset
label.center.x = scrollContentOffset - ((scrollView.bounds.width / 4 - viewOffset) / 2)
}
}
}}class CustomView: UIView {
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = UIColor.darkGray
imageView.contentMode = .scaleAspectFit
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}
This is a launch on iPhone 5s
This is a launch on iPhone 8 plus
What you should be doing is setting the width of each UILabel to the size of the CustomView that contains the image, and setting each label's "numberOfLines" to 0 and set the lineBreak to wordWrap. This should, in theory, let the labels only be the width of the images AND let the UILabels fit the size of the text vertically, rather than horizontally - which is what sizeToFit does.
I am a beginner at programming. I have an app where you can tap into any of the images that are on display to show that image in full-screen mode and can pinch to zoom. The issue I am having is that if you rotate the phone then the image is only half visible.
I'm using a scroll view to achieve the zoom functionality as that seems to be the consensus of the best way to do it.
It works perfectly in portrait mode, or if I enter the fullscreen image while the app is already in landscape orientation, but If I go into the landscape while in the fullscreen image that's where it goes wrong. Here is the code:
class PictureDetailViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var routeData = IndividualRoute(numberOfRoute: UserDefaults.standard.integer(forKey: "currentRoute"))
var detailPicture = UserDefaults.standard.bool(forKey: "segueFromDetailvc")
var detailImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
routeData.routesValues()
scrollView.delegate = self
selectImage()
scrollView.frame = UIScreen.main.bounds
scrollView.addSubview(detailImage)
scrollViewContents()
setupConstraints()
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHieght = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleHieght, scaleWidth)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 1
scrollView.zoomScale = minScale
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreen))
tap.numberOfTapsRequired = 2
view.addGestureRecognizer(tap)
}
//****************************************
//Image Setup
func setupConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
scrollView.centerYAnchor.constraint(equalTo: view.centerYAnchor)])
scrollView.contentSize = (detailImage.image?.size)!
}
func selectImage() {
if !detailPicture {
let foo = "\(routeData.achievements[UserDefaults.standard.integer(forKey: "currentAchievement")])"
detailImage.image = UIImage(named: "\(foo.folding(options: .diacriticInsensitive, locale: nil)) 0")
} else {
let foo = "\(routeData.achievements[0])"
detailImage.image = UIImage(named: "\(foo.folding(options: .diacriticInsensitive, locale: nil)) 0")
print("\(foo.folding(options: .diacriticInsensitive, locale: nil)) 0")
}
guard let width = detailImage.image?.size.width else {
return
}
guard let height = detailImage.image?.size.height else {
return
}
let frame: CGRect = CGRect(x: 0, y: 0, width: width, height: height)
detailImage.frame = frame
detailImage.isUserInteractionEnabled = true
}
//**************************************
//Scrollview setup
#objc func dismissFullscreen(){
scrollView.setZoomScale(1, animated: true)
}
func scrollViewContents() {
let boundSize = UIScreen.main.bounds.size
var contentFrame = detailImage.frame
if contentFrame.size.width < boundSize.width {
contentFrame.origin.x = (boundSize.width - contentFrame.size.width) / 2
} else {
contentFrame.origin.x = 0
}
if contentFrame.size.height < boundSize.height {
contentFrame.origin.y = (boundSize.height - contentFrame.size.height) / 2
} else {
contentFrame.origin.y = 0
}
detailImage.frame = contentFrame
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
scrollViewContents()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return detailImage
}
Sorry for posting so much code but its pretty much all relevant (I think).
Here are screenshots of the problem:
Okay, I've managed to fix it! Had a little jump around with delight.
I set up a new function called setupScale() that is called in viewdidload when the view is presented. I also added the viewwillLayoutSubview() override and called the setupScale() function inside it.
If looks like this:
private func setupScale() {
scrollView.frame = UIScreen.main.bounds
scrollView.contentSize = (detailImage.image?.size)!
scrollViewContents()
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHieght = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleHieght, scaleWidth)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 1
scrollView.zoomScale = minScale
}
override func viewWillLayoutSubviews() {
setupScale()
}
Results look perfect on my iPad and iPhone 7 in landscape and portrait.