textview shakes when resizing view - swift

I'm resizing the view that a textview belongs to and the text shakes when the view either gets bigger or gets smaller.
Declaration of said text view:
lazy var textview: UITextView = {
let textView = UITextView()
textView.text = ""
textView.font = .systemFont(ofSize: 12, weight: UIFontWeightMedium)
textView.isScrollEnabled = false
textView.isEditable = false
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textAlignment = .center
textView.textColor = .lightGray
textView.dataDetectorTypes = .link
return textView
}()
I'm resizing the view that it's in to fit the full screen like this
if let window = UIApplication.shared.keyWindow {
let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveLinear, animations: {
self.frame = CGRect(x: 0, y: statusBarHeight, width: window.frame.width, height: window.frame.height - statusBarHeight)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
Upon doing so, the view expands perfectly but the textviews text does a bounce effect that makes the animation look extremely unprofessional... any advice?
Edit: It seems like when I remove the center text alignment option it works fine. How do I make it work with the text center aligned?

edit: I took another look at this and attempted to use the technique based in UIScrollView animation of height and contentOffset "jumps" content from bottom.
Here's a minimal working example with text view with centered text alignment which is working for me!
I'd recommend managing animations either to be all constraint based, or all frame based. I attempted a version where the animation is driven by updating the container view frame but it was starting to take too long to left it at this constraint based approach.
Hope this points you in the right direction :)
import UIKit
class ViewController: UIViewController {
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "testing text view"
textView.textAlignment = .center
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
lazy var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var widthConstraint: NSLayoutConstraint!
var topAnchor: NSLayoutConstraint!
override func viewDidLoad() {
view.backgroundColor = .groupTableViewBackground
// add container view and constraints
view.addSubview(containerView)
containerView.frame = view.bounds.insetBy(dx: 100, dy: 200)
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// keep reference to topAnchor and width as properties to animate
topAnchor = containerView.topAnchor.constraint(lessThanOrEqualTo: view.topAnchor, constant: 100)
widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 300)
topAnchor.isActive = true
widthConstraint.isActive = true
// add text view to container view and set constraints
containerView.addSubview(textView)
textView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
textView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
#IBAction func toggleResize(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
view.layoutIfNeeded()
widthConstraint.constant = sender.isSelected ? view.bounds.width : 300
topAnchor.constant = sender.isSelected ? 20 : 100
// caculate the textView content offset for starting position based on
// expected end position at end of the animation
let xOffset = (textView.bounds.width - widthConstraint.constant) / 2
textView.contentOffset = CGPoint(x: -xOffset, y: textView.contentOffset.y)
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}
}

Related

func is reseting position of pan gesture

The func addBlackView is adding a black view everytime the func is called. The black view is connected a to uiPangesture the problem is evertyime the func addblackview is called the code is reseting the position of wherever the first black has been moved. You can see what is goin on in the gif below. I just want the 1st black view to not move and stay in the same position if a new black view is Called.
import UIKit
class ViewController: UIViewController {
var image1Width2: NSLayoutConstraint!
var iHieght: NSLayoutConstraint!
var currentView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(currentView)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
button.widthAnchor.constraint(equalToConstant: 100),
button.heightAnchor.constraint(equalToConstant: 80),
])
button.addTarget(self,action: #selector(addBlackView),for: .touchUpInside)
}
let slider:UISlider = {
let slider = UISlider(frame: .zero)
return slider
}()
private lazy var button: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.setTitleColor(.white, for: .normal)
button.setTitle("add", for: .normal)
return button
}()
let blackView: UIView = {
let view = UIView()
view.backgroundColor = .black
return view
}()
var count = 0
#objc
private func addBlackView() {
let newBlackView = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100)) // whatever frame you want
newBlackView.backgroundColor = .orange
self.view.addSubview(newBlackView)
self.currentView = newBlackView
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(moveView(_:)))
newBlackView.addGestureRecognizer(recognizer)
newBlackView.isUserInteractionEnabled = true
image1Width2 = newBlackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.1)
image1Width2.isActive = true
iHieght = newBlackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1)
iHieght.isActive = true
count += 1
newBlackView.tag = (count)
newBlackView.translatesAutoresizingMaskIntoConstraints = false
newBlackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
newBlackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
#objc private func moveView(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
let translation = recognizer.translation(in: self.view)
recognizer.view!.center = .init(x: recognizer.view!.center.x + translation.x,
y: recognizer.view!.center.y + translation.y)
recognizer.setTranslation(.zero, in: self.view)
default:
break
}
}
}
They always go back to the centre because you have constrained the black (orange) views to the centre:
newBlackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
newBlackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
You shouldn't even be able to drag any of the views at all, but I guess setting center ignores constraints for some reason. Anyway, when you add a new view, UIKit calls view.setNeedsLayout/layoutIfNeeded somewhere down the line, and this causes all the views to realise "oh wait, I'm supposed to be constrained to the centre!" and snap back. :D
If you want to keep using constraints, try storing the centre X and Y constraints of all the views in an array:
var centerXConstraints: [NSLayoutConstraint] = []
var centerYConstraints: [NSLayoutConstraint] = []
And append to them when you add a new view:
let yConstraint = newBlackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let xConstraint = newBlackView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
xConstraint.isActive = true
yConstraint.isActive = true
centerXConstraints.append(xConstraint)
centerYConstraints.append(yConstraint)
Then, rather than changing the center, change the constant of these constraints:
let centerXConstraint = centerXConstraints[recognizer.view!.tag - 1]
let centerYConstraint = centerYConstraints[recognizer.view!.tag - 1]
centerXConstraint.constant += translation.x
centerYConstraint.constant += translation.y
Alternatively, and this is what I would do, just remove all your constraints, and translateAutoresizingMaskIntoConstraints = true. This way you can freely set your center.

Adding constraints programmatically leads to unexpected behavior

I'm playing around with constraints, trying to learn how they work and to try and learn how to build a UI without IB, and I'm not getting the results I anticipated. In the code below, if I comment out the constraints at the end, I can see the purple view. If I uncomment them, all I get is an empty window where I would expect the view to be pinned to the left, topped right edges of the main view.
I've also tried doing a similar thing with the centerX and centerY properties to try and center the view in the middle of the window, again I get an empty window when those are activated.
Any help appreciated!
import Cocoa
class ViewController : NSViewController {
override func loadView() {
// NSMakeRect parameters do nothing?
let view = NSView(frame: NSMakeRect(0,0,400,2000))
view.wantsLayer = true
view.layer?.borderWidth = 5
view.layer?.borderColor = NSColor.gray.cgColor
self.view = view
}
override func viewWillAppear() {
super.viewWillAppear()
// Do any additional setup after loading the view.
createMasterView()
}
func makeView() -> NSView {
let view = NSView()
view.translatesAutoresizingMaskIntoConstraints = false
view.setFrameSize(NSSize(width: 600, height: 100))
view.wantsLayer = true
view.heightAnchor.constraint(equalToConstant: 1000)
return view
}
func createMasterView() {
let mainView = self.view
let headerView = makeView()
headerView.layer?.backgroundColor = NSColor.purple.cgColor
headerView.layer?.borderWidth = 5
headerView.layer?.borderColor = CGColor.black
mainView.translatesAutoresizingMaskIntoConstraints = false
mainView.addSubview(headerView)
headerView.topAnchor.constraint(equalTo: mainView.topAnchor).isActive = true
headerView.leadingAnchor.constraint(equalTo: mainView.leadingAnchor).isActive = true
headerView.trailingAnchor.constraint(equalTo: mainView.trailingAnchor).isActive = true
}
}
Edit: I'm also including my AppDelegate code below. I'm still very new to all this so the code is stuff I've cobbled together from various tutorials.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var windowController: NSWindowController!
var window: NSWindow!
var windowTitle = "Test App"
var customBGColor = NSColor(red: 1, green: 1, blue: 1, alpha: 1)
func applicationDidFinishLaunching(_ aNotification: Notification) {
createMainWindow()
}
func createMainWindow() {
window = NSWindow()
// window.alphaValue = 0.5
window.backgroundColor = customBGColor
window.title = windowTitle
window.styleMask = NSWindow.StyleMask(rawValue: 0xf)
window.backingType = .buffered
window.contentViewController = ViewController()
window.setFrame(NSRect(x: 700, y: 200, width: 1920, height: 1080), display: false)
windowController = NSWindowController()
windowController.contentViewController = window.contentViewController
windowController.window = window
windowController.showWindow(self)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
view.setFrameSize(NSSize(width: 600, height: 100))
You are overriding the height shortly afterwards with the heightAnchor.
Try setting width as well with an anchor
With auto layout, you don't touch the frame property of the view. When working programmatically, however, you have to with the view itself, but after that, all subviews can be sized and positioned using constraints. For clarity, I got rid of makeView():
func createMasterView() {
let headerView = NSView() // instantiate
headerView.layer?.backgroundColor = NSColor.purple.cgColor // style
headerView.layer?.borderWidth = 5
headerView.layer?.borderColor = CGColor.black
headerView.translatesAutoresizingMaskIntoConstraints = false // disable mask translating
view.addSubview(headerView) // add as a subview
// then configure constraints
// one possible setup
headerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// another possible setup
headerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
headerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
headerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
// another possible setup
headerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
headerView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
headerView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -50).isActive = true
headerView.heightAnchor.constraint(equalTo: view.heightAnchor, constant: -50).isActive = true
}

UIView Mask Is Causing View To Move Positions (AutoLayout)

In my app, I have a UILabel, which I am using to mask a UIView. I am using AutoLayout throughout the app, and am finding that when setting the mask of my label, its position suddenly changes.
Here is my code when adding my label;
// Label
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello!"
label.font = UIFont.systemFont(ofSize: 50.0)
label.textColor = UIColor.white
view.addSubview(label)
// Constraints
label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
This produces the result. When adding the mask, however, via the following code;
// Mask
let mask = UIView()
mask.translatesAutoresizingMaskIntoConstraints = false
mask.backgroundColor = UIColor.blue
mask.mask = label
view.addSubview(mask)
// Constraints
mask.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
mask.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
mask.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
mask.heightAnchor.constraint(equalTo: self.view.heightAnchor).isActive = true
My label ends up repositioning itself, and I am seeking to have the text stay in position at the perfect center.
You cannot use Auto Layout on the view that is used as a mask. That view it lives outside the normal view hierarchy. You do not add it to the view hierarchy by calling addSubview(_:), you only add it as a mask by setting it as the mask property of another view.
Because of that you have to set the label's frame directly to center your label. You also have to set it again everytime the frame of your masked view changed (e.g. if the user rotates the device). Because of that you have to set the label's frame in viewDidLayoutSubviews()
I tried to make it work by just setting the label's center to the view's center, but that does not work. Somehow the label does not get displayed. I could make it work by explicitly setting the labels size to its intrinsicContentSize. I guess this is because the label is used as a masked and never part of the view hierarchy.
Here is a working example. I took the liberty to change the naming from mask to maskedView to avoid confusion with the mask property ;-)
class ViewController: UIViewController {
var label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
// Label
label.text = "Hello!"
label.font = UIFont.systemFont(ofSize: 50.0)
label.textColor = UIColor.white
// Mask
let maskedView = UIView()
maskedView.translatesAutoresizingMaskIntoConstraints = false
maskedView.backgroundColor = UIColor.blue
maskedView.mask = label
view.addSubview(maskedView)
// Constraints
maskedView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
maskedView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
maskedView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
maskedView.heightAnchor.constraint(equalTo: self.view.heightAnchor).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let labelHeight = label.intrinsicContentSize.height
let labelWidth = label.intrinsicContentSize.width
label.frame = CGRect(
x: view.center.x - labelWidth / 2,
y: view.center.y - labelHeight / 2,
width: labelWidth,
height: labelHeight
)
}
}
You could make the code inside viewDidLayoutSubviews() a bit shorter by setting label.textAlignment = .center
Then this is enough:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
label.frame = view.frame
}

Cannot see Buttons in UIScrollView

I was making a list in the form of scrollview in swift where the view consists of various types such as labels, button etc.
However when i added the button to the subview, they were not displayed although all other labels etc were displayed. I also tried messing around in the constraints and anchors.
On the other hand when i added the same button to self.view.addsubview instead of scrollview.addsubview, they were displayed just not scrolling since not a part of the scrollview anymore.
I even removed the label to make sure that the buttons were not being overlapped(didn't work either)
I also tried to see the code in "code debug hierarchy " (3D mode), i couldn't see the button there either even though i had added it
Below is my code with an example of label, scrollview and button. It be great if anyone could provide any insights.....thanks either way....
................scrollview..........................
var editInfoView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.contentSize.height = 700
view.backgroundColor = tableBackGroundColor
view.frame = CGRect(x: 0, y: 220, width: 375, height: 400)
return view
}()
.......................label...................
vehicleNumberLabel.translatesAutoresizingMaskIntoConstraints = false
vehicleNumberLabel.textColor = .white
vehicleNumberLabel.text = "Vehicle Number"
vehicleNumberLabel.textAlignment = .left
editInfoView.addSubview(vehicleNumberLabel)
vehicleNumberLabel.leftAnchor.constraint(equalTo: editInfoView.leftAnchor).isActive = true
vehicleNumberLabel.topAnchor.constraint(equalTo: editInfoView.topAnchor, constant: 100).isActive = true
vehicleNumberLabel.widthAnchor.constraint(equalToConstant: 160).isActive = true
vehicleNumberLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
.....................button................................
vehicleNumberButton.translatesAutoresizingMaskIntoConstraints = false
vehicleNumberButton.setTitleColor(tableTextColor, for: .normal)
vehicleNumberButton.setTitle("Vehicle Number", for: .normal)
vehicleNumberButton.tintColor = tableTextColor
vehicleNumberButton.backgroundColor = tableTextColor
editInfoView.addSubview(vehicleNumberButton)
vehicleNumberButton.rightAnchor.constraint(equalTo: editInfoView.rightAnchor).isActive = true
vehicleNumberButton.topAnchor.constraint(equalTo: editInfoView.topAnchor, constant: 400).isActive = true
vehicleNumberButton.widthAnchor.constraint(equalToConstant: 600).isActive = true
vehicleNumberButton.heightAnchor.constraint(equalToConstant: 255).isActive = true
Although I cannot determine the root cause of your issue with the code and explanation provided I suspect the frame of your UIScrollView() is zero after viewDidAppear(_:) adding subviews to a CGRect.zero can cause some strange behavior with the layout engine. When we create constraints programmatically we are creating a combination of inequalities, equalities, and priorities to restrict the view to a particular frame. If a the value of these constraint equations is incorrect it changes how your relating views appear. Its good practice to avoid the use of leftAnchor and rightAnchor as well, because views may flip direction based on language (writing direction) and user settings.
ViewController.swift
import UIKit
class ViewController: UIViewController {
var editInfoScrollView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isUserInteractionEnabled = true
view.alwaysBounceVertical = true
view.isScrollEnabled = true
view.contentSize.height = 700
view.backgroundColor = UIColor.red.withAlphaComponent(0.3)
// Does nothing because `translatesAutoresizingMaskIntoConstraints = false`
// Instead, set the content size after activating constraints in viewDidAppear
//view.frame = CGRect(x: 0, y: 220, width: 375, height: 400)
return view
}()
var vehicleNumberLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
label.text = "Vehicle Number"
label.textAlignment = .left
return label
}()
lazy var vehicleNumberButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tag = 1
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("Go to Vehicle", for: .normal)
button.tintColor = UIColor.white
button.backgroundColor = UIColor.clear
button.layer.cornerRadius = 30 // about half of button.frame.height
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 2.0
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(handelButtons(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.setupSubviews()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.editInfoScrollView.contentSize = CGSize(width: self.view.frame.width, height: 700.0)
}
func setupSubviews() {
self.view.addSubview(editInfoScrollView)
editInfoScrollView.addSubview(vehicleNumberLabel)
editInfoScrollView.addSubview(vehicleNumberButton)
let spacing: CGFloat = 12.0
let constraints:[NSLayoutConstraint] = [
editInfoScrollView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
editInfoScrollView.heightAnchor.constraint(equalToConstant: 400.0),
editInfoScrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
editInfoScrollView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 220.0),
vehicleNumberLabel.leadingAnchor.constraint(equalTo: editInfoScrollView.leadingAnchor, constant: spacing),
vehicleNumberLabel.trailingAnchor.constraint(equalTo: editInfoScrollView.trailingAnchor, constant: -spacing),
vehicleNumberLabel.centerXAnchor.constraint(equalTo: editInfoScrollView.centerXAnchor, constant: -50),
vehicleNumberLabel.heightAnchor.constraint(equalToConstant: 75.0),
vehicleNumberButton.widthAnchor.constraint(equalTo: editInfoScrollView.widthAnchor, multiplier: 0.66),
vehicleNumberButton.heightAnchor.constraint(equalToConstant: 65.0),
vehicleNumberButton.topAnchor.constraint(equalTo: vehicleNumberLabel.bottomAnchor, constant: spacing),
vehicleNumberButton.centerXAnchor.constraint(equalTo: editInfoScrollView.centerXAnchor),
]
NSLayoutConstraint.activate(constraints)
}
#objc func handelButtons(_ sender: UIButton) {
switch sender.tag {
case 0:
print("Default button tag")
case 1:
print("vehicleNumberButton was tapped")
default:
print("Nothing here yet")
}
}
}

Removing a UIView from UIStackView Changes its Size

I am trying to build a drag drop action and if I drag a view from stack view and drop it to somewhere and remove the view with "removeArrangedSubview" it changes the dragged items size and makes it bigger. I am using this piece of code to resize dropped item.
sender.view!.frame = CGRectMake(sender.view!.frame.origin.x, sender.view!.frame.origin.y, sender.view!.frame.width * 0.5, sender.view!.frame.height * 0.5)
Here is the image for the comparison.
You are setting the frame of the view you are moving but when you place it in a stack view the stack view will reset it based on the stack view's constraints and the intrinsic content size of the view that you are adding to it. The reason it is not doing that when you don't call removeArrangedSubview is because it has not triggered an auto layout pass. In general, when you're using auto layout you shouldn't set views frames but rather update constraints intrinsicContentSize and request auto layout updates.
You should play around with the distribution property of your stack view, the constraints you have placed on it, as well as the intrinsicContentSize of the views you are adding to it until you get your desired result.
For example:
class ViewController: UIViewController
{
let stackView = UIStackView()
let otherStackView = UIStackView()
override func viewDidLoad()
{
self.view.backgroundColor = UIColor.whiteColor()
stackView.alignment = .Center
stackView.axis = .Horizontal
stackView.spacing = 10.0
stackView.distribution = .FillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
otherStackView.alignment = .Center
otherStackView.axis = .Horizontal
otherStackView.spacing = 10.0
otherStackView.distribution = .FillEqually
otherStackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
self.view.addSubview(otherStackView)
stackView.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor).active = true
stackView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor).active = true
stackView.heightAnchor.constraintEqualToConstant(150).active = true
otherStackView.topAnchor.constraintEqualToAnchor(self.view.topAnchor).active = true
otherStackView.widthAnchor.constraintGreaterThanOrEqualToConstant(50).active = true
otherStackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
otherStackView.heightAnchor.constraintEqualToConstant(150).active = true
self.addSubviews()
}
func addSubviews()
{
for _ in 0 ..< 5
{
let view = View(frame: CGRect(x: 0.0, y: 0.0, width: 100, height: 100))
view.userInteractionEnabled = true
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panHandler(_:)))
view.addGestureRecognizer(panGesture)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.redColor()
self.stackView.addArrangedSubview(view)
}
}
func panHandler(sender: UIPanGestureRecognizer)
{
guard let view = sender.view else{ return }
switch sender.state {
case .Began:
self.stackView.removeArrangedSubview(view)
self.view.addSubview(view)
let location = sender.locationInView(self.view)
view.center = location
case .Changed:
view.center.x += sender.translationInView(self.view).x
view.center.y += sender.translationInView(self.view).y
case .Ended:
if CGRectContainsPoint(self.otherStackView.frame, view.center)
{
self.otherStackView.addArrangedSubview(view)
self.otherStackView.layoutIfNeeded()
}
else
{
self.stackView.addArrangedSubview(view)
self.otherStackView.layoutIfNeeded()
}
default:
self.stackView.addArrangedSubview(view)
self.otherStackView.layoutIfNeeded()
}
sender.setTranslation(CGPointZero, inView: self.view)
}
}
class View: UIView
{
override func intrinsicContentSize() -> CGSize
{
return CGSize(width: 100, height: 100)
}
}
The above view controller and UIView subclass does something similar to what you're looking for, but it's not possible to tell from your question. Notice that pinning a stack view to the edges of its super view (like stackView) causes it to stretch subviews to fill all the available space while allowing the stack view to dynamically size based on the intrinsic size of its subviews (like otherStackView) does not.
Update
If you don't want to add the view to another stack view but you want them to retain their frame, you should remove any constraints the view's have on them and then set their translatesAutoresizingMaskIntoConstraints property to true. For example:
func panHandler(sender: UIPanGestureRecognizer)
{
guard let view = sender.view else{ return }
switch sender.state {
case .Began:
self.stackView.removeArrangedSubview(view)
self.view.addSubview(view)
let location = sender.locationInView(self.view)
view.center = location
case .Changed:
view.center.x += sender.translationInView(self.view).x
view.center.y += sender.translationInView(self.view).y
default:
view.removeConstraints(view.constraints)
view.translatesAutoresizingMaskIntoConstraints = true
// You can now set the view's frame or position however you want
view.center.x += sender.translationInView(self.view).x
view.center.y += sender.translationInView(self.view).y
}
sender.setTranslation(CGPointZero, inView: self.view)
}