use slider to change nslayout constraint size - swift

I want to use the slider slizer to change the size of the uiview pic. With Nslayout constraints it creates a fixed object so I added to constraints as vars. So I assume in the increase func the startCon will become deactivated and the changeCon will become activated. I have not used constraints as var in a while and dont know what to do. But I want to position pic using nslayout constraints and then change the width and height together using the slider.
import UIKit
class ViewController: UIViewController{
var pic = UIView();var slizer = UISlider()
var startCon: NSLayoutConstraint!
var changeCon: NSLayoutConstraint!
override func viewDidLoad() {
[pic,slizer].forEach{
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
NSLayoutConstraint.activate([
pic.centerYAnchor.constraint(equalTo: view.centerYAnchor),
pic.centerXAnchor.constraint(equalTo: view.centerXAnchor),
pic.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.3),
pic.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier: 0.3),
slizer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
slizer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
slizer.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.2),
slizer.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier: 1),
])
pic.backgroundColor = .orange
slizer.addTarget(self, action: #selector(increase), for: .touchDown)
}
#objc func increase(){
//increase decrease size of pic
}
}

It's impossible to modify an activated constraints. You have to disable your contraints and set a new one.
var pic = UIView()
var slizer = UISlider()
var slidermultiplier: CGFloat = 0.3
var widthConstraints: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
widthConstraints = pic.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: slidermultiplier)
[pic,slizer].forEach{
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
NSLayoutConstraint.activate([
pic.centerYAnchor.constraint(equalTo: view.centerYAnchor),
pic.centerXAnchor.constraint(equalTo: view.centerXAnchor),
pic.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.3),
widthConstraints!,
slizer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
slizer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
slizer.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.2),
slizer.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier: 1),
])
pic.backgroundColor = .orange
// changed to .valueChanged
slizer.addTarget(self, action: #selector(increase), for: .valueChanged)
}
#objc func increase() {
slidermultiplier = CGFloat(slizer.value)
widthConstraints?.isActive = false
widthConstraints = pic.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: slidermultiplier)
widthConstraints?.isActive = true
}
and you can do the same with the height.
You can also set min and max value to your UISlider to have a Range of multiplier.

You can hold properties that are responsible for image size, change those values in slider action. For example:
change value pic.heightAnchor.constraint's multiplier in method increase action.

Related

slider only increasing imageview size not decreasing size

My swift code code below uses a slider to increase the size of the imageview. What I would like to do is when the slider moves from less than 0.5 the imageview gets smaller. If the value of the slider is equal to or greater than 0.5 the size of the imageveiw increases. Right now the slider value is set at 0.5
import UIKit
class ViewController: UIViewController {
var image1Width2: NSLayoutConstraint!
var image1Height2: NSLayoutConstraint!
var slider = UISlider()
var blueMove = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
slider.value = 0.5
blueMove.backgroundColor = .blue
blueMove.isUserInteractionEnabled = true
blueMove.backgroundColor = .blue
[blueMove,slider].forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
//image12
image1Width2 = blueMove.widthAnchor.constraint(equalTo: view.widthAnchor ,multiplier: 0.25)
image1Height2 = blueMove.heightAnchor.constraint(equalTo: view.heightAnchor ,multiplier: 0.20)
let percent1 = self.view.frame.height * 0.1
let percent2 = self.view.frame.width * 0.2
NSLayoutConstraint.activate([
blueMove.topAnchor.constraint(equalTo: view.topAnchor, constant : percent1),
image1Width2,
image1Height2,
blueMove.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :percent2)
])
slider.addTarget(self, action: #selector(hhh), for: .allEvents)
}
override func viewDidLayoutSubviews() {
NSLayoutConstraint.activate ([
slider.bottomAnchor.constraint(equalTo: view.bottomAnchor),
slider.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.20, constant: 0),
slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.20, constant: 0),
slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
])
}
#objc func hhh() {
image1Width2.constant = CGFloat(slider.value) * view.frame.size.width * 0.25
image1Height2.constant = CGFloat(slider.value) * view.frame.size.height * 0.25
}
}
In viewDidLoad() you are defining the constraints for your "blue view" based on the slider having a value of 0.0 ... but you are giving your slider an initial value of 0.5.
So when you first touch the slider, your blue view's size "jumps" to a 0.5 adjusted size.
Add a call to hhh() at the end of viewDidLoad():
slider.addTarget(self, action: #selector(hhh), for: .allEvents)
// change blue view based on initial slider value of 0.5
hhh()
Now you'll see the blue view get smaller when you drag the slider to the left, and larger when you drag it to the right.

use slider to to change width and height of NSLAYOUT constraint imageview

My swift's code goal is to use a slider to increase decrease the size of a imageview. The imageview pic should be able to increase decrease size depending on what the value of slide slider is. When the func is called the origanal constraints must be de activated so the width and height can be changed. I heard that it is possible to use animation blocks.
import UIKit
class ViewController: UIViewController {
var pic = UIImageView()
var slide = UISlider()
var currentView: UIView?
var oldCons = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
slide.backgroundColor = .systemPink
pic.backgroundColor = .systemGreen
[pic,slide].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
NSLayoutConstraint.activate([
slide.heightAnchor.constraint(equalTo: view.heightAnchor ,multiplier: 0.25),
slide.widthAnchor.constraint(equalTo: view.widthAnchor ,multiplier: 0.25),
slide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),
slide.topAnchor.constraint(equalTo: pic.bottomAnchor, constant :0),
])
oldCons = [
pic.topAnchor.constraint(equalTo: view.topAnchor, constant :0),
pic.heightAnchor.constraint(equalTo: view.heightAnchor ,multiplier: 0.25),
pic.widthAnchor.constraint(equalTo: view.widthAnchor ,multiplier: 0.25),
pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),
]
NSLayoutConstraint.activate(oldCons)
slide.addTarget(self, action: #selector(change), for: .allTouchEvents)
}
#objc func change(){
NSLayoutConstraint.deactivate(oldCons)
pic.bounds.size.width = CGFloat(slide.value)
pic.bounds.size.height = CGFloat(slide.value)
}
}
You don't need to deactivate your constraints. You just need to save them and update the constants as the slider slides.
class ViewController: UIViewController {
var pic = UIImageView()
var slide = UISlider()
var currentView: UIView?
var picWidth: NSLayoutConstraint!
var picHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// ...
picWidth = pic.widthAnchor.constraint(equalTo: view.widthAnchor ,multiplier: 0.25)
picHeight = pic.heightAnchor.constraint(equalTo: view.heightAnchor ,multiplier: 0.25)
NSLayoutConstraint.activate([
slide.heightAnchor.constraint(equalTo: view.heightAnchor ,multiplier: 0.25),
slide.widthAnchor.constraint(equalTo: view.widthAnchor ,multiplier: 0.25),
slide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),
slide.topAnchor.constraint(equalTo: pic.bottomAnchor, constant :0),
pic.topAnchor.constraint(equalTo: view.topAnchor, constant :0),
picWidth,
picHeight,
pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),
])
// ...
}
#objc func change(){
picWidth.constant = CGFloat(slide.value) * view.frame.size.width * 0.25
picHeight.constant = CGFloat(slide.value) * view.frame.size.height * 0.25
}
}

add 2 image views to a uiscrollview func every time it is called

My swift code below goal is to add 2 image views every time. Ass you can in the gif below only one image view is being added. I just need to add 2 image views. The image views are lastImage and lastImage2. you can see only lastImage is being shown. It seems I can only add 1 imageview when func didclickadd is called.
import UIKit
class ViewController: UIViewController {
fileprivate var lastImage:UIImageView?
fileprivate var lastImage2:UIImageView?
fileprivate var mainViewBootom:NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupVIew()
}
override func viewDidAppear(_ animated: Bool) {
scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
view.layoutIfNeeded()
}
//MARK: Components
let scrollView:UIScrollView = {
let sv = UIScrollView(frame: .zero)
return sv
}()
let mainView:UIView = {
let uv = UIView()
uv.backgroundColor = .white
return uv
}()
let btnAdd:UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Add", for: .normal)
return btn
}()
let textField:UITextField = {
let jake = UITextField()
return jake
}()
//MARK: Setup UI
func setupVIew() {
view.addSubview(scrollView)
view.addSubview(btnAdd)
view.addSubview(textField)
scrollView.translatesAutoresizingMaskIntoConstraints = false
btnAdd.translatesAutoresizingMaskIntoConstraints = false
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
btnAdd.centerXAnchor.constraint(equalTo: view.centerXAnchor),
btnAdd.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
btnAdd.widthAnchor.constraint(equalToConstant: 100),
btnAdd.heightAnchor.constraint(equalToConstant: 45),
//
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 25),
textField.widthAnchor.constraint(equalToConstant: 100),
textField.heightAnchor.constraint(equalToConstant: 45),
//
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: btnAdd.topAnchor , constant: -12),
])
btnAdd.addTarget(self, action: #selector(didClickedAdd), for: .touchUpInside)
scrollView.addSubview(mainView)
mainView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mainView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mainView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mainView.topAnchor.constraint(equalTo: scrollView.topAnchor),
])
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
imgView.backgroundColor = .red
mainView.addSubview(imgView)
let samsam = UIImageView(frame: CGRect(x: 0, y: 200, width: 40, height: 100))
samsam.backgroundColor = .blue
mainView.addSubview(samsam)
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imgView.widthAnchor.constraint(equalToConstant: 150).isActive = true
imgView.heightAnchor.constraint(equalToConstant: 100).isActive = true
samsam.translatesAutoresizingMaskIntoConstraints = false
samsam.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
samsam.topAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
samsam.widthAnchor.constraint(equalToConstant: 75).isActive = true
samsam.heightAnchor.constraint(equalToConstant: 100).isActive = true
if lastImage != nil {
imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 20).isActive = true
}else{
imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 12).isActive = true
}
lastImage = samsam
mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 12)
mainViewBootom!.isActive = true
}
#objc func didClickedAdd(){
let imgView = UIImageView(frame: CGRect(x: 20, y: 0, width: 30, height: 20))
imgView.backgroundColor = .orange
mainView.addSubview(imgView)
let ss = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 50))
imgView.backgroundColor = .green
mainView.addSubview(ss)
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
imgView.heightAnchor.constraint(equalToConstant: 60).isActive = true
ss.translatesAutoresizingMaskIntoConstraints = false
ss.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = false
ss.widthAnchor.constraint(equalToConstant: 80).isActive = true
ss.heightAnchor.constraint(equalToConstant: 90).isActive = true
if lastImage != nil {
ss.topAnchor.constraint(equalTo: imgView.topAnchor , constant: 20).isActive = true
imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 50).isActive = true
}else{
imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 10).isActive = true
ss.bottomAnchor.constraint(equalTo: imgView.bottomAnchor , constant: 25).isActive = true
}
lastImage = imgView
lastImage2 = ss
mainView.removeConstraint(mainViewBootom!)
mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage2!.bottomAnchor , constant: 40)
mainViewBootom!.isActive = true
view.layoutIfNeeded()
scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
view.layoutIfNeeded()
}
}
Couple notes...
With proper constraint setup, auto-layout handles the UIScrollView content size all by itself. No need to ever set scrollView.contentSize = ...
You have several instances of adding a subview (image view) to your mainView, which is a subview of your scroll view, but then you add constraints from that subview to your controller's view. Make sure you are constraining elements to the proper other elements.
Here's your code, with commented changes:
class BenViewController: UIViewController {
fileprivate var lastImage:UIImageView?
// 1) don't need this
// fileprivate var lastImage2:UIImageView?
fileprivate var mainViewBootom:NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupVIew()
}
// 2) don't need this
// override func viewDidAppear(_ animated: Bool) {
// scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
// view.layoutIfNeeded()
// }
//MARK: Components
let scrollView:UIScrollView = {
let sv = UIScrollView(frame: .zero)
return sv
}()
let mainView:UIView = {
let uv = UIView()
uv.backgroundColor = .white
return uv
}()
let btnAdd:UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Add", for: .normal)
return btn
}()
let textField:UITextField = {
let jake = UITextField()
return jake
}()
//MARK: Setup UI
func setupVIew() {
view.addSubview(scrollView)
view.addSubview(btnAdd)
view.addSubview(textField)
scrollView.translatesAutoresizingMaskIntoConstraints = false
btnAdd.translatesAutoresizingMaskIntoConstraints = false
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
btnAdd.centerXAnchor.constraint(equalTo: view.centerXAnchor),
btnAdd.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
btnAdd.widthAnchor.constraint(equalToConstant: 100),
btnAdd.heightAnchor.constraint(equalToConstant: 45),
//
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 25),
textField.widthAnchor.constraint(equalToConstant: 100),
textField.heightAnchor.constraint(equalToConstant: 45),
//
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: btnAdd.topAnchor , constant: -12),
])
btnAdd.addTarget(self, action: #selector(didClickedAdd), for: .touchUpInside)
scrollView.addSubview(mainView)
mainView.translatesAutoresizingMaskIntoConstraints = false
// 3) change this:
// NSLayoutConstraint.activate([
// mainView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// mainView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// mainView.topAnchor.constraint(equalTo: scrollView.topAnchor),
// ])
//
// to this
NSLayoutConstraint.activate([
mainView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
mainView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
mainView.topAnchor.constraint(equalTo: scrollView.topAnchor),
mainView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
mainView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
])
// end of change 3)
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
imgView.backgroundColor = .red
mainView.addSubview(imgView)
let samsam = UIImageView(frame: CGRect(x: 0, y: 200, width: 40, height: 100))
samsam.backgroundColor = .blue
mainView.addSubview(samsam)
imgView.translatesAutoresizingMaskIntoConstraints = false
// 4) change view.centerXAnchor to mainView.centerXAnchor
// imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imgView.centerXAnchor.constraint(equalTo: mainView.centerXAnchor).isActive = true
imgView.widthAnchor.constraint(equalToConstant: 150).isActive = true
imgView.heightAnchor.constraint(equalToConstant: 100).isActive = true
samsam.translatesAutoresizingMaskIntoConstraints = false
// 5) change view.centerXAnchor to mainView.centerXAnchor
// samsam.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
samsam.centerXAnchor.constraint(equalTo: mainView.centerXAnchor).isActive = true
samsam.topAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
samsam.widthAnchor.constraint(equalToConstant: 75).isActive = true
samsam.heightAnchor.constraint(equalToConstant: 100).isActive = true
if lastImage != nil {
imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 20).isActive = true
}else{
imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 12).isActive = true
}
lastImage = samsam
mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 12)
mainViewBootom!.isActive = true
}
#objc func didClickedAdd(){
let imgView = UIImageView(frame: CGRect(x: 20, y: 0, width: 30, height: 20))
imgView.backgroundColor = .orange
mainView.addSubview(imgView)
let ss = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 50))
// 6) typo or copy/paste mistake
// imgView.backgroundColor = .green
ss.backgroundColor = .green
mainView.addSubview(ss)
imgView.translatesAutoresizingMaskIntoConstraints = false
// 7) change view.centerXAnchor to mainView.centerXAnchor
// imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imgView.centerXAnchor.constraint(equalTo: mainView.centerXAnchor).isActive = true
imgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
imgView.heightAnchor.constraint(equalToConstant: 60).isActive = true
ss.translatesAutoresizingMaskIntoConstraints = false
// 8) change view.leadingAnchor to mainView.leadingAnchor
// ss.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = false
ss.leadingAnchor.constraint(equalTo: mainView.leadingAnchor).isActive = false
ss.widthAnchor.constraint(equalToConstant: 80).isActive = true
ss.heightAnchor.constraint(equalToConstant: 90).isActive = true
// 9) always need to do this ... but did you mean imgView.bottomAnchor?
ss.topAnchor.constraint(equalTo: imgView.topAnchor , constant: 20).isActive = true
if lastImage != nil {
// 9a) instead of only here
//ss.topAnchor.constraint(equalTo: imgView.topAnchor , constant: 20).isActive = true
imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 50).isActive = true
}else{
imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 10).isActive = true
}
// 10) always need to do this
// deactivate bottom constraint
mainViewBootom?.isActive = false
lastImage = ss
mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage!.bottomAnchor, constant: 40)
mainViewBootom?.isActive = true
// 11) don't need any of this
// lastImage = imgView
// lastImage2 = ss
// mainView.removeConstraint(mainViewBootom!)
//
//
// mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage2!.bottomAnchor , constant: 40)
//
//
//
//
// mainViewBootom!.isActive = true
// view.layoutIfNeeded()
//
// scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
// view.layoutIfNeeded()
}
}
Use Xcode’s “view debugger” (the button is circled in red in my screen snapshot below) and you’ll see what’s going on:
Your ss view has no background color. Note, that when you created that view, you accidentally reset the imgView background color a second time rather than setting the ss.backgroundColor.
Fix that and you’ll see your both imgView and ss:
The view debugger is your best friend when trying to diagnose issues like this. Now, obviously, the green view probably isn’t where you intended it, but you should now be able to see it and diagnose that issue very easily.
All of this having been said, a few observations:
You’re making life much harder than you need to. If you just set the constraints for the scroll view and a stack view within that scroll view, you then only need to add an arranged subview. For example:
#objc func didTapButton(_ sender: UIButton) {
stackView.addArrangedSubview(randomView())
stackView.addArrangedSubview(randomView())
}
Note, once the stack view and scroll view have been set up (see below), then you don’t need to mess around with contentSize or constraints for these subviews at all (other than the widthAnchor and heightAnchor). The auto layout engine, combined with the constraints between the stack view and the scroll view, will take care of everything for you.
So, a full working example:
class ViewController: UIViewController {
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.spacing = 10
return stackView
}()
let button: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Add", for: .normal)
button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
}
// MARK: - Actions
extension ViewController {
#objc func didTapButton(_ sender: UIButton) {
stackView.addArrangedSubview(randomView())
stackView.addArrangedSubview(randomView())
}
}
// MARK: - Private utility methods
private extension ViewController {
func configure() {
view.addSubview(scrollView)
view.addSubview(button)
scrollView.addSubview(stackView)
NSLayoutConstraint.activate([
// define frame of `scrollView`
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: button.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// define frame of `button`
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// define contentSize of `scrollView` based upon size of `stackView`
stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
// but define width of `stackView` relative to the _main view_
stackView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
])
button.setContentHuggingPriority(.required, for: .vertical)
}
func randomView() -> UIView {
let widthRange = view.bounds.width * 0.1 ... view.bounds.width * 0.9
let heightRange = view.bounds.width * 0.1 ... view.bounds.width * 0.25
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.widthAnchor.constraint(equalToConstant: .random(in: widthRange)),
view.heightAnchor.constraint(equalToConstant: .random(in: heightRange))
])
view.backgroundColor = UIColor(red: .random(in: 0.25...1), green: .random(in: 0.25...1), blue: .random(in: 0.25...1), alpha: 1)
return view
}
}
Even better, I’d personally set up the scroll view, stack view, button, and all the associated constraints in Interface Builder, and then that hairy configure method in my example goes away completely. It’s fun to learn how to create views programmatically, but in real-world projects, it’s rarely the most productive way to do it. Do programmatic views where needed (e.g. adding arranged subviews to the stack view on the click of a button), but otherwise, for those views that should be there when you first run the app, Interface Builder is worth considering.
E.g. It dramatically reduces the amount of code above, leaving us simply with:
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var stackView: UIStackView!
#IBAction func didTapButton(_ sender: UIButton) {
stackView.addArrangedSubview(randomView())
stackView.addArrangedSubview(randomView())
}
}
// MARK: - Private utility methods
private extension ViewController {
func randomView() -> UIView { ... }
}
Clearly, it takes a while to get used to designing views and configuring constraints in IB, but it’s worth the effort. It distills our code down the the bare essentials.
In your code, you’re setting frames for these image views and then setting translatesAutoresizingMaskIntoConstraints. There’s absolutely no point in setting the frame in that case, because translatesAutoresizingMaskIntoConstraints says “ignore my frame, use constraints instead.”
I’m assuming you’re doing all of this just to become familiar with scroll views, but it’s worth noting that, especially when adding lots of image views, that the scroll view is an inherently inefficient approach.
For example, let’s say you’ve added 100 image views, but you can see only 8 at a time. Do you really want to hold all 100 image views in memory at the same time? No.
But, UITableView, which is a subclass of UIScrollView, takes care of this. You end up only keeping the currently visible image views in memory. It’s a far better approach.
This is especially true when you start using actual UIImage objects, because they require a lot of memory. We get lulled into a sense of security, looking at reasonably sized PNG/JPG assets, but when they’re loaded into memory, they’re uncompressed and require a disproportionate amount of memory.

Entering text into textfield causing uipangesture object to move back to its orginal position

My swift code below has a image view connected to a pangesture. When something is entered into a textfield when after the image view is moved. The image view reverts back to its original position.The gif represents that what is going on. I just don't want the effect of the pangesutre to be nullified after text is entered into the textfield.
LINK TO GITHUB https://github.com/redrock34/sse
import UIKit
class ViewController: UIViewController {
var pic = UIImageView()
let fight = (0..<10).map { _ in UIImageView() }
var textEnter = UITextField()
var g2 = UIPanGestureRecognizer()
var slider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
fight[0].image = UIImage(named: "a.png")
fight.forEach{
$0.isUserInteractionEnabled = true
}
[slider,textEnter].forEach{
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
$0.backgroundColor = .blue
}
slider.backgroundColor = .clear
g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
fight[0].addGestureRecognizer(g2)
pic.backgroundColor = .clear
pic.backgroundColor = .systemGreen
fight.forEach{
$0.backgroundColor = .clear
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
[pic].forEach{
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
// Do any additional setup after loading the view.
NSLayoutConstraint.activate ([
pic.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
pic.topAnchor.constraint(equalTo: fight[0].bottomAnchor, constant : 0),
pic.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.62, constant: 0),
pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
textEnter.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
textEnter.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
textEnter.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1, constant: 0),
textEnter.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
fight[0].trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
fight[0].topAnchor.constraint(equalTo: textEnter.bottomAnchor, constant : 0),
fight[0].heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.10, constant: 0),
fight[0].widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.10, constant: 0),
fight[0].leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
slider.topAnchor.constraint(equalTo: pic.bottomAnchor, constant : 0),
slider.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.08, constant: 0),
slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1, constant: 0),
slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
])
textEnter.textAlignment = .center
self.view.sendSubviewToBack(pic)
}
#objc func g1Method(_ sender: UIPanGestureRecognizer){
let tranistioon = sender.translation(in: self.view)
sender.view!.center = CGPoint(x: sender.view!.center.x + tranistioon.x, y: sender.view!.center.y + tranistioon.y)
sender.setTranslation(CGPoint.zero,in: self.view) }
}
Try creating a class of UIImageVIew and write the following code:
import UIKit
class draggableImage:UIImageView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.red.cgColor
self.isUserInteractionEnabled = true
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first;
let location = touch?.location(in: self.superview);
if(location != nil)
{
self.frame.origin = CGPoint(x: location!.x-self.frame.size.width/2, y: location!.y-self.frame.size.height/2);
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
Don't forget to assign the class draggableImage to the Image View from the Attribute Menu.
You cannot mix explicit frame setting with auto-layout constraints for the same object.
For example, if you:
add a label to the main view
set label.translatesAutoresizingMaskIntoConstraints = false
set constraints for the label
then, via code...
change the .center property of the label
you will see the label move, but when the next UI update happens (you edit a text field, tap a button, rotate the device, etc), auto-layout will reset the frame of the label based on its constraints.
So, in your pan gesture handler, you can either update the .constant values for the fight[0] object (instead of changing its .center), or...
You can leave label.translatesAutoresizingMaskIntoConstraints = true for your fight objects and explicitly set their frames and centers.
Note: in either case, you do not want to constrain any other elements relative to the fight objects, or they will move when you move fight[0].
Here is a modification to your ViewController class (from your GitHub zip), implementing the method of not using auto-layout constraints on your fight objects.
class ViewController: UIViewController {
var pic = UIImageView()
let fight = (0..<10).map { _ in UIImageView() }
var textEnter = UITextField()
var g2 = UIPanGestureRecognizer()
var slider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
fight[0].image = UIImage(named: "a.png")
fight.forEach{
$0.isUserInteractionEnabled = true
}
[slider,textEnter].forEach{
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
$0.backgroundColor = .blue
}
slider.backgroundColor = .clear
g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
fight[0].addGestureRecognizer(g2)
pic.backgroundColor = .systemGreen
fight.forEach{
$0.backgroundColor = .clear
view.addSubview($0)
// do NOT use auto-layout for fight views
//$0.translatesAutoresizingMaskIntoConstraints = false
}
[pic].forEach{
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
// need a non-rendering "spacer" to vertically separate textEnter from Pic
let spacer = UILayoutGuide()
view.addLayoutGuide(spacer)
// NOTE: do NOT constrain any elements relative to fight views
NSLayoutConstraint.activate ([
// constrain textEnter top / leading / trailing to view
textEnter.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
textEnter.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
textEnter.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
// constrain textEnter height to 0.1 * view height
textEnter.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1, constant: 0),
// constrain spacer top to bottom of textEnter
spacer.topAnchor.constraint(equalTo: textEnter.bottomAnchor, constant: 0.0),
// constrain spacer leading to view leading
spacer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
// constrain spacer height to 0.1 * view height
spacer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.10),
// spacer width doesn't matter
spacer.widthAnchor.constraint(equalToConstant: 1.0),
// constrain pic Top to spacer bottom
pic.topAnchor.constraint(equalTo: spacer.bottomAnchor, constant: 0.0),
// constrain pic leading / trailing to view
pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
pic.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
// constrain pic height as you had it
pic.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.62, constant: 0),
// slider constraints
slider.topAnchor.constraint(equalTo: pic.bottomAnchor, constant : 0),
slider.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.08, constant: 0),
slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1, constant: 0),
slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
])
textEnter.textAlignment = .center
self.view.sendSubviewToBack(pic)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// set fight[0] frame *after* textEnter has been laid-out
fight[0].frame.size = CGSize(width: view.frame.width * 0.10, height: view.frame.height * 0.10)
let x = view.frame.origin.x
let y = textEnter.frame.origin.y + textEnter.frame.size.height
fight[0].frame.origin = CGPoint(x: x, y: y)
}
#objc func g1Method(_ sender: UIPanGestureRecognizer){
let tranistioon = sender.translation(in: self.view)
sender.view!.center = CGPoint(x: sender.view!.center.x + tranistioon.x, y: sender.view!.center.y + tranistioon.y)
sender.setTranslation(CGPoint.zero,in: self.view)
}
}
Use this method instead
var existingTransition : CGAffineTransform?
#objc func g1Method(_ sender: UIPanGestureRecognizer){
guard let child = sender.view else{return}
let transitionPoint = sender.translation(in: self.view)
let newTransition = CGAffineTransform(translationX: transitionPoint.x, y: transitionPoint.y)
switch sender.state {
case .ended,.cancelled:// on End
if let existing = existingTransition{
self.existingTransition = newTransition.concatenating(existing)
}else{
self.existingTransition = newTransition
}
default://on change and other states
if let existing = existingTransition{
child.transform = newTransition
.concatenating(existing)
}else{
child.transform = newTransition
}
}
self.view.layoutIfNeeded()
}
Sorry if my code is messy. But I checked some scenaios its working fine.
happy codding

Using UIScrollView with a minimum content top anchor causes visual glitch

I have a scroll view in which I have a content view. I set the scroll view's top anchor to be just above the bottom of an image. I set the content view's top anchor to actually be at the bottom of the image. That way you can pull down on the content and reveal up to the bottom of the image without being able to pull the content view down any further. However, this is causing the content to jump.
Here is my code:
class HomeParallaxScrollViewController: UIViewController {
private let topImageView = UIImageView(image: UIImage(named: "cat"))
private let contentView = UIView()
private let scrollView = UIScrollView()
private let label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
topImageView.contentMode = .scaleAspectFill
contentView.backgroundColor = .white
label.text = "SOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT"
label.textColor = .black
label.numberOfLines = 0
[contentView, label, topImageView, scrollView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
scrollView.addSubview(contentView)
contentView.addSubview(label)
view.addSubview(topImageView)
view.addSubview(scrollView)
NSLayoutConstraint.activate([
topImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
topImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
topImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
topImageView.heightAnchor.constraint(equalToConstant: 200),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
scrollView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: -30),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.topAnchor.constraint(lessThanOrEqualTo: topImageView.bottomAnchor), //This is what's causing the glitch
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.topAnchor.constraint(equalTo: contentView.topAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
}
And here is that is happening:
Trying to add another top constraint -- particularly to an element outside the scroll view -- is a bad idea, and, as you see, won't work. I'm sure you noticed auto-layout conflict messages being generated.
One approach is to implement scrollViewDidScroll delegate func:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// limit drag-down in the scroll view to the overlap size
scrollView.contentOffset.y = max(scrollView.contentOffset.y, -30)
}
As the user drags-down to scroll, it will stop at 30-points.
Here is your example, with slight modifications -- I don't have your .plBackgroundLightGray or .PLSemiboldFont and I added an image load for the top image view -- but this should run as-is:
// conform to UIScrollViewDelegate
class HomeParallaxScrollViewController: UIViewController, UIScrollViewDelegate {
private let topImageView = UIImageView(image: UIImage(named: "cat"))
private let contentView = UIView()
private let scrollView = UIScrollView()
private let label = UILabel()
// this will be the "overlap" of the scroll view and top image view
private var scrollOverlap: CGFloat = 30.0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// limit drag-down in the scroll view to scrollOverlap points
scrollView.contentOffset.y = max(scrollView.contentOffset.y, -scrollOverlap)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray // .plBackgroundLightGray
topImageView.contentMode = .scaleAspectFill
if let img = UIImage(named: "background") {
topImageView.image = img
}
contentView.backgroundColor = .white
label.text = "SOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT"
label.font = UIFont.boldSystemFont(ofSize: 16) // .PLSemiboldFont(size: 16)
label.textColor = .black
label.numberOfLines = 0
[contentView, label, topImageView, scrollView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
scrollView.addSubview(contentView)
contentView.addSubview(label)
view.addSubview(topImageView)
view.addSubview(scrollView)
NSLayoutConstraint.activate([
topImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
topImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
topImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
topImageView.heightAnchor.constraint(equalToConstant: 200),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
scrollView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: scrollOverlap),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// nope, not a good idea -- will cause constraint conflicts
//contentView.topAnchor.constraint(lessThanOrEqualTo: topImageView.bottomAnchor), //This is what's causing the glitch
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.topAnchor.constraint(equalTo: contentView.topAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
// set delegate to self
scrollView.delegate = self
}
}