Dynamic Auto Layout - ios

To everyone who reads it. I want to make chat application like Viber, WhatsApp, etc. And the main problem for me is dynamic design. I know how to work with constraints if user interface is stick but, if it is changing, I don’t.
Please, look at the animation from Viber:
Everything is moving and changing when typing and receiving or sending messages. How to do it using Autoalyout? Especially I am interesting how to do it with UITextView. To expand it when typing, and, what is more important, how to move table view above it. Maybe, change size of parent view which holds text view. I think all this stuff can be call Dynamic Auto Layout.
I will appreciate any help or advice!
I am using Swift and Xcode 7, all latest versions.

Here is a piece of code that builds a keypad using UIStackViews. The layout is managed by UIStackView.
func newPad(runModeVariable:Int) {
var xPos:CGFloat = 0
var yPos:CGFloat = 0
keyPadWindow = UIView(frame: CGRect(x: xPos, y: yPos, width: 320, height: 356))
var keyCount = 0
keyPadSV = UIStackView(frame: CGRect(x: 0, y: 0, width: 320, height: 356))
keyPadSV!.axis = .Vertical
keyPadSV!.spacing = 0.0
keyPadSV!.alignment = .Center
keyPadSV!.distribution = .EqualSpacing
for _ in 0 ..< 5 {
let keyPadSVB = UIStackView(frame:CGRect(x: 0, y: 0, width: 0, height: 0))
keyPadSVB.axis = .Horizontal
keyPadSVB.spacing = 0.0
keyPadSVB.alignment = .Center
keyPadSVB.distribution = .EqualSpacing
for _ in 0 ..< 4 {
let button = UIButton(type: UIButtonType.Custom) as UIButton
button.frame = CGRectMake(0, 0, 0, 0)
button.tag = keyCount
let blah = "x" + String(keyCount)
let blahIMAGE = UIImage(named: blah)
button.setImage(blahIMAGE, forState: UIControlState.Normal)
button.addTarget(self, action: #selector(ViewController.keyPadPress(_:)), forControlEvents: UIControlEvents.TouchUpInside)
keyPadSVB.addArrangedSubview(button)
keyCount += 1
}
keyPadSV.addArrangedSubview(keyPadSVB)
}
keyPadWindow.addSubview(keyPadSV)
self.view.addSubview(keyPadWindow)
}

Related

button does not work after adding uiview in swift 4

let SearchView = UIView()
SearchView.backgroundColor = UIColor.clear
SearchView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
let Searchbutton = UIButton(type: .system)
Searchbutton.setImage(UIImage (named: "SEARCH"), for: .normal)
Searchbutton.backgroundColor = .clear
Searchbutton.frame = CGRect(x: -17, y: -20, width: 30, height: 30)
Searchbutton.contentMode = .scaleAspectFit
let WidthConstraint = Searchbutton.widthAnchor.constraint(equalToConstant: 30)
let HeightConstraint = Searchbutton.heightAnchor.constraint(equalToConstant: 30)
WidthConstraint.isActive = true
HeightConstraint.isActive = true
let barButtonItem = UIBarButtonItem(customView: SearchView)
self.navigationItem.rightBarButtonItem = barButtonItem
SearchView.addSubview(Searchbutton)
Searchbutton.addTarget(self, action: #selector(viewanewcontroller(button:)), for: .touchUpInside)
//right search button
After making a button, I wanted to move it little bit to the right. So, i made UI View to move the button inside the view. But, then, after this, the button does not work anymore. Can anyone tell me the solution?
Your code is working fine but need to clarify some points.
Reason Your button not working is below 4 lines
let WidthConstraint = Searchbutton.widthAnchor.constraint(equalToConstant: 30)
let HeightConstraint = Searchbutton.heightAnchor.constraint(equalToConstant: 30)
WidthConstraint.isActive = true
HeightConstraint.isActive = true
As you already set UIButton frame then no need to use of above lines of code.
Yellow color view is your SearchView and green color is your UIButton.
If you use Searchbutton frame like Searchbutton.frame = CGRect(x: -17, y: -20, width: 30, height: 30) then it happens something like below image.
You added UIButton as subview of UIView and when you click on green button which is inside yellow View will work but the area which is outside Yellow area doesn't work.
You need to start UIButton frame from 0,0,30,30
Searchbutton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
or you can directly set UIButton frame same as UIView frame then it looks like below image.
Searchbutton.frame = SearchView.frame
Below is your Working Code.
let SearchView = UIView()
SearchView.backgroundColor = UIColor.clear
SearchView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
let Searchbutton = UIButton(type: .system)
Searchbutton.setImage(UIImage (named: "SEARCH"), for: .normal)
Searchbutton.backgroundColor = .clear
Searchbutton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
Searchbutton.contentMode = .scaleAspectFit
let barButtonItem = UIBarButtonItem(customView: SearchView)
self.navigationItem.rightBarButtonItem = barButtonItem
SearchView.addSubview(Searchbutton)
Searchbutton.addTarget(self, action: #selector(viewanewcontroller(button:)), for: .touchUpInside)
//right search button
You're doing a lot of unnecessary things here. Firstly, you don't need to put your Button in a container view to put it as the right bar button item.
You also don't need to add constraints to your searchbutton, since you have given it a fixed frame.
let Searchbutton = UIButton(type: .system)
Searchbutton.setImage(UIImage (named: "SEARCH"), for: .normal)
Searchbutton.backgroundColor = .clear
Searchbutton.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
Searchbutton.contentMode = .scaleAspectFit
let barButtonItem = UIBarButtonItem(customView: Searchbutton)
self.navigationItem.rightBarButtonItem = barButtonItem
Searchbutton.addTarget(self, action: #selector(viewanewcontroller(button:)), for: .touchUpInside)
Also as Pratik's comment said, you're meant to use lower camel case. so SearchView should be searchView and SearchButton as searchButton
As for moving it to the right, it seems like that isn't possible anymore without either subclassing the UINavigationBar and changing the implementation, or by making UIViews look exactly like the standard Navigation Bar.
Get rid of the space on the right side of a UINavigationBar

Swift 4 Move Location of UIButton

I've been searching all around the other questions but none of the solutions I've read worked for me.
I have this code I need to modify so that when the button's title changes, it still appears to be aligned with some other items in the table. The button was declared programmatically as shown below:
internal func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section % 2 != 0 {
let frame : CGRect = tableView.frame
btnSelectAll = UIButton(frame: CGRect(x: frame.size.width-92, y: 0 , width: 100, height: 30))
btnSelectAll.setTitle(btnTitle, for: .normal)
btnSelectAll.setTitleColor(UIColor.blue, for: .normal)
let lblTitle : UILabel = UILabel(frame: CGRect(x: 19, y: 0, width: 120, height: 30))
btnSelectAll.addTarget(self, action:#selector(selectAllClicked(sender:)), for: .touchUpInside)
lblTitle.text = Constants.FILTER_DEPARTMENT_SECTION
lblTitle.font = UIFont.boldSystemFont(ofSize: 17)
let headerView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
headerView.backgroundColor = UIColor.white
headerView.addSubview(btnSelectAll)
headerView.addSubview(lblTitle)
return headerView
}
return nil
}
I tried to change it like so here:
if filter[Constants.FILTER_DEPARTMENT_SECTION]?.count == department.count {
if let btnSelectAll = btnSelectAll {
let frame : CGRect = tableView.frame
let newFrame = CGRect(x: frame.size.width-150, y: 0 , width: 100, height: 30)
btnSelectAll.frame = newFrame
btnSelectAll.setTitle(Constants.DESELECT_ALL, for: .normal)
btnSelectAll.setNeedsDisplay()
}
}
Unfortunately I still can't get it to move.
Use btnSelectAll.sizeToFit(),
As Apple says:
Call this method when you want to resize the current view so that it uses the most appropriate amount of space. Specific UIKit views resize themselves according to their own internal needs. In some cases, if a view does not have a superview, it may size itself to the screen bounds. Thus, if you want a given view to size itself to its parent view, you should add it to the parent view before calling this method.

Swift Content Tabs?

I am building an app and I am required to have 3 tabs if you click on the first tab, its a tableview, second tab a different tableview, third tab, TextView, here is an image of what I am trying to do:
Every time I google looking for examples of something like this, I get referred to UITabBarController, I don't think a tab bar is what I am looking for.
What you are looking for is called Segmented Control (UISegmentedControl).
You can see it in Action natively e.g. in both the Apple iTunes and Health App.
Instead of performing segues to different views with the UITabBarController, you can, using Interface Builder, connect it to your UIViewController like so
#IBOutlet weak var segmentedControl: UISegmentedControl!
and in
viewDidLoad() {
switch segmentedControl.selectedIndex {
case 0: // Do something on your first picture
someFunction()
case 1: // Do something on your second picture
performSegue(withIdentifier: "your identifier here", sender: nil)
case 2: // Do something on your third picture
image.isHidden = true
button.isEnabled = false
default: break
}
... etc. You could also initiate different View Controllers instead of manipulating just one View, which you can then access via segues.
Please see official Swift doc # https://developer.apple.com/documentation/uikit/uisegmentedcontrol?changes=_3
I would create this layout programmatically. You want a custom look to it so I do not think the standard controls are your best option.
Here is a playground to get you started on one way this could be achieved:
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let view1 = UIView(frame: CGRect(x: 0, y: 100, width: 768, height: 924))
let view2 = UIView(frame: CGRect(x: 0, y: 100, width: 768, height: 924))
let view3 = UIView(frame: CGRect(x: 0, y: 100, width: 768, height: 924))
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x: 0, y: 0, width: 768, height: 1024)
self.view.backgroundColor = .black
// View to have a border around the buttons
let box = UIView(frame: CGRect(x: 40, y: 20, width: 300, height: 50))
box.layer.borderColor = UIColor.white.cgColor
box.layer.borderWidth = 2
self.view.addSubview(box)
// Tab buttons
let button1 = UIButton(frame: CGRect(x: 40, y: 20, width: 100, height: 50))
button1.setTitle("Orange", for: .normal)
button1.tag = 1
button1.addTarget(self, action: #selector(tabTouched(sender:)), for: .touchUpInside)
self.view.addSubview(button1)
let button2 = UIButton(frame: CGRect(x: 140, y: 20, width: 100, height: 50))
button2.setTitle("Blue", for: .normal)
button2.tag = 2
button2.addTarget(self, action: #selector(tabTouched(sender:)), for: .touchUpInside)
self.view.addSubview(button2)
let button3 = UIButton(frame: CGRect(x: 240, y: 20, width: 100, height: 50))
button3.setTitle("Green", for: .normal)
button3.tag = 3
button3.addTarget(self, action: #selector(tabTouched(sender:)), for: .touchUpInside)
self.view.addSubview(button3)
// Tab Views
view1.backgroundColor = .orange
self.view.addSubview(view1)
view2.backgroundColor = .blue
view2.alpha = 0
self.view.addSubview(view2)
view3.backgroundColor = .green
view3.alpha = 0
self.view.addSubview(view3)
}
// When each of the buttons are tapped we will hide or show the correct tab's view
#objc func tabTouched(sender: UIButton) {
if sender.tag == 1 {
UIView.animate(withDuration: 0.3) {
self.view1.alpha = 1
self.view2.alpha = 0
self.view3.alpha = 0
}
} else if sender.tag == 2 {
UIView.animate(withDuration: 0.3) {
self.view1.alpha = 0
self.view2.alpha = 1
self.view3.alpha = 0
}
} else if sender.tag == 3 {
UIView.animate(withDuration: 0.3) {
self.view1.alpha = 0
self.view2.alpha = 0
self.view3.alpha = 1
}
}
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = ViewController()
You can copy and paste this into an empty playground to see it in action. Essentially this is showing or hiding the appropriate views based on which button is selected. You can swap out the views for view controllers if you so desire.

Adding a button to a stackview via code

i'm trying to add a button back into a stackview and i'm not sure how to do it in the code, i only want the button to appear under a circumstance so that's why i'm doing it in code, here's what i have
i'm removing it with
func hideCompareButton() {
UIView.animateWithDuration(0.4, animations: {
self.compareButton.alpha = 0
}) { completed in
if completed == true {
self.compareButton.removeFromSuperview()
}
}
}
and i've got this that would normally work with other views, but i'm not sure how to add a button onto a stackview, at a prefered locations, there's 3 buttons when it's not added, 4 when it is, and id like it to be the 3rd button in from the left
func showCompareButton() {
// bottomStackView.addArrangedSubview(compareButton) ???
bottomStackView.addSubview(compareButton)
let bottomConstraint = compareButton.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
let leftConstraint = compareButton.leftAnchor.constraintEqualToAnchor(filterButton.rightAnchor)
let rightConstraint = compareButton.rightAnchor.constraintEqualToAnchor(shareButton.leftAnchor)
let heightConstraint = compareButton.heightAnchor.constraintEqualToConstant(44)
NSLayoutConstraint.activateConstraints([bottomConstraint, leftConstraint, rightConstraint, heightConstraint])
bottomStackView.layoutIfNeeded()
}
Why don't you build the stack view the way you want it with all the buttons and then simply hide the 3rd button. When you want it unhide it. The stack view will do all the rest. Here some stack view code to get you going. This builds a keypad, but its the same principle, it adds all the buttons and then makes it visible.
Here some stack view code to get you going.
keyPadWindow = UIView(frame: CGRect(x: 128, y: 224, width: 512, height: 356))
var keyCount = 0
keyPadSV = UIStackView(frame: CGRect(x: 0, y: 0, width: 512, height: 356))
print("pickerWindow.bounds \(pickerWindow.bounds)")
keyPadSV!.axis = .Vertical
keyPadSV!.spacing = 0.0
keyPadSV!.alignment = .Center
keyPadSV!.distribution = .EqualSpacing
for var k2s = 0; k2s < 5; k2s++ {
let keyPadSVB = UIStackView(frame:CGRect(x: 0, y: 0, width: 0, height: 0))
keyPadSVB.axis = .Horizontal
keyPadSVB.spacing = 0.0
keyPadSVB.alignment = .Center
keyPadSVB.distribution = .EqualSpacing
for var k4s = 0; k4s < 4; k4s++ {
let button = UIButton(type: UIButtonType.Custom) as UIButton
button.frame = CGRectMake(0, 0, 0, 0)
button.tag = keyCount
let blah = "x" + String(keyCount)
let blahIMAGE = UIImage(named: blah)
button.setImage(blahIMAGE, forState: UIControlState.Normal)
print("blah \(blah)")
button.addTarget(self, action: "keyPadPress:", forControlEvents: UIControlEvents.TouchUpInside)
keyPadSVB.addArrangedSubview(button)
keyCount++
}
keyPadSV.addArrangedSubview(keyPadSVB)
}
keyPadWindow.addSubview(keyPadSV)
self.view.addSubview(keyPadWindow)
}
Don't add any constraints! That is exactly the job of the stack view. Just make the button an arranged view of the stack view and stop.
If the problem is that the button is to be inserted in the midst of the existing arranged views, well, that is what insertArrangedSubview:atIndex: is for. See IOS stackview addArrangedSubview add at specific index

Creating and Setting title of UIButton in FOR loop breaks loop

In my app I'm creating a paged slider (UISlider + UIPageControl). The app reaches out to CoreData to get the array of Entities to create a "page" for.
I build the paginated scroller n the following loop:
for i in 0..<features.count {
var pageWidth = Int(view.frame.width)*i
var pageView = UIView(frame: CGRectMake(CGFloat(pageWidth), 0, scrollView.frame.width, scrollView.frame.height))
pageView.backgroundColor = UIColor.clearColor()
pageView.clipsToBounds = true
//Background image
var imageView = UIImageView(frame: CGRectMake(0, 0, scrollView.frame.width, scrollView.frame.height))
var image:UIImage!
imageView.contentMode = .ScaleAspectFill
pageView.addSubview(imageView)
if fileManager.fileExistsAtPath(features[i].coverImage){
//The background image
image = UIImage(contentsOfFile: self.features[i].coverImage)
imageView.image = image
}
else if !fileManager.fileExistsAtPath(features[i].coverImage) && !features[i].coverImage.isEmpty {
FileStoreClient.downloadFileIfNotExists(features[i].cover, completion: { (success, error) -> Void in
if error == nil {
image = UIImage(contentsOfFile: self.features[i].coverImage)
imageView.image = image
}
else {
println("There was an error during the download")
}
})
}
//The background cover
var coverView = UIView(frame: CGRectMake(0, 0, view.frame.width, 250))
coverView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
pageView.addSubview(coverView)
//The title
var titleLabel = UILabel(frame: CGRectMake(10, 40, view.frame.width-20, 25))
titleLabel.font = UIFont(name: "Haymaker", size: 24)
titleLabel.textColor = UIColor.whiteColor()
titleLabel.layer.shadowColor = UIColor.blackColor().CGColor
titleLabel.layer.shadowOffset = CGSizeMake(1, 1)
titleLabel.layer.shadowRadius = 1
titleLabel.layer.shadowOpacity = 1
titleLabel.textAlignment = .Center
titleLabel.text = features[i].title
pageView.addSubview(titleLabel)
//The text
var textLabel = UILabel(frame: CGRectMake(10, titleLabel.frame.origin.y+titleLabel.frame.height+5, view.frame.width-20, 60))
textLabel.numberOfLines = 0
textLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
textLabel.font = UIFont(name: "OpenSans", size: 13)
textLabel.textColor = UIColor.whiteColor()
textLabel.textAlignment = .Center
textLabel.text = features[i].summary
pageView.addSubview(textLabel)
//The button
var readMoreButton = UIButton(frame: CGRectMake(0, pageView.frame.height - 90, 200, 80))
readMoreButton.setAttributedTitle(NSAttributedString(string: Strings.readMoreButtonTitle, attributes: Fonts.featureButtonText), forState: UIControlState.Normal)
//readMoreButton.addTarget(self, action: "readArticle", forControlEvents: .TouchUpInside)
readMoreButton.layer.cornerRadius = 6
readMoreButton.layer.borderColor = UIColor.whiteColor().CGColor
readMoreButton.layer.borderWidth = 1
readMoreButton.center.x = pageView.center.x
pageView.addSubview(readMoreButton)
println(i)
scrollView.addSubview(pageView)
}
Everything works exactly as it should, except that the moment I try to assign a title to the new button, the loop turns into an infinite loop. If I print out the current index of the loop, it keeps cycling through endlessly. Example:
0
1
2
0
1
2
0
1
2
If I comment out the myButton.setTitle() method, the problem goes away and the indexes print out like the following:
0
1
2
I have tried setTitle and setAttributedTitle, both of which have the same effect. I have tried dispatching different queues and nothing seems to be working.
Again, everything works for the background image, the two labels, views, etc. Problem only exists when a button is brought into the mix. The issue exists even if I remove the FileStoreManager code.
Something about a button in a for loop isn't happy.
EDIT #1
I decided to try and create a new project just to test button creation in a for loop. This works perfectly fine.
var x = 20
var y = 20
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...10 {
var myview = UIView(frame: CGRectMake(CGFloat(x), CGFloat(y), 100, 50))
var button = UIButton(frame: CGRectMake(0, 0, 100, 50))
button.setTitle("Test", forState: .Normal)
button.setTitleColor(UIColor.blackColor(), forState: .Normal)
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 2
myview.addSubview(button)
view.addSubview(myview)
x = x+20
y = y+20
println(i)
}
}
You can also try fast enumeration in case something is somehow getting messed up in your range.
for feature in features { ... }
or if you need the index:
for (index, features) in features.enumerate() { ... }
Not sure it will resolve the issue, but it might increase readability.
It turns out it was because I had the above code in the viewDidLayoutSubviews() function. Something about setting a buttons title triggers the method, it seems.
As I needed to use this method due to dynamic sizes, I simply added a "hasLoaded:Bool" to check if the function has already fired.
This resolved the issue.

Resources