Create a horizontal UIScrollView in swift programmatically - swift4

I would like to know how to create a horizontal scrollview in swift programmatically without using storyboards, with a page controller.

import UIKit
class SecondViewController: UIViewController, UIScrollViewDelegate {
var scrollview = UIScrollView()
var imageview = UIImageView()
var view1 = UIView()
override func viewDidLoad() {
super.viewDidLoad()
imageview.frame = CGRect(x: 0, y: 0, width: view.frame.size.width , height: 1000)
imageview.image = UIImage(named: "image")
scrollview.delegate = self
scrollview.contentSize = CGSize(width: imageview.frame.width, height: imageview.frame.height)
scrollview.backgroundColor = UIColor.red
imageview.backgroundColor = UIColor.green
self.scrollview.addSubview(imageview)
view.addSubview(scrollview)
scrollview.translatesAutoresizingMaskIntoConstraints = false
scrollview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
scrollview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollview.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollview.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Do any additional setup after loading the view.
}

Try This
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
let textData: String = "Just to add onto the already great answers, you might want to add multiple labels in your project so doing all of this (setting size, style etc) will be a pain. To solve this, you can create a separate UILabel class."
override func viewDidLoad() {
super.viewDidLoad()
let textLable = UILabel(frame: CGRect(x: 0, y: 0, width: 1000, height: 200))
textLable.text = textData
self.scrollView = UIScrollView()
self.scrollView.delegate = self
self.scrollView.contentSize = CGSize(width: textLable.frame.width, height: textLable.frame.height)
scrollView.addSubview(textLable)
view.addSubview(scrollView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Related

How to dynamically change UIStackView height based on content size?

I have a view below that I'm trying to dynamically change the height of based on the heights of the subviews in the content view. Currently, I'm setting the scrollView and contentView's contentSize with a static height of "screenHeight + 1000". What steps can I take to dynamically update the height property?
import Foundation
import UIKit
import TinyConstraints
class ViewController: UIViewController {
// MARK: - PROPERTIES
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
var contentViewSize = CGSize(width: screenWidth, height: screenHeight + 1000)
// MARK: - VIEW METHODS
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
constrainSubviews()
}
// MARK: - VIEW OBJECTS
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: .zero)
scrollView.frame = view.bounds
scrollView.contentSize = contentViewSize
scrollView.autoresizingMask = .flexibleHeight
scrollView.showsHorizontalScrollIndicator = true
scrollView.bounces = true
return scrollView
}()
lazy var contentView: UIView = {
let contentView = UIView()
contentView.frame.size = contentViewSize
return contentView
}()
lazy var topView: UIStackView = { ... }()
lazy var nutritionView: UIStackView = { ... }()
lazy var ingredientsView: UIStackView = { ... }()
lazy var instructionsView: UIStackView = { ... }()
fileprivate func addSubviews() {
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(topView)
contentView.addSubview(nutritionView)
contentView.addSubview(ingredientsView)
contentView.addSubview(instructionsView)
}
fileprivate func constrainSubviews() {
let stack = [topView, nutritionView, ingredientsView, instructionsView]
contentView.stack(stack, spacing: screenHeight * 0.03)
contentView.centerXToSuperview()
contentView.topToSuperview(offset: screenHeight * 0.04)
contentView.width(screenWidth * 0.8)
}
}
You should add a scroll view and inside that add and stackview, and inside the stack view add the views, and the scroll view will increase dynamically, like this code.
class ViewController: UIViewController {
let scrollView = UIScrollView()
lazy var contentStackView = UIStackView(arrangedSubviews: [getView(height: 500, color: .red),
getView(height: 600, color: .blue),
getView(height: 70, color: .gray),
getView(height: 80,color: .yellow)])
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = .white
contentStackView.axis = .vertical
setupConstraints()
}
func setupConstraints() {
self.view.addSubview(scrollView)
self.scrollView.addSubview(contentStackView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
contentStackView.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor),
contentStackView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
contentStackView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor)
])
}
func getView(height: Double, color: UIColor) -> UIView {
let view = UIView()
view.backgroundColor = color
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: height).isActive = true
return view
}
}

Is there a short way to assign all the anchors of a view equals to another view's all anchors

let's say I have a container view, which has an imageView and blurEffectView as its subView. In short, I apply a blurEffect on this image inside the container view.
I used anchors to configure the auto-layout programmatically without IB. And the anchors of imageView and blurredEffectView are completely the same as their container view.
I'm wondering is there a simple/quick way to do things like aView.allAnchors.equals(bView's allAnchors)? Then we could save a lot of repeating code.
- container
- imageView
- blurEffectView
let container: UIView = {
let aView = UIView()
aView.translatesAutoresizingMaskIntoConstraints = false
return aView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
navigationController?.navigationBar.prefersLargeTitles = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
configureLayout()
}
func configureLayout() {
let imageView = UIImageView(image: UIImage(named: "Wang Fei"))
// imageView.frame = container.bounds
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
container.addSubview(imageView)
let blurEffect = UIBlurEffect(style: .dark)
let blurredEffectView = UIVisualEffectView(effect: blurEffect)
blurredEffectView.translatesAutoresizingMaskIntoConstraints = false
// blurredEffectView.frame = imageView.bounds
container.addSubview(blurredEffectView)
view.addSubview(container)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: view.topAnchor),
container.leadingAnchor.constraint(equalTo: g.leadingAnchor),
container.trailingAnchor.constraint(equalTo: g.trailingAnchor),
container.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.42),
imageView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
imageView.topAnchor.constraint(equalTo: container.topAnchor),
imageView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
blurredEffectView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
blurredEffectView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
blurredEffectView.topAnchor.constraint(equalTo: container.topAnchor),
blurredEffectView.bottomAnchor.constraint(equalTo: container.bottomAnchor)
])
}
There's no built-in shortcut. So most people write one. Or they adopt an existing framework that provides such shortcuts; SnapKit is a popular choice.
You can wrap the repeating code in a convenient extension like this -
import UIKit
extension UIView {
func setUpEdgeToEdge(in container: UIView, insets: UIEdgeInsets = .zero) {
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: container.topAnchor, constant: insets.top),
self.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: insets.left),
self.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: insets.bottom),
self.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -insets.right),
])
}
}
Now you can call it like -
imageView.setUpEdgeToEdge(in: container)
// OR
imageView.setUpEdgeToEdge(in: container, insets: UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16))
You can use a frame instead of a constraint. Also, in code setting constraint and view inside the viewDidLayoutSubviewsit's not a good way. Also, use lazy.
Here is the possible solution.
class TestViewController: UIViewController {
private lazy var container: UIView = {
let aView = UIView()
aView.translatesAutoresizingMaskIntoConstraints = false
return aView
}()
private lazy var imageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "Red"))
imageView.translatesAutoresizingMaskIntoConstraints = true
imageView.contentMode = .scaleAspectFit
return imageView
}()
private lazy var blurredEffectView: UIVisualEffectView = {
let blurEffect = UIBlurEffect(style: .dark)
let blurredEffectView = UIVisualEffectView(effect: blurEffect)
blurredEffectView.translatesAutoresizingMaskIntoConstraints = false
return blurredEffectView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
navigationController?.navigationBar.prefersLargeTitles = true
setViews()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.imageView.frame = container.bounds
self.blurredEffectView.frame = container.bounds
}
private func setViews() {
view.addSubview(container)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: view.topAnchor),
container.leadingAnchor.constraint(equalTo: g.leadingAnchor),
container.trailingAnchor.constraint(equalTo: g.trailingAnchor),
container.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.42)])
container.addSubview(imageView)
container.addSubview(blurredEffectView)
}
}

How to implement a scrollView inside inputAccessoryView [Swift]

In my swift app I'm working with inputActivityView, really hard, and my idea is to add a scroll view to this view with 2 subviews and paging enable.
Here's what I've done, I think the problem are constraints but I don't know how to solve it.
lazy var scrollView: UIScrollView = {
let sv = UIScrollView(frame: self.bounds)
sv.backgroundColor = .blue
sv.isPagingEnabled = true
sv.contentSize = .init(width: 2 * self.frame.width, height: 54)
return sv
}()
override init(frame: CGRect) { // the init of the customInputAccessoryView
super.init(frame: frame)
setup()
}
override var intrinsicContentSize: CGSize {
return .zero
}
func setup() {
backgroundColor = .red
autoresizingMask = .flexibleHeight
addSubview(scrollView)
scrollView.fillSuperview()
scrollView.heightAnchor.constraint(equalToConstant: 54).isActive = true
firstView = UIView(frame: .init(origin: .zero, size: .init(width: frame.width, height: 54)))
firstView.frame.origin = .zero
firstView.backgroundColor = .gray
firstView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(firstView)
secondView = UIView(frame: firstView.bounds)
secondView.frame.origin.x = frame.width
secondView.backgroundColor = .lightGray
secondView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(secondView)
addConstraints()
}
private func addConstraints() {
NSLayoutConstraint.activate([
firstView.widthAnchor.constraint(equalToConstant: frame.width),
firstView.heightAnchor.constraint(equalToConstant: 54)
])
}
How can I set the constraints for the subviews, because in this way appear only the first view, and I can't scroll to the second one.
Yes, you're missing some constraints.
First, no need to instantiate views with UIView(frame: ...) if you are then setting .translatesAutoresizingMaskIntoConstraints = false because the frame you just gave it will be ignored.
Second, if you have your constraints setup correctly, no need to set a scroll view's .contentSize
// don't do this
//sv.contentSize = .init(width: 2 * self.frame.width, height: 54)
Third, when configuring subviews of a scroll view, make sure your constraints define Top / Leading / Bottom / Trailing AND Width and Height.
Here's an edited version of your code to try:
class MyInputAccessoryView: UIView {
lazy var scrollView: UIScrollView = {
let sv = UIScrollView()
sv.backgroundColor = .blue
sv.isPagingEnabled = true
// no need for this
//sv.contentSize = .init(width: 2 * self.frame.width, height: 54)
return sv
}()
var firstView: UIView!
var secondView: UIView!
override init(frame: CGRect) { // the init of the customInputAccessoryView
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
func setup() {
backgroundColor = .red
autoresizingMask = .flexibleHeight
addSubview(scrollView)
//scrollView.fillSuperview()
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
scrollView.heightAnchor.constraint(equalToConstant: 54),
])
//firstView = UIView(frame: .init(origin: .zero, size: .init(width: frame.width, height: 54)))
//firstView.frame.origin = .zero
firstView = UIView()
firstView.backgroundColor = .gray
firstView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(firstView)
//secondView = UIView(frame: firstView.bounds)
//secondView.frame.origin.x = frame.width
secondView = UIView()
secondView.backgroundColor = .lightGray
secondView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(secondView)
addConstraints()
}
private func addConstraints() {
NSLayoutConstraint.activate([
// make both subviews equal width and height to scrollView
firstView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
firstView.heightAnchor.constraint(equalTo: scrollView.heightAnchor),
secondView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
secondView.heightAnchor.constraint(equalTo: scrollView.heightAnchor),
// constrain firstView Leading and Top to scrollView contentLayoutGuide Leading and Top
firstView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
firstView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
// constrain secondView Leading to firstView Trailing
secondView.leadingAnchor.constraint(equalTo: firstView.trailingAnchor),
// constrain secondView Top / Bottom / Trailing Top to scrollView contentLayoutGuide Top / Bottom / Trailing
secondView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
secondView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
secondView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
])
}
}

What is wrong with that addArrangedSubview code?

I'm trying to understand how addArrangedSubview is working so I tried with a dummy project. Here's my VC :
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var stackOutlet: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let newView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
newView.backgroundColor = UIColor.darkGray
let newView2 = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
newView2.backgroundColor = UIColor.blue
stackOutlet.addArrangedSubview(newView)
stackOutlet.addArrangedSubview(newView2)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
When I launch the app, nothing is appearing. I think I have to set some constraints in the stackView?
Ok so for everyone sakes : you need to use this =
mapView.heightAnchor.constraint(equalToConstant: 200).isActive = true
mapView.widthAnchor.constraint(equalToConstant: 200).isActive = true

UISwipeGestureRecognizer not recognized when added to UIView's subviews

I currently have a subclass of UIView which contains numerous subviews. I wish to add a UISwipeGesture to the subviews but unfortunately the swipe gesture is not recognized. I've set userInteractionEnabled = true and direction of the swipe gesture but nothing works.
public class CardStackView: UIView{
public var dataSource = [UIImage]()
private var swipeGuesture: UISwipeGestureRecognizer!
override public func layoutSubviews() {
for img in dataSource{
let view = AppView(image: img, frame: self.frame)
self.addSubview(view)
}
animateSubview()
self.userInteractionEnabled = true
}
func animateSubview(){
for (index, sView) in self.subviews.enumerate() {
swipeGuesture = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGuestureDidSwipeRight(_:)))
swipeGuesture.direction = .Right
sView.addGestureRecognizer(swipeGuesture)
sView.userInteractionEnabled = true
let move: CGFloat = CGFloat(-20 + index * 20)
let opacity = Float(1 - 0.2 * CGFloat(index))
sView.shadowOpacity(opacity).shadowOffset(CGSizeMake(20 - CGFloat(index) * 5, 20 - CGFloat(index) * 5)).shadowRadius(5).moveX(-move).moveY(-move).gravity().shadowColor(UIColor.grayColor()).duration(1)
.completion({
}).animate()
}
}
func swipeGuestureDidSwipeRight(gesture: UISwipeGestureRecognizer) {
print("Swiped right")
let subview = self.subviews[0]
subview.moveX(-60).duration(1).animate()
}
}
Example
class ExampleController: UIViewController {
var stackView: CardStackView!
override func viewDidLoad() {
super.viewDidLoad()
stackView = CardStackView(frame: CGRect(x: 20, y: 80, width: 200, height: 200))
stackView.dataSource = [UIImage(named: "2008")!, UIImage(named: "2008")!]
self.view.addSubview(stackView)
}
}
self.view.bringSubviewToFront(yourSubview)
try this code for all your subviews and if it doesn't work try this in your controller class for your CardStackView.
Try to call setNeedsLayout for stackView:
override func viewDidLoad() {
super.viewDidLoad()
stackView = CardStackView(frame: CGRect(x: 20, y: 80, width: 200, height: 200))
stackView.dataSource = [UIImage(named: "2008")!, UIImage(named: "2008")!]
stackView.setNeedsLayout()
self.view.addSubview(stackView)
}