Swift UIScrollView - strange padding - ios

I need to make the flowers image flipping. Images must be with the same height, but the width to set automatically. I want them to scroll right and left
Here is my code:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
for i in 1...3 {
images.append(UIImage(named: "bild-0\(i).jpg")!)
}
var i: CGFloat = 0
var origin: CGFloat = 0
let height: CGFloat = scrollView.bounds.height
for image in images {
let imageView = UIImageView(frame: CGRectZero)
imageView.frame.size.height = height
imageView.image = image
imageView.sizeToFit()
imageView.frame.origin.x = origin
println(imageView.frame.size.width)
println(imageView.frame.origin.x)
println(imageView.frame.size.height)
println("asd")
origin = origin + imageView.frame.size.width
i++
scrollView.addSubview(imageView)
}
scrollView.contentSize.width = origin
scrollView.bounces = false
scrollView.pagingEnabled = false
}
}
Storyboard:
Problem (Padding from top! - Red color - is a background for UIScrollView):
Images are 765x510 300x510 and so on
UIScrollView height is 170

This is caused by scrolling insets:
Click your ViewController on Storyboard and go to file inspector, and you should see this dialog:
Untick the Adjust Scroll View Insets.

Related

Swift ScrollView Layout Issue With PageController and Images iOS

I can't figure out how to set constraints for a scrollView with an imageView inside.
I am using the scrollView with a pageConroller to swipe thru a bunch of images.
See my layout in the picture below.
// Code for imageView
for index in 0..<drinksImagesArray.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let imageView = UIImageView(frame: frame)
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: imagesArray[index].name)
self.scrollView.addSubview(imageView)
}
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(imagesArray.count), height: scrollView.frame.size.height)
scrollView.delegate = self
Any suggestions? Thank you!
Layout
You will have much better luck using auto-layout --- it can handle all of the frame sizes and .contentSize for you.
Here's a quick example - it uses a view controller with a scroll view added in Storyboard, so it should be pretty easy for you to integrate with your code:
class ScrollingImagesViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
var drinksImagesArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// however you're populating your array...
drinksImagesArray = [
"drink1",
"drink2",
"drink3",
// etc...
]
// create a horizontal stack view
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.spacing = 0
// add the stack view to the scroll view
stack.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stack)
// use scroll view's contentLayoutGuide for content constraints
let svCLG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// stack view constrained Top / Bottom / Leading / Trailing of scroll view CONTENT guide
stack.topAnchor.constraint(equalTo: svCLG.topAnchor),
stack.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor),
stack.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor),
// stack view height == scroll view FRAME height
stack.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor),
])
// create image views and add them to the stack view
drinksImagesArray.forEach { imgName in
let v = UIImageView()
v.backgroundColor = .lightGray
v.contentMode = .scaleAspectFit
// make sure we load a valid image
if let img = UIImage(named: imgName) {
v.image = img
}
stack.addArrangedSubview(v)
}
// stack distribution is set to .fillEqually, so we only need to set the
// width constraint on the first image view
// unwrap it
if let firstImageView = stack.arrangedSubviews.first {
firstImageView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
}
}
}
Edit
After reviewing your Storyboard...
Auto-layout doesn't seem to like it when you add a UINavigationBar and a UIToolbar and a UIScrollView as subviews. In particular, it appears to confuse the scroll view's frame related constraints.
The fix is to first add constraints for your scroll view:
Top to Navigation Bar Bottom
Bottom to Page Control Top
Leading and Trailing to safe-area
Storyboard / Interface builder will complain that the scroll view is not configured properly. You can either ignore that, or select the scroll view and set Ambiguity to Never Verify:
Then, in your view controller class, we need to create a height constraint for the stack view we're adding to the scroll view, and set that height constant in viewDidLayoutSubviews().
Here's the full code:
//
// WasserhaushaltViewController.swift
// deSynthTheOceans
//
// Created by robinsonhus0 on 24.03.20.
// Copyright © 2020 robinsonhus0. All rights reserved.
//
import UIKit
import AVFoundation
import Charts
import FSCalendar
import HealthKit
struct WasserSpeicher: Codable {
let wassermenge: Double
let speicherdatum: String
let speicherStelle: Double
}
class WasserhaushaltViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var diagrammView: UIView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
let drinksImagesArray = ["tapWater", "water", "milk", "cola", "coffee", "tea", "juice", "beer"]
var imageIndex = Int()
struct Drinks {
var name: String
var tagesMengeFactor: Double
var gesamtMengeFactor: Double
}
var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
var pageNumber = CGFloat()
#IBOutlet weak var todaysWaterConsumptionLabel: UILabel!
#IBOutlet weak var waterGoalProgress: UIProgressView!
#IBOutlet weak var waterGoalLabel: UILabel!
#IBOutlet weak var wasserMengeStepper: UIStepper!
#IBOutlet weak var motivationTextView: UITextView!
#IBOutlet weak var wasserglasButton: UIBarButtonItem!
#IBOutlet weak var kleineFlascheButton: UIBarButtonItem!
#IBOutlet weak var grosseFlascheButton: UIBarButtonItem!
#IBOutlet weak var overAllWaterConsumptionLabel: UILabel!
// added
let scrollingImagesStackView = UIStackView()
var stackHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = drinksImagesArray.count
setupDrinkImages()
}
func setupDrinkImages() {
// set stack view properties
scrollingImagesStackView.axis = .horizontal
scrollingImagesStackView.alignment = .fill
scrollingImagesStackView.distribution = .fillEqually
scrollingImagesStackView.spacing = 0
// add the stack view to the scroll view
scrollingImagesStackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(scrollingImagesStackView)
// use scroll view's contentLayoutGuide for content constraints
let svCLG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// stack view constrained Top / Bottom / Leading / Trailing of scroll view CONTENT guide
scrollingImagesStackView.topAnchor.constraint(equalTo: svCLG.topAnchor),
scrollingImagesStackView.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor),
scrollingImagesStackView.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor),
scrollingImagesStackView.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor),
])
// create the stack view height constraint - it will be updated in viewDidLayoutSubviews
stackHeightConstraint = scrollingImagesStackView.heightAnchor.constraint(equalToConstant: 0)
stackHeightConstraint.isActive = true
// create image views and add them to the stack view
drinksImagesArray.forEach { imgName in
let v = UIImageView()
v.backgroundColor = .orange
v.contentMode = .scaleAspectFit
// make sure we load a valid image
if let img = UIImage(named: imgName) {
v.image = img
}
scrollingImagesStackView.addArrangedSubview(v)
}
// stack distribution is set to .fillEqually, so we only need to set the
// width constraint on the first image view
// unwrap it
if let firstImageView = scrollingImagesStackView.arrangedSubviews.first {
firstImageView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
}
scrollView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// since we have a UINavigationBar and a UIToolBar in the view hierarchy,
// we need to set this here
// Note: if the view size changes
// stack view height == scroll view FRAME height
stackHeightConstraint.constant = scrollView.frame.height
}
// func setupDrinkImages() {
// for index in 0..<drinksImagesArray.count {
// frame.origin.x = scrollView.frame.size.width * CGFloat(index)
// frame.size = scrollView.frame.size
//
// let imageView = UIImageView(frame: frame)
// imageView.contentMode = .scaleAspectFit
// imageView.image = UIImage(named: drinksImagesArray[index])
// self.scrollView.addSubview(imageView)
// }
// scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(drinksImagesArray.count), height: scrollView.frame.size.height)
// scrollView.delegate = self
// }
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(pageNumber)
}
}
Your (modified) Storyboard is too big to add here... if you have any trouble with the changes mentioned above, here it is: https://pastebin.com/2Q1uFUgL

Size of image inside textfield is not resizing

I have a custom text field and i want to place image on its left side, but when ever i am running the app, the size of image is not adjusting , i.e its full scale and not taking the width and height being provided. The code and pictures are attached
ViewController class:(In which text field is present)
import UIKit
class Signup2ViewController: UIViewController {
#IBOutlet weak var Email: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
Email.leftViewMode = .always
let imageview = UIImageView()
imageview.frame = CGRect(x: 0, y: 0, width: 5.0, height: 5.0)
view.addSubview(imageview)
let icon = UIImage(named: "c.png")
imageview.image = icon
Email.leftView = imageview
// Do any additional setup after loading the view.
}
}
To set the frame for your imageView you need to subclass UITextField and override leftViewRect(forBounds:). The code below will result in a 20x20 view offset 10 points from the left and centered vertically.
class AwesomeTextField: UITextField {
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
let leftViewHeight: CGFloat = 20
let y = bounds.size.height / 2 - leftViewHeight / 2
return .init(x: 10, y: y, width: leftViewHeight, height: leftViewHeight)
}
}
To add an imageView to the textField you would do this:
class ViewController: UIViewController {
#IBOutlet weak var textField: AwesomeTextField!
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
imageView.image = UIImage(named: "c")
textField.leftView = imageView
textField.leftViewMode = .always
}
}
Make sure you set the appropriate class name for the textField in the identity inspector of your storyboard.

How can I fill image in scroll view to fill screen?

I am playing around with scroll views, and I've run into an issue I'be stuck with. I have a view controller create in Storyboard. The view controller contains a scroll view which fills the entire superview.
I then added the images programmatically to the scroll view. The images do show within the scroll view and paging works just fine. Only problem is the scroll view is set ti fill superview but the image view that hold the images seems like it stops above where the navigation bar would be. How can I have the image view fill the whole view within the scroll view?
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pagingView: UIPageControl!
var images = [UIImage]()
var frame = CGRect(x: 0, y: 0,width: 0,height: 0)
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.isPagingEnabled = true
images = [UIImage(named: "Slide1")!, UIImage(named: "Slide2")!, UIImage(named: "Slide3")!, UIImage(named: "Slide4")!]
pagingView.numberOfPages = images.count
// This is where I think I'm having the height problem.
for i in 0..<images.count {
let imageView = UIImageView()
let x = self.view.frame.size.width * CGFloat(i)
imageView.frame = CGRect(x: x, y: 0, width: self.view.frame.width, height: self.view.frame.height)
imageView.contentMode = .scaleAspectFill
imageView.image = images[i]
scrollView.contentSize.width = scrollView.frame.size.width * CGFloat(i + 1)
scrollView.addSubview(imageView)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pagingView.currentPage = Int(pageNumber)
}
After setting nav bar to hidden, here is the output
Scroll view background color is red
In this case you need to enable the parent view clipsToBounds. Set UIScrollview clipsToBounds property to True.
Programmatically scrollView.clipsToBounds = true
In UIStoryBoard - Click the view->Attributes Inspector
If you would like to see the whole screen, make sure to add the topConstraint of scrollView assigned superView and hide the navigationBar in viewWillAppear,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
Make sure to remove the status bar by
override var prefersStatusBarHidden: Bool {
return true
}
Update the Y position of Image.
imageView.frame = CGRect(x: x, y: **self.scrollView.frame.minY**, width: self.view.frame.width, height: self.view.frame.height)
Update the scrollView topConstraint by -20.

when images Scroll then nav Bar updated

I have a array of Images and i'm moving images using scroll view and ALL WORKS FINE.
As i'm new in Swift so Now i need a solution for, When the images moving how can i check which image number(or image index number) is showing from total of images and then update Navigation bar accordingly.
Here is attached sample that i'm looking for:
code of Moving Images
class ImageScrollViewController: UIViewController {
#IBOutlet weak var mainScrollView: UIScrollView!
var imageArray = [UIImage]()
override func viewDidLoad() {
//mainScrollView.frame = view.frame
imageArray.append(UIImage(named: "nature-2")!)
imageArray.append(UIImage(named: "nature")!)
imageArray.append(UIImage(named: "nature4")!)
imageArray.append(UIImage(named: "nature5")!)
imageArray.append(UIImage(named: "nature6")!)
for i in 0..<imageArray.count{
let imageView = UIImageView()
imageView.image = imageArray[i]
imageView.contentMode = .scaleAspectFit
let xPosition = (self.view.frame.width * CGFloat(i)) + CGFloat(8)
imageView.frame = CGRect(x: xPosition , y: 0, width: self.mainScrollView.frame.width - CGFloat(16), height: self.mainScrollView.frame.height)
mainScrollView.contentSize.width = mainScrollView.frame.width * CGFloat(i+1)
mainScrollView.addSubview(imageView)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Hope so you guys, understand my Question.
Any help will be appreciated.
Thanks in Advance.
You can use contentOffset to continuously keep track of the position in UIScrollViewDelegate method scrollViewDidScroll.
So, if the screen is 1000px wide:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// how far from beginning of scrollView (ex: user scrolls 3300px to the right)
let scrollDistance = mainScrollView.contentOffset.x
// size of 1 image (each is 1000px wide + 8px spacing between images)
let sizeOfOneImage = self.view.frame.width + 8
// number of images scrolled (3300px scrolled right = ~3.3 images scrolled so far...)
let numberOfScreensScrolled = scrollDistance / sizeOfOneImage
// floor(#) turns 3.3 into 3 (removes everything after decimal)
var imageInteger = floor(numberOfScreensScrolled)
// need +1, or else 1st image will be "0", 2nd image will be "1", etc.
imageInteger = imageInteger + 1
// # / total
self.title = "\(imageInteger)/\(imageArray count)"
}
Below, + self.view.frame.width/2 exists to make the transition to the next number occur when ~50% of the next image is onscreen (can be changed to whatever seems best).
class ImageScrollViewController: UIViewController, UIScrollViewDelegate {
...
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let imageInteger = floor((mainScrollView.contentOffset.x + self.view.frame.width/2) / (self.view.frame.width + 8)) + 1
self.title = "\(imageInteger)/\(imageArray count)"
}

How to set UIScrollView contensize when content is load asynchronously

My view hierarchy is this
PhotoDetailViewController.swift
View
UIScrollView
UIImageView
I set this up using storyboard, and add four constraints(top=0, bottom=0, leading=0, tailing=0) to UIScrollView, four constraints(top=0, bottom=0, leading=0, tailing=0) to UIImageView, but there are two error says
"ScrollView has ambiguous scrollable content width"
"ScrollView has ambiguous scrollable content height"
I understand that this is because I haven't set UIScrollView contentSize, but What I trying to do is load photo from PHAsset asynchronously, so I can only get the photo size at run time. So the question is:
1:Given that photo size can only be get at run time, how to solve the "ambiguous scrollable content" error?
2:In which View's life cycle method should I call PHImageManager.requestImageForAsset? because I think I should set UIScrollView contentSize programmatically, but when?
update with PhotoDetailViewController.swift
import UIKit
import Photos
class PhotoDetailViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
#IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
#IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!
var devicePhotosAsset : PHFetchResult!
var index = 0
var photo : UIImage!
var imgManager:PHImageManager!
#IBOutlet weak var imageView : UIImageView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func awakeFromNib() {
self.imgManager = PHImageManager()
}
override func viewDidLoad() {
super.viewDidLoad()
self.displayPhoto()
}
override func viewWillLayoutSubviews() {
super.viewDidLayoutSubviews()
updateMinZoomScaleForSize()
updateConstraintsForSize()
}
func displayPhoto () {
_ = self.imgManager.requestImageForAsset(self.devicePhotosAsset[self.index] as! PHAsset, targetSize: PHImageManagerMaximumSize, contentMode: .AspectFit, options: nil, resultHandler: {(result, info) -> Void in
NSOperationQueue.mainQueue().addOperationWithBlock(){
self.imageView.image = result
}
})
}
private func targetSize() -> CGSize {
let scale = UIScreen.mainScreen().scale
let targetSize = CGSizeMake(CGRectGetWidth(self.imageView.bounds)*scale, CGRectGetHeight(self.imageView.bounds)*scale)
return targetSize
}
private func updateMinZoomScaleForSize() {
let size = scrollView.bounds.size
let widthScale = size.width / imageView.bounds.width
let heightScale = size.height / imageView.bounds.height
let minScale = min(widthScale, heightScale)
scrollView.minimumZoomScale = minScale
scrollView.zoomScale = minScale
}
func recenterImage(){
let scrollViewSize = scrollView.bounds.size
let imageSize = imageView.frame.size
let horizontalSpace = imageSize.width < scrollViewSize.width ? (scrollViewSize.width - imageSize.width)/2 : 0
let verticalSpace = imageSize.height < scrollViewSize.height ? (scrollViewSize.height - imageSize.height)/2 : 0
scrollView.contentInset = UIEdgeInsets(top: verticalSpace, left: horizontalSpace, bottom: verticalSpace, right: horizontalSpace)
}
private func updateConstraintsForSize() {
let size = scrollView.bounds.size
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()
}
}
extension PhotoDetailViewController: UIScrollViewDelegate {
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidZoom(scrollView: UIScrollView) {
updateConstraintsForSize()
}
}
Your existing constraints are enough to set the content size, it's just that it's based on the image view intrinsic content size and that doesn't really exist until the image view has an image.
You can add a width and height constraint to the image view with default values and deactivate those constraints when the image is set to the view. Or you could use a placeholder image and avoid those extra constraints because you'd always have an intrinsic content size for the image view.
You should set two more constraint to your imageView.
Horizontally in Container (or you can say it center X)
Fixed Height
Second thing you can put UIView on Scrollview with Constraints like,
Top,leading,trailing,bottom,Horizontally in container(center x),fixed height).
Then add your imageview to that view. And can change it's constraint after getting image to resize it's height and width.
You can connect outlet of any constraint and can change it's constant programmatically.
Xcode UI builder has special type of constraint for such cases (when you can setup constraint only in runtime). It's so called "placeholder constraint" which will be removed at build time but helps to remove constraints errors for developing.
So solution is
Add some sample constraints IB and mark them as placeholders
Add needed constraints in runtime
When you get the data, just add these lines
float sizeOfContent = 0;
UIView *lLast = [yourscrollview.subviews lastObject];
NSInteger wd = lLast.frame.origin.y;
NSInteger ht = lLast.frame.size.height;
sizeOfContent = wd+ht;
yourscrollview.contentSize = CGSizeMake(yourscrollview.frame.size.width, sizeOfContent);
Hope this helps

Resources