Cannot create UIScrollerView with pure auto layout constraints - ios

I stripped down my code so that its easy to understand.
Say you have a controller and you want to add a simple scroller using pure auto layout.
You can invoke my function tool (provided below) as follows:
// Create scroll view
let strip = addStripCategoryTo(view)
// Attach it to the view, vertically and horizontally
strip.topAnchor.constraintEqualToAnchor(view.topAnchor).active = true
strip.leftAnchor.constraintEqualToAnchor(view.leftAnchor).active = true
// The function
func addStripCategoryTo(parent: UIView) -> UIView {
let h:CGFloat = 128
let w = 2*h/3
let n = 5
let width = w * CGFloat(n)
let height = h
// Scroll view
let scrollview = UIScrollView()
parent.addSubview(scrollview)
scrollview.scrollEnabled = true
scrollview.translatesAutoresizingMaskIntoConstraints = false
scrollview.widthAnchor .constraintEqualToAnchor(parent.widthAnchor).active = true
scrollview.heightAnchor.constraintEqualToConstant(h).active = true
scrollview.layer.borderWidth = 2
scrollview.layer.borderColor = UIColor.greenColor().CGColor
scrollview.backgroundColor = UIColor.brownColor()
// Scroll view content
let contentView = UIView() //frame:CGRect(origin: CGPointZero, size:CGSize(width: width, height: height)))
scrollview.addSubview(contentView)
contentView.backgroundColor = UIColor.redColor()
contentView.layer.borderWidth = 10
contentView.layer.borderColor = UIColor.blueColor().CGColor
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.centerYAnchor.constraintEqualToAnchor(scrollview.centerYAnchor).active = true
contentView.widthAnchor .constraintEqualToConstant(width).active = true
contentView.heightAnchor.constraintEqualToConstant(height).active = true
return scrollview
}
Unfortunately, I cannot scroll it horizontally, see screenshot as follows:
What am I missing?

Your constraints should be like this,
Scrollview - top,bottom,leading,trailing
ContentView - top,bottom,leading,trailing, fixed width, vertically center in container (center y)
and your content view's width should be large than screen width then also you can scroll horizontally
Hope this will help :)

Going to give this a try and answer this, see if it helps.
EDIT
Answering question better, old answer deleted:
Adding these two lines should make to scroll horizontally:
contentView.leadingAnchor.constraintEqualToAnchor(scrollview.leadingAnchor).active = true contentView.trailingAnchor.constraintEqualToAnchor(scrollview.trailingAnchor).active = true
The problem is that without these two constraint, the scrollView doesn't know its contentSize, therefor not scrolling, even though you gave the contentView a specific width. You need to let know the scrollView where does that contentView start and end, and these two constraints will help the scrollView know how big is the contentsView width. If not the scrollView's contentSize is going to stay the same as the device's width, and the contentView is just going to show offscreen since it's width is larger than the device's screen.
Hope this helped better.
Full code of the function
func addStripCategoryTo(parent: UIView)-> UIView {
let h:CGFloat = 128
let w = 2*h/3
let n = 5
let width = w * CGFloat(n)
let height = h
// Scroll view
let scrollview = UIScrollView()
parent.addSubview(scrollview)
scrollview.scrollEnabled = true
scrollview.translatesAutoresizingMaskIntoConstraints = false
scrollview.leadingAnchor.constraintEqualToAnchor(parent.leadingAnchor).active = true
scrollview.trailingAnchor.constraintEqualToAnchor(parent.trailingAnchor).active = true
scrollview.widthAnchor .constraintEqualToAnchor(parent.widthAnchor).active = true
scrollview.heightAnchor.constraintEqualToConstant(h).active = true
scrollview.layer.borderWidth = 2
scrollview.layer.borderColor = UIColor.greenColor().CGColor
scrollview.backgroundColor = UIColor.brownColor()
// Scroll view content
let contentView = UIView() //frame:CGRect(origin: CGPointZero, size:CGSize(width: width, height: height)))
scrollview.addSubview(contentView)
contentView.backgroundColor = UIColor.redColor()
contentView.layer.borderWidth = 10
contentView.layer.borderColor = UIColor.blueColor().CGColor
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.centerYAnchor.constraintEqualToAnchor(scrollview.centerYAnchor).active = true
contentView.leadingAnchor.constraintEqualToAnchor(scrollview.leadingAnchor).active = true
contentView.trailingAnchor.constraintEqualToAnchor(scrollview.trailingAnchor).active = true
contentView.widthAnchor.constraintEqualToConstant(width).active = true
contentView.heightAnchor.constraintEqualToConstant(height).active = true
return scrollview
}

Here is the solution of my issue. I provided a tool function that solve the issue as follow:
func setScrollViewConstraints(scrollView: UIScrollView, contentView:UIView, multiplier m:(width:CGFloat, height:CGFloat)=(1, 1)) {
guard let scrollViewParent = scrollView.superview else {
assert(false, "setScrollViewConstraints() requires the scrollview to have a superview")
return
}
guard let contentViewParent = contentView.superview else {
assert(false, "setScrollViewConstraints() requires the contentView to have a superview")
return
}
guard contentViewParent == scrollView else {
assert(false, "setScrollViewConstraints() requires the contentView to have a superview being the scrollView")
return
}
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.widthAnchor .constraintEqualToAnchor(scrollViewParent.widthAnchor, multiplier: m.width ).active = true
scrollView.heightAnchor .constraintEqualToAnchor(scrollViewParent.heightAnchor, multiplier: m.height).active = true
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor .constraintEqualToAnchor(contentViewParent.leftAnchor ).active = true
contentView.rightAnchor .constraintEqualToAnchor(contentViewParent.rightAnchor ).active = true
contentView.topAnchor .constraintEqualToAnchor(contentViewParent.topAnchor ).active = true
contentView.bottomAnchor.constraintEqualToAnchor(contentViewParent.bottomAnchor ).active = true
/*
// Pre-iOS 9 version
let views = [
"scrollView":scrollview,
"contentView":contentView
]
parent.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options:[], metrics: [:], views:views))
parent.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options:[], metrics: [:], views:views))
scrollview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[contentView]|", options:[], metrics: [:], views:views))
scrollview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[contentView]|", options:[], metrics: [:], views:views))
*/
}
Use it like this for having the scroll view 100% width the super view and 1/4th of its height:
func addStripCategoryTo(parent: UIView) -> UIView {
// Scroll view
let scrollview = UIScrollView()
parent.addSubview(scrollview)
scrollview.backgroundColor = UIColor.brownColor()
scrollview.layer.borderWidth = 2
scrollview.layer.borderColor = UIColor.greenColor().CGColor
// Scroll view content
let contentView = UIImageView()
contentView.image = UIImage(named: "cover-0.jpeg")
scrollview.addSubview(contentView)
contentView.backgroundColor = UIColor.redColor()
contentView.layer.borderWidth = 10
contentView.layer.borderColor = UIColor.blueColor().CGColor
// Apply constraints
setScrollViewConstraints(scrollview, contentView:contentView, multiplier: (1, 0.25))
return scrollview
}

Related

UIImageView cuts inside UIScrollView

I'm trying to create UIScrollView With UIStackView that contains multiple UIImageView with this code:
let stackView = UIStackView(frame: .zero)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .green
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
self.view.addSubview(scrollView)
scrollView.anchor(top: textSV.bottomAnchor, leading: self.view.safeAreaLayoutGuide.leadingAnchor, bottom: anotherView?.topAnchor, trailing: self.view.safeAreaLayoutGuide.trailingAnchor)
scrollView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
scrollView.contentInsetAdjustmentBehavior = .never
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .equalSpacing
stackView.spacing = 0
scrollView.addSubview(stackView)
stackView.fillSuperview()
for _ in 1...8 {
let pageView = UIImageView(image: UIImage(named: "iphone12mockup"))
pageView.clipsToBounds = true
pageView.contentMode = .scaleAspectFit
pageView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(pageView)
pageView.anchor(top: stackView.topAnchor, leading: nil, bottom: stackView.bottomAnchor, trailing: nil)
}
The problem is that the UIImageView does not resize to scaleAspectFit and it looks like this(Can't see full image):
EDIT
let img = UIImage(named: "iphone12mockup")
let width = img?.size.width
let pageView = UIImageView(image: img)
pageView.clipsToBounds = true
pageView.contentMode = .scaleAspectFit
pageView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(pageView)
pageView.anchor(top: stackView.topAnchor, leading: nil, bottom: stackView.bottomAnchor, trailing: nil)
pageView.widthAnchor.constraint(equalToConstant: width!).isActive = true
You want to make use of the scroll view's Content and Frame Layout Guides...
constrain all 4 sides of the stack view to the scroll view's Content Layout Guide
constrain the stack view's Height to the scroll view's Frame Layout Guide
for each image view you add to the stack view:
constrain the image view's Width to the scroll view's Frame Layout Guide
Here is a complete example:
class ViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// just trying to include what you've shown
let textSV = UILabel()
textSV.backgroundColor = .yellow
textSV.text = "textSV"
textSV.textAlignment = .center
let anotherView = UILabel()
anotherView.backgroundColor = .cyan
anotherView.text = "anotherView"
anotherView.textAlignment = .center
[textSV, anotherView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
textSV.topAnchor.constraint(equalTo: safeG.topAnchor),
textSV.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
textSV.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
textSV.heightAnchor.constraint(equalToConstant: 60.0),
anotherView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
anotherView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
anotherView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor),
anotherView.heightAnchor.constraint(equalToConstant: 60.0),
])
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .green
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
view.addSubview(scrollView)
// use a stack view to hold and arrange the scrollView's subviews
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
// add the stackView to the scrollView
scrollView.addSubview(stackView)
// use scrollView's Content Layout Guide to define scrollable content
let layoutG = scrollView.contentLayoutGuide
// use scrollView's Frame Layout Guide to define content height (since you want horizontal scrolling)
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView Top to textSV Bottom
scrollView.topAnchor.constraint(equalTo: textSV.bottomAnchor),
// constrain scrollView Leading/Trailing to safe area
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
// constrain scrollView Bottom to anotherView Top
scrollView.bottomAnchor.constraint(equalTo: anotherView.topAnchor),
// constrain all 4 sides of the stackView to scrollView's Content Layout Guide
stackView.topAnchor.constraint(equalTo: layoutG.topAnchor),
stackView.bottomAnchor.constraint(equalTo: layoutG.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: layoutG.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: layoutG.trailingAnchor),
// constrain stackView's height to scrollView's Frame Layout Guide height
stackView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
// add imageViews to the stack view
for _ in 1...8 {
let pageView = UIImageView(image: UIImage(named: "iphone12mockup"))
//let pageView = UIImageView(image: UIImage(named: "sample"))
// set image view background color so you can
// see its frame (since the image will be aspect-fit scaled)
pageView.backgroundColor = .systemYellow
pageView.contentMode = .scaleAspectFit
// add it to the stack view
stackView.addArrangedSubview(pageView)
// constrain its Width to scrollView's Frame Layout Guide Width
pageView.widthAnchor.constraint(equalTo: frameG.widthAnchor).isActive = true
}
}
}
It will look like this on startup (on an iPhone 8):
and after scrolling a little to the right:
Note that since you want the image view set to Aspect Fit, I gave the "pageView" image views a background color of .systemYellow so you can see that the imageView frame fills the scroll view frame width and height.
Edit -- if you want the images to be proportional to their height, without "empty space on the sides," you need to set the image view width constraint proportional to its height, based on the image size.
Replace the "add image views" loop with this:
// add imageViews to the stack view
for _ in 1...8 {
guard let img = UIImage(named: "iphone12mockup") else {
fatalError("Could not load image!")
}
let pageView = UIImageView()
pageView.image = img
pageView.contentMode = .scaleToFill
// add it to the stack view
stackView.addArrangedSubview(pageView)
// constrain its Width proportional to the image height
pageView.widthAnchor.constraint(equalTo: pageView.heightAnchor, multiplier: img.size.width / img.size.height).isActive = true
}
and the output will be:
and after scrolling a little to the right:

Center stack view elements and not fill them

I am using a UIStackView as UITableView's BackGroundView property so when there was an error getting the collection that populates the tableView I can call a function that displays this stack view containing views that show a warning message and a retry button.
I tested doing a similar behaviour in an empty UIViewController so I could center the stackView and its children. The solution worked when I pinned the stack view to the superView's trailing and leading, centered it vertically and set it's top anchor to be greater or equal to the superView's top anchor and similarly it's bottom anchor is greater or equal to the superView's bottom anchor. I have also set the alignment to center and distribution to fill and all seemed to work properly.
Here are some screenshots:
I used this code in a UITableView's extension, but could only reproduce this behaviour. Are there any errors on this code?
func show(error: Bool, withMessage message : String? = nil, andRetryAction retry: (() -> Void)? = nil){
if error{
let iconLabel = UILabel()
iconLabel.GMDIcon = .gmdErrorOutline
iconLabel.textAlignment = .center
iconLabel.numberOfLines = 0
iconLabel.font = iconLabel.font.withSize(50)
iconLabel.textColor = Constants.Colors.ErrorColor
iconLabel.backgroundColor = .blue
let messageLabel = UILabel()
messageLabel.text = message ?? "Ocorreu um erro"
messageLabel.textColor = Constants.Colors.ErrorColor
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center
messageLabel.font = UIFont(name: "TrebuchetMS", size: 20)
messageLabel.backgroundColor = .green
var views: [UIView] = [iconLabel, messageLabel]
if let retry = retry{
let button = RaisedButton(title: "Tentar novamente")
button.pulseColor = Constants.Colors.PrimaryTextColor
button.backgroundColor = Constants.Colors.PrimaryColor
button.titleColor = .white
button.actionHandle(controlEvents: .touchUpInside, ForAction: retry)
button.contentEdgeInsets = UIEdgeInsetsMake(10,10,10,10)
views.append(button)
}
}else{
self.backgroundView = nil
}
}
let stack = UIStackView()
stack.spacing = 10
stack.axis = .vertical
stack.alignment = .center
stack.distribution = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
for view in views{
view.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(view)
}
if self.tableFooterView == nil{
tableFooterView = UIView()
}
self.backgroundView = stack;
if #available(iOS 11, *) {
let guide = self.safeAreaLayoutGuide
stack.topAnchor.constraint(greaterThanOrEqualTo: guide.topAnchor).isActive = true
stack.bottomAnchor.constraint(greaterThanOrEqualTo: guide.bottomAnchor).isActive = true
stack.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: guide.centerYAnchor).isActive = true
} else {
stack.topAnchor.constraint(greaterThanOrEqualTo: self.topAnchor).isActive = true
stack.bottomAnchor.constraint(greaterThanOrEqualTo: self.bottomAnchor).isActive = true
stack.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
A stack view knows its height if its elements all have an intrinsic size (ie its the sum of their individual heights + the inter item spacing). In this case because you have a 2 y position constraints, you are implying a height, so your constraints are unsatisfiable. The only y axis constraint you need is center vertically. get rid of the top and bottom constraints. The system will then use the intrinsic size to compute the height of the stack view and center it vertically in the background view. Leave your x axis constraints as is.

How to make auto-layout constraint dependent on multiple other anchors?

How can auto-layout be used to make a view's height equal to the sum of two other view's heights?
For example:
viewA.heightAnchor.constraint(equalTo: ...),
viewB.heightAnchor.constraint(equalTo: ...),
viewC.heightAnchor.constraint(equalTo: viewA.heightAnchor + viewB.heightAnchor)
Is there a solution that does not involve setting a constant value and recalculating it on every view bounds change?
You can, but only with the help of some helper views, I think. See this example playground I just now cooked up that achieves this:
import Foundation
import UIKit
import PlaygroundSupport
let vc = UIViewController()
vc.view = UIView()
vc.view.backgroundColor = .white
let viewA = UIView()
let viewB = UIView()
let viewC = UIView()
viewA.backgroundColor = .red
viewB.backgroundColor = .green
viewC.backgroundColor = .blue
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.translatesAutoresizingMaskIntoConstraints = false
viewC.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(viewA)
vc.view.addSubview(viewB)
vc.view.addSubview(viewC)
let helperViewA = UIView()
let helperViewB = UIView()
helperViewA.translatesAutoresizingMaskIntoConstraints = false
helperViewB.translatesAutoresizingMaskIntoConstraints = false
helperViewA.isHidden = true
helperViewB.isHidden = true
vc.view.addSubview(helperViewA)
vc.view.addSubview(helperViewB)
viewA.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
viewA.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor).isActive = true
viewA.trailingAnchor.constraint(equalTo: viewB.leadingAnchor).isActive = true
viewA.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
viewB.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
viewB.widthAnchor.constraint(equalTo: viewA.widthAnchor, multiplier: 1.0).isActive = true
viewB.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor).isActive = true
viewB.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
viewA.heightAnchor.constraint(equalTo: helperViewA.heightAnchor, multiplier: 1.0).isActive = true
viewB.heightAnchor.constraint(equalTo: helperViewB.heightAnchor, multiplier: 1.0).isActive = true
helperViewA.bottomAnchor.constraint(equalTo: helperViewB.topAnchor).isActive = true
viewC.widthAnchor.constraint(equalToConstant: 100).isActive = true
viewC.topAnchor.constraint(equalTo: helperViewA.topAnchor).isActive = true
viewC.bottomAnchor.constraint(equalTo: helperViewB.bottomAnchor).isActive = true
helperViewA.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
helperViewA.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
helperViewB.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
helperViewB.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
viewC.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true
viewC.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor).isActive = true
vc.view.frame.size = CGSize(width: 375, height: 667)
PlaygroundPage.current.liveView = vc.view
The idea is that you have two helper views stacked vertically on each other, connected by the top one's bottomAnchor and the bottom one's topAnchor. You then set each of these helper views' heights to be equal to your viewA and viewB heights. Then, your viewC can be attached to the top view's topAnchor and the bottom view's bottomAnchor, which gives the result of viewC being the height of viewA's height plus viewB's height.

Swift: ScrollView not scrolling (how to set the constraints)?

I am trying to set up a scroll view with auto layout in my storyboard and populate it with a couple of buttons in code, but the scroll view doesn't scroll and I don't understand how to set up the constraints to make scrolling available?
Especially how to set the constraints to the content view (what's that?).
In storyboard the scroll view is placed at the bottom of the screen (20 pix to the safe area) and from leading to trailing. It has a size of 375x80.
Now in code this is how the scroll view is populated with buttons and how it is set up:
override func viewDidLoad() {
super.viewDidLoad()
var xCoord: CGFloat = 5
var yCoord: CGFloat = 5
let buttonWidth: CGFloat = 70
let buttonHeight: CGFloat = 70
let gapBetweenButtons: CGFloat = 5
var itemCount = 0
for i in 0..<CIFilterNames.count {
itemCount = 1
// Button properties
let filterButton = UIButton(type: .custom)
filterButton.frame = CGRect(x: xCoord, y: yCoord, width: buttonWidth, height: buttonHeight)
filterButton.tag = itemCount
filterButton.addTarget(self, action: #selector(ViewController.filterButtonTapped(sender:)), for: .touchUpInside)
filterButton.layer.cornerRadius = 6
filterButton.clipsToBounds = true
//Code for filters will be added here
let ciContext = CIContext(options: nil)
let coreImage = CIImage(image: originalImage.image!)
let filter = CIFilter(name: "\(CIFilterNames[i])")
filter!.setDefaults()
filter?.setValue(coreImage, forKey: kCIInputImageKey)
let filteredImageDate = filter!.value(forKey: kCIOutputImageKey) as! CIImage
let filteredImageRef = ciContext.createCGImage(filteredImageDate, from: filteredImageDate.extent)
let imageForButton = UIImage(cgImage: filteredImageRef!)
// Asign filtered image to the button
filterButton.setBackgroundImage(imageForButton, for: .normal)
// Add buttons in the scrollView
xCoord += buttonWidth + gapBetweenButtons
filterScrollView.addSubview(filterButton)
}
filterScrollView.contentSize = CGSize(width: buttonWidth * CGFloat(itemCount + 2), height: yCoord)
filterScrollView.isScrollEnabled = true
}
Depending on the device size 4 or 5 buttons are shown, but not more and scrolling is not possible.
What can be done to make scrolling possible?
Reason is in itemCount , you're settings it to itemCount + 2 will be 3 as itemCount is 1 from the for loop so , set it to equal array size
filterScrollView.contentSize = CGSize(width: buttonWidth * CIFilterNames.count , height: yCoord)
You will be much better off using auto-layout over calculating sizes - gives much more flexibility for future changes.
Here is an example you can run in a Playground page. I used the "count" as the button labels, since I don't have your images. If you add images, you can un-comment the appropriate lines and remove the .setTitle and .backgroundColor lines:
import UIKit
import PlaygroundSupport
import CoreImage
class TestViewController : UIViewController {
var CIFilterNames = [
"CIPhotoEffectChrome",
"CIPhotoEffectFade",
"CIPhotoEffectInstant",
"CIPhotoEffectNoir",
"CIPhotoEffectProcess",
"CIPhotoEffectTonal",
"CIPhotoEffectTransfer",
"CISepiaTone"
]
let buttonWidth: CGFloat = 70
let buttonHeight: CGFloat = 70
let gapBetweenButtons: CGFloat = 5
override func viewDidLoad() {
super.viewDidLoad()
// create a UIScrollView
let filterScrollView = UIScrollView()
// we will set the auto-layout constraints
filterScrollView.translatesAutoresizingMaskIntoConstraints = false
// set background color so we can see the scrollView when the images are scrolled
filterScrollView.backgroundColor = .orange
// add the scrollView to the view
view.addSubview(filterScrollView)
// pin scrollView 20-pts from bottom/leading/trailing
filterScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true
filterScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0).isActive = true
filterScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20.0).isActive = true
// scrollView height is 80
filterScrollView.heightAnchor.constraint(equalToConstant: 80).isActive = true
// create a UIStackView
let stackView = UIStackView()
stackView.spacing = gapBetweenButtons
// we will set the auto-layout constraints
stackView.translatesAutoresizingMaskIntoConstraints = false
// add the stackView to the scrollView
filterScrollView.addSubview(stackView)
// with auto-layout, scroll views use the content's constraints to
// determine the contentSize,
// so pin the stackView to top/bottom/leading/trailing of the scrollView
// with padding to match the gapBetweenButtons
stackView.leadingAnchor.constraint(equalTo: filterScrollView.leadingAnchor, constant: gapBetweenButtons).isActive = true
stackView.topAnchor.constraint(equalTo: filterScrollView.topAnchor, constant: gapBetweenButtons).isActive = true
stackView.trailingAnchor.constraint(equalTo: filterScrollView.trailingAnchor, constant: -gapBetweenButtons).isActive = true
stackView.bottomAnchor.constraint(equalTo: filterScrollView.bottomAnchor, constant: -gapBetweenButtons).isActive = true
// loop through the images
for i in 0..<CIFilterNames.count {
// create a new UIButton
let filterButton = UIButton(type: .custom)
filterButton.tag = i
// we will set the auto-layout constraints, and allow the stackView
// to handle the placement
filterButton.translatesAutoresizingMaskIntoConstraints = false
// set the width and height constraints
filterButton.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true
filterButton.heightAnchor.constraint(equalToConstant: buttonHeight).isActive = true
filterButton.layer.cornerRadius = 6
filterButton.clipsToBounds = true
filterButton.addTarget(self, action: #selector(filterButtonTapped(_:)), for: .touchUpInside)
// this example doesn't have your images, so
// set button title and background color
filterButton.setTitle("\(i)", for: .normal)
filterButton.backgroundColor = .blue
// //Code for filters will be added here
//
// let ciContext = CIContext(options: nil)
// let coreImage = CIImage(image: originalImage.image!)
// let filter = CIFilter(name: "\(CIFilterNames[i])")
// filter!.setDefaults()
// filter?.setValue(coreImage, forKey: kCIInputImageKey)
// let filteredImageDate = filter!.value(forKey: kCIOutputImageKey) as! CIImage
// let filteredImageRef = ciContext.createCGImage(filteredImageDate, from: filteredImageDate.extent)
// let imageForButton = UIImage(cgImage: filteredImageRef!)
//
// // Asign filtered image to the button
// filterButton.setBackgroundImage(imageForButton, for: .normal)
// add the image view to the stackView
stackView.addArrangedSubview(filterButton)
}
}
func filterButtonTapped(_ sender: Any?) -> Void {
if let b = sender as? UIButton {
print("Tapped:", b.tag)
}
}
}
let vc = TestViewController()
vc.view.backgroundColor = .red
PlaygroundPage.current.liveView = vc

How to scroll images horizontally in iOS swift

I have an imageview called "cardImgView" in that I want to load two images by scrolling horizontally, I have tried the following way, in this case I can able to scroll only to up and down and the images also not changing, anyone
have idea how to do this correctly.
let img: UIImage = self.dataDict.object(forKey: kCardImgFront) as! UIImage
let img2:UIImage = self.dataDict.object(forKey: kCardImgBack) as! UIImage
imgArray = [img, img2]
for i in 0..<imgArray.count{
cardImgView?.image = imgArray[i]
scrollView.contentSize.width = scrollView.frame.width * CGFloat(i + 1)
scrollView.addSubview(cardImgView!)
}
thanks in advance.
First, as I commented, you are currently using a single UIImageView --- so each time through your for-loop you are just replacing the .image of that one image view.
Second, you will be much better off using auto-layout and constraints, instead of trying to explicitly set frames and the scrollView's contentSize.
Third, UIStackView is ideal for your use case - adding multiple images that you want to horizontally scroll.
So, the general idea is:
add a scroll view
add a stack view to the scroll view
use constraints to make the stack view control the scroll view's contentSize
create a new UIImageView for each image
add each image view to the stack view
Here is a simple example that you can run in a Playground page to see how it works. If you add your own images named image1.png and image2.png to the playground's resources, they will be used (otherwise, this example creates solid blue and solid green images):
import UIKit
import PlaygroundSupport
// UIImage extension to create a new, solid-color image
public extension UIImage {
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
}
class TestViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create a UIScrollView
let scrollView = UIScrollView()
// we will set the auto-layout constraints
scrollView.translatesAutoresizingMaskIntoConstraints = false
// set background color so we can see the scrollView when the images are scrolled
scrollView.backgroundColor = .orange
// add the scrollView to the view
view.addSubview(scrollView)
// pin scrollView 20-pts from top/bottom/leading/trailing
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20.0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20.0).isActive = true
// create an array of empty images in case this is run without
// valid images in the resources
var imgArray = [UIImage(color: .blue), UIImage(color: .green)]
// if these images exist, load them and replace the blank images in imgArray
if let img1: UIImage = UIImage(named: "image1"),
let img2: UIImage = UIImage(named: "image2") {
imgArray = [img1, img2]
}
// create a UIStackView
let stackView = UIStackView()
// we can use the default stackView properties
// but can change axis, alignment, distribution, spacing, etc if desired
// we will set the auto-layout constraints
stackView.translatesAutoresizingMaskIntoConstraints = false
// add the stackView to the scrollView
scrollView.addSubview(stackView)
// with auto-layout, scroll views use the content's constraints to
// determine the contentSize,
// so pin the stackView to top/bottom/leading/trailing of the scrollView
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0.0).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0.0).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0.0).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0.0).isActive = true
// loop through the images
for img in imgArray {
// create a new UIImageView
let imgView = UIImageView(image: img)
// we will set the auto-layout constraints, and allow the stackView
// to handle the placement
imgView.translatesAutoresizingMaskIntoConstraints = false
// set image scaling as desired
imgView.contentMode = .scaleToFill
// add the image view to the stackView
stackView.addArrangedSubview(imgView)
// set imgView's width and height to the scrollView's width and height
imgView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0).isActive = true
imgView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0).isActive = true
}
}
}
let vc = TestViewController()
vc.view.backgroundColor = .red
PlaygroundPage.current.liveView = vc
I modified my code and tried as follows and its working now. with page contrlller
let imgArray = [UIImage]()
let img: UIImage = self.dataDict.object(forKey: kCardImgFront) as! UIImage
let img2:UIImage = self.dataDict.object(forKey: kCardImgBack) as! UIImage
imgArray = [img, img2]
for i in 0..<imgArray.count {
let imageView = UIImageView()
imageView.image = imgArray[i]
let xPosition = self.view.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xPosition, y: 0, width:
self.scrollView.frame.width + 50, height: self.scrollView.frame.height)
scrollView.contentSize.width = scrollView.frame.width * CGFloat(i + 1)
scrollView.addSubview(imageView)
}
self.scrollView.delegate = self
func scrollViewDidScroll(_ scrollView: UIScrollView){
pageController.currentPage = Int(self.scrollView.contentOffset.x /
CGFloat(4))
}
I think you need to set a proper frame for the cardImgView. It would be something like
cardImgView.frame = CGRect(x: scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: scrollView.frame.height)
Finally, after the for loop, you need to set scroll view's content size:
scrollView.contentSize.width = scrollView.frame.width * imgArray.count
Hope this helps.
I have written scrolling images horizontally in swift. Please check with this:
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
#IBOutlet weak var Bannerview: UIView!
var spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
var loadingView: UIView = UIView()
var loadinglabel: UILabel = UILabel()
var nextPage :Int!
var titlelab :UILabel!
var bannerimg :UIImageView!
var scroll :UIScrollView!
var viewPanel :UIView!
var pgCtr:UIPageControl!
var bannerArr:[String]!
var imgUrlstr :NSString!
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
func uicolorFromHex(rgbValue:UInt32)->UIColor
{
let red = CGFloat((rgbValue & 0xFF0000) >> 16)/256.0
let green = CGFloat((rgbValue & 0xFF00) >> 8)/256.0
let blue = CGFloat(rgbValue & 0xFF)/256.0
return UIColor(red:red, green:green, blue:blue, alpha:1.0)
}
override func viewWillAppear(_ animated: Bool)
{
screenSize = UIScreen.main.bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
bannerArr = ["image1.jpeg","image2.jpeg","image3.jpeg","images4.jpeg","images5.jpeg"]
self.bannerview()
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.navigationController?.navigationBar.isTranslucent = false
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}

Resources