Auto align vertically when width decreases - ios

I have three buttons aligned like below when the width of the container is 640px:
UIButton 1 <30px> UIButton 2 <30px> UIButton 3
When the width decreases to 320px, I would like to bring the third button to the next line like this:
UIButton 1 <30px> UIButton 2
UIButton 3
Let me know if this can be done using autolayout without creating outlets of the constraints.

You are must overide layoutSubviews and recalculate manualy setFrame's properties for each views.
I found this blog post with example on github

Ended up using a collection view with a left aligned vertical flow layout. It may seem a bit of an overkill but works nice.

Because there wasn't any specification about how/where to create such constraints I'd go this way.
Here is a little NSLayoutConstraint Extension of mine to create constraints fast (for iOS 9 and later).
I'm not using StoryBoards/IB so here is a fast code solution in Swift (2.0):
class SomeViewController: UIViewController {
private let button1 = UIButton(type: UIButtonType.System)
private let button2 = UIButton(type: UIButtonType.System)
private let button3 = UIButton(type: UIButtonType.System)
private var buttonArray: [UIButton] { return [self.button1, self.button2, self.button3] }
private var constraintsForWideWidth: [NSLayoutConstraint] = []
private var constraintsForShortWidth: [NSLayoutConstraint] = []
override func viewDidLoad() {
super.viewDidLoad()
self.button1.translatesAutoresizingMaskIntoConstraints = false
self.button2.translatesAutoresizingMaskIntoConstraints = false
self.button2.translatesAutoresizingMaskIntoConstraints = false
// set your buttons somewhere !!!
self.view.addSubview(self.button1)
self.view.addSubview(self.button2)
self.view.addSubview(self.button3)
self.setupConstraints()
}
private func setupConstraints() {
// points are doubled px on retina display
// setup the width and/or height as you would like
// for this example I'll set them with some static numbers
for button in self.buttonArray {
NSLayoutConstraint.with(button.widthAnchor == 80)
NSLayoutConstraint.with(button.heightAnchor == 20)
}
NSLayoutConstraint.with(self.button2.leftAnchor == self.button1.rightAnchor, constant: 15)
NSLayoutConstraint.with(self.button2.centerYAnchor == self.button1.centerYAnchor)
self.constraintsForWideWidth.append(NSLayoutConstraint.with(self.button3.leftAnchor == self.button2.rightAnchor, constant: 15))
self.constraintsForWideWidth.append(NSLayoutConstraint.with(self.button3.centerYAnchor == self.button2.centerYAnchor))
self.constraintsForShortWidth.append(NSLayoutConstraint.with(self.button3.topAnchor == self.button1.bottomAnchor, active: false))
self.constraintsForShortWidth.append(NSLayoutConstraint.with(self.button3.centerXAnchor == self.button1.centerXAnchor, active: false))
}
func someFunctionThatExecutesWhenTheSizeChanges() {
// deactivate both, because we want less code later
NSLayoutConstraint.deactivateConstraints(self.constraintsForShortWidth)
NSLayoutConstraint.deactivateConstraints(self.constraintsForWideWidth)
// 640 px are 320 pt
if self.view.frame.width == 320 {
NSLayoutConstraint.activateConstraints(self.constraintsForWideWidth)
} else if self.view.frame.width == 160 {
NSLayoutConstraint.activateConstraints(self.constraintsForShortWidth)
}
self.view.layoutIfNeeded()
}
}
Don't be afraid of the code, it is not that much and really not that hard to understand. :) Hope that will help you somehow.

Related

Expanding UITextView with UIButton instead of automatically based on content

I have a UITextView which is embedded in a UIView amongst a number of other UIViews which are all in a UIScrollView (a form essentially). Instead of the textView automatically expanding with the content, I have a button underneath which I would like the user to be able to click and expand/colapse the textView.
Here is what I have:
var textViewIsExpanded: Bool = false {
didSet {
if self.textViewIsExpanded {
self.expandTextViewButton.isSelected = true
guard self.myTextView.contentSize.height > 70 else { return }
self.myTextView.isScrollEnabled = false
self.myTextView.translatesAutoresizingMaskIntoConstraints = true
self.myTextView.sizeThatFits(CGSize(width: self.scrollView.width - 24, height: CGFloat.greatestFiniteMagnitude))
} else {
self.expandTextViewButton.isSelected = false
self.myTextView.isScrollEnabled = true
self.myTextView.translatesAutoresizingMaskIntoConstraints = false
}
}
}
#IBAction func expandTextViewButtonTapped(_ sender: UIButton) {
textViewIsExpanded.toggle()
}
I have tried .sizeToFit() in place of .sizeThatFits(...) which sort of worked but it resized the width along with the height and I am only looking to expand/colapse the height. I'm guessing it is a matter of correctly implementing the CGSize and/or IB constraints but I am not able to land on a solution that works how I want.
First, it's a bad idea to toggle .translatesAutoresizingMaskIntoConstraints between true and false.
What you probably want is to give your text view a Height constraint of 70... connect it to an #IBOutlet... and then toggle .isActive on that constraint.
Second, if you have only one line of text, so the content size height is, maybe, 30, and then you call
textViewIsExpanded = true
your code as-is will set textViewIsExpanded to true but will leave .isScrollEnabled true --- so it won't really be "expanded".
Third, you need to let auto-layout know that you're changing the sizing behavior of the text view by calling:
self.myTextView.invalidateIntrinsicContentSize()
after toggling .isScrollEnabled.
So, add and connect a property for your text view's height constraint:
#IBOutlet var textViewHeightConstraint: NSLayoutConstraint!
and try changing your code to this:
var textViewIsExpanded: Bool = false {
didSet {
if self.textViewIsExpanded {
// if contentSize.height is less-than 71
// reset to false
if self.myTextView.contentSize.height < 71 {
self.textViewIsExpanded = false
return
} else {
self.expandTextViewButton.isSelected = true
self.myTextView.isScrollEnabled = false
self.textViewHeightConstraint.isActive = false
}
} else {
self.expandTextViewButton.isSelected = false
self.myTextView.isScrollEnabled = true
self.textViewHeightConstraint.isActive = true
}
self.myTextView.invalidateIntrinsicContentSize()
}
}

UIStackView & Gestures

I'm trying to get/keep a handle on elements in my UIStackView after I have moved it with a pan gesture.
For example, the elements I am trying to grab a handle on are things like the button tag or the text label text.
The code explained…
I am creating a UIStackView, via the function func createButtonStack(label: String, btnTag: Int) -> UIStackView
It contains a button and a text label.
When the button stack is created, I attach a pan gesture to it so I can move the button around the screen. The following 3 points work.
I get the button stack created
I can press the button and call the fun to print my message.
I can move the button stack.
The issue I have is…
Once I move the button stack the first time and the if statement gesture.type == .ended line is triggered, I lose control of the button stack.
That is, the button presses no longer work nor can I move it around any longer.
Can anyone please help? Thanks
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
let ButtonStack = createButtonStack(label: “Button One”, btnTag: 1)
view.addSubview(ButtonStack)
ButtonStack.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
ButtonStack.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
let panGuesture = UIPanGestureRecognizer(target: self, action: #selector(pan(guesture:)))
ButtonStack.isUserInteractionEnabled = true
ButtonStack.addGestureRecognizer(panGuesture)
}
func createButtonStack(label: String, btnTag: Int) -> UIStackView {
let button = UIButton()
button.setImage( imageLiteral(resourceName: "star-in-circle"), for: .normal)
button.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
button.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
button.contentMode = .scaleAspectFit
button.tag = btnTag
switch btnTag {
case 1:
button.addTarget(self, action: #selector(printMessage), for: .touchUpInside)
case 2:
break
default:
break
}
//Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.green
textLabel.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
textLabel.font = textLabel.font.withSize(15)
textLabel.text = label
textLabel.textAlignment = .center
//Stack View
let buttonStack = UIStackView()
buttonStack.axis = UILayoutConstraintAxis.vertical
buttonStack.distribution = UIStackViewDistribution.equalSpacing
buttonStack.alignment = UIStackViewAlignment.center
buttonStack.spacing = 1.0
buttonStack.addArrangedSubview(button)
buttonStack.addArrangedSubview(textLabel)
buttonStack.translatesAutoresizingMaskIntoConstraints = false
return buttonStack
}
#objc func printMessage() {
print(“Button One was pressed”)
}
#objc func pan(guesture: UIPanGestureRecognizer) {
let translation = guesture.translation(in: self.view)
if let guestureView = guesture.view {
guestureView.center = CGPoint(x: guestureView.center.x + translation.x, y: guestureView.center.y + translation.y)
if guesture.state == .ended {
print("Guesture Center - Ended = \(guestureView.center)")
}
}
guesture.setTranslation(CGPoint.zero, in: self.view)
}
If you're using autolayout on the buttonStack you can't manipulate the guestureView.center centerX directly. You have to work with the constraints to achieve the drag effect.
So instead of ButtonStack.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true you should do something along the lines of:
let centerXConstraint = ButtonStack.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
centerXConstraint.isActive = true
ButtonStack.centerXConstraint = centerXConstraint
To do it like this you should declare a weak property of type NSLayoutConstraint on the ButtonStack class. You can do the same thing for the centerY constraint.
After that in the func pan(guesture: UIPanGestureRecognizer) method you can manipulate the centerXConstraint and centerYConstraint properties directly on the ButtonStack view.
Also, I see you are not setting the translatesAutoresizingMaskIntoConstraints property to false on the ButtonStack. You should do that whenever you are using autolayout programatically.
Thanks to PGDev and marosoaie for their input. Both provided insight for me to figure this one out.
My code worked with just the one button, but my project had three buttons inside a UIStackView.
Once I moved one button, it effectively broke the UIStackView and I lost control over the moved button.
The fix here was to take the three buttons out of the UIStackView and I can now move and control all three buttons without issues.
As for keeping a handle on the button / text field UIStackView, this was achieved by adding a .tag to the UIStackView.
Once I moved the element, the .ended action of the pan could access the .tag and therefore allow me to identify which button stack was moved.
Thanks again for all of the input.

Issue for reposition after delete UIView

I am building Application in iOS swift 3.0. I am creating dynamic UIViews. I need to remove custom view randomly, But currently I am unable to relocate the positions as I get the gaps between the two views and I want remove them, as shows in the picture with code below, Kindly help me with this.
class ViewController: UIViewController {
var myView: subView!
var y : CGFloat!
var tag : Int = 0
#IBOutlet weak var addButton: UIButton!
override func viewDidLoad() {
y = 1
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func cancelbutton(_ sender: UIButton)
{
let selectViewTagValue : Int = sender.tag /// save the selected view tag value
for object in self.view.subviews {
if ((object is subView) && object.tag == selectViewTagValue)
{
object.removeFromSuperview()
}
}
}
#IBAction func buttonAction(_ sender: Any) {
y = y + 110
myView = subView(frame: CGRect(x: 80, y: y, width: 300, height: 100))
myView.tag = tag
myView.actionButton.tag = tag
tag = tag + 1
myView.backgroundColor = UIColor.green
myView.actionButton.addTarget(self, action: (#selector(cancelbutton(_:))), for: UIControlEvents.touchUpInside)
self.view.addSubview(myView)
}
Please Help me this issue... "Thanks in advance"
When you have multiple similar views to be displayed and re-arranged dynamically, UITableView is a smart choice. Re-arranging frames would be tedious to maintain and requires lot of code to implement. Why not use tools provided by UITableView? Just use deleteRowsAtIndexPaths to delete your view and the rest will be taken care for you.

CosmicMind Swift Material library programmatic autolayout with stacked menu

I'm using CosmicMind's Material library for swift. I'm trying to get the Menu example to work with programmatic autolayout - where can I find an example of how to make this work?
From the examples on Github I haven't figured out a way to use autolayout.
/// Prepares the FlatMenu example.
private func prepareFlatbMenuExample() {
let btn1: FlatButton = FlatButton()
btn1.addTarget(self, action: "handleFlatMenu", forControlEvents: .TouchUpInside)
btn1.setTitleColor(MaterialColor.white, forState: .Normal)
btn1.backgroundColor = MaterialColor.blue.accent3
btn1.pulseColor = MaterialColor.white
btn1.setTitle("Sweet", forState: .Normal)
view.addSubview(btn1)
let btn2: FlatButton = FlatButton()
btn2.setTitleColor(MaterialColor.blue.accent3, forState: .Normal)
btn2.borderColor = MaterialColor.blue.accent3
btn2.pulseColor = MaterialColor.blue.accent3
btn2.borderWidth = .Border1
btn2.setTitle("Good", forState: .Normal)
view.addSubview(btn2)
let btn3: FlatButton = FlatButton()
btn3.setTitleColor(MaterialColor.blue.accent3, forState: .Normal)
btn3.borderColor = MaterialColor.blue.accent3
btn3.pulseColor = MaterialColor.blue.accent3
btn3.borderWidth = .Border1
btn3.setTitle("Nice", forState: .Normal)
view.addSubview(btn3)
// Initialize the menu and setup the configuration options.
flatMenu = Menu(origin: CGPointMake(spacing, view.bounds.height - height - spacing))
flatMenu.direction = .Down
flatMenu.spacing = 8
flatMenu.buttonSize = CGSizeMake(120, height)
flatMenu.buttons = [btn1, btn2, btn3]
}
flatMenu is of type Menu from the Material library, and is a class that holds each of the buttons in the menu. It appears the "origin" is what controls the positioning on the page, but because Menu isn't a UIView subclass, I'm not sure how to use this with autolayout.
public class Menu {
...
}
What view do you have the buttons in? Try positioning the superview with AutoLayout using the same dimensions as your button size and then set the origin of the Menu as CGRectZero.
Also make sure you have clipsToBounds for the superview set to false (the default). And because the buttons are outside the bounds of your autolayout placed view, you'll need to use a custom UIView subclass like the one below to handle the actions on the buttons.
class MenuParentView: UIView {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
//because the subviews will be outside the bounds of this view
//we need to look at the subviews to see if we have a hit
for subview in self.subviews {
let pointForTargetView = subview.convertPoint(point, fromView: self)
if CGRectContainsPoint(subview.bounds, pointForTargetView) {
return subview.hitTest(pointForTargetView, withEvent: event)
}
}
return super.hitTest(point, withEvent: event)
}
}

iOS Keyboard in Swift is too wide and tall when loading from XIB file

I'm building a keyboard using Swift (XCode 6 with iOS 8.3 SDK) and when I load the xib, the keyboard is about 1000 pixels too wide and tall. I've, in the freeform xib file, put all my keys in a UIView and set the UIViews constraints to superview top, bottom, left and right.
As you can see, the result is annoying. Here's my code:
import UIKit
class KeyboardViewController: UIInputViewController {
var BlurBoardView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
loadInterface()
}
func loadInterface() {
//load the nib file
var blurboardNib = UINib(nibName: "BlurBoardView", bundle: nil)
// initiate the view
BlurBoardView = blurboardNib.instantiateWithOwner(self, options: nil)[0] as! UIView
// add the interface to the main view
view.addSubview(BlurBoardView)
// copy the background color
view.backgroundColor = BlurBoardView.backgroundColor
let blur = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blur.frame = view.frame
view.addSubview(blur)
}
#IBAction func didTapButton(sender: AnyObject?) {
let button = sender as! UIButton
let title = button.titleForState(.Normal)
var proxy = textDocumentProxy as! UITextDocumentProxy
switch title as String!{
case "<" :
proxy.deleteBackward()
case "RETURN" :
proxy.insertText("\n")
case " " :
proxy.insertText(" ")
case "CHG" :
self.advanceToNextInputMode()
default :
proxy.insertText(title!)
}
}
/*
override func textWillChange(textInput: UITextInput) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
var proxy = self.textDocumentProxy as! UITextDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
textColor = UIColor.whiteColor()
} else {
textColor = UIColor.blackColor()
}
//self.nextKeyboardButton.setTitleColor(textColor, forState: .Normal)
}
*/
}
The keyboard, in it's precompiled xib looks like this:
You can also see the parent (of the keys) views constraints.
I'm mainly an Objective-C developer, so the code makes sense, but the issue might just be a huge SBE because of Swift, so sorry if it's ridiculously simple ;)
edit: The P key has a constraint to tie it 14 pixels to the left superview, and 0 pixels to the right (to the O key) which is how I know it's way too wide. Keyboard bottom constraint is to bottom of superview.
Look like you're using Autolayout and you're not setting constraints between your view and your BlurBoardView.
When ViewDidLoad is called, the view doesn't have its right frame. It's only when viewDidLayoutSubviews is called.
So set constraints between your view and your BlurBoardView in ViewDidLoad. Or put BlurBoardView.frame = the frame your wish in viewDidLayoutSubview.
Same thing for your blurView

Resources