I am trying to create a UIbutton that changes color to green when tapped and blue when deselected. My logic works however when I exit the screen it still shows blue when selected because it's the default color shown before the button is tapped. What can I do to resolve this ?
func setUpSelectDealerButton(){
selectDealerButton.layer.cornerRadius = 5
view.addSubview(selectDealerButton)
selectDealerButton.setTitle( "Select Dealer" , for: .normal)
selectDealerButton.backgroundColor = .systemBlue
selectDealerButton.snp.makeConstraints{ (make) in
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16)
make.top.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(16)
make.leading.equalTo(view.safeAreaLayoutGuide.snp.leading).offset(3)
make.trailing.equalTo(view.safeAreaLayoutGuide.snp.trailing).offset(-3)
make.height.equalTo(50)
make.width.equalTo(50)
}
selectDealerButton.addTarget(self, action: #selector(addToSelectedAccounts), for: .touchUpInside)
}
#objc func addToSelectedAccounts() {
if let selectedAccounts = delegate?.selectedAccounts, !selectedAccounts.contains(viewModel.account) {
delegate?.didSelect(account: viewModel.account)
}
if selectDealerButton.isSelected{
selectDealerButton.isSelected = false
selectDealerButton.setTitle( "Select Dealer" , for: .normal)
selectDealerButton.backgroundColor = .systemBlue
}
else{
selectDealerButton.isSelected = true
selectDealerButton.setTitle( "Dealer Selected" , for: .normal)
selectDealerButton.backgroundColor = .systemGreen
GlobalMBProgressHud.sharedInstance.showGlobalHud(withSuccess: true, text: "Selected!")
}
}
This is the screenshot: https://postimg.cc/xJt8ngy9
If I understand well this is an example of how to control button state, declare your objects:
let myButton: UIButton = {
let button = UIButton(type: .system)
button.layer.cornerRadius = 5
button.clipsToBounds = true
button.setTitleColor(.white, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let myLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 20, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var buttonState = Int() // set to integer
in viewDidLoad add target to your button set constraints (how you prefer) and call handleChangeButton function:
view.backgroundColor = .red
let control = UserDefaults.standard.integer(forKey: "buttonState")
if control == 0 {
buttonState = 0
} else {
buttonState = control
}
myButton.addTarget(self, action: #selector(handleChangeButton), for: .touchUpInside)
view.addSubview(myButton)
myButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
myButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
myButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.addSubview(myLabel)
myLabel.leadingAnchor.constraint(equalTo: myButton.leadingAnchor).isActive = true
myLabel.trailingAnchor.constraint(equalTo: myButton.trailingAnchor).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
handleChangeButton()
set variable to control button state and write the function:
#objc fileprivate func handleChangeButton() {
if buttonState == 0 {
myButton.setTitle( "Select Dealer" , for: .normal)
myButton.backgroundColor = .systemBlue
myLabel.text = myButton.currentTitle
buttonState = 1
} else {
myButton.setTitle( "Dealer Selected" , for: .normal)
myButton.backgroundColor = .systemGreen
myLabel.text = myButton.currentTitle
buttonState = 0
}
UserDefaults.standard.set(buttonState, forKey: "buttonState")
}
This is the result:
Related
I'm facing this weird animation issues when hiding UIButton in a StackView using the new iOS 15 Configuration. See playground:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private weak var contentStackView: UIStackView!
override func viewDidLoad() {
view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
view.backgroundColor = .white
let contentStackView = UIStackView()
contentStackView.spacing = 8
contentStackView.axis = .vertical
for _ in 1...2 {
contentStackView.addArrangedSubview(makeConfigurationButton())
}
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.addAction(buttonAction, for: .primaryActionTriggered)
view.addSubview(contentStackView)
view.addSubview(button)
contentStackView.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentStackView.topAnchor.constraint(equalTo: view.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
self.contentStackView = contentStackView
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0) {
guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return }
toggleElement.isHidden.toggle()
toggleElement.alpha = toggleElement.isHidden ? 0 : 1
self?.contentStackView.layoutIfNeeded()
}
}
}
private func makeSystemButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("System Button", for: .normal)
return button
}
private func makeConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
config.title = "Configuration Button"
button.configuration = config
return button
}
}
PlaygroundPage.current.liveView = MyViewController()
Which results in this animation:
But I want the animation to look like this, where the button only shrinks vertically:
Which you can replicate in the playground by just swapping contentStackView.addArrangedSubview(makeConfigurationButton()) for contentStackView.addArrangedSubview(makeSystemButton()).
I guess this has something to do with the stack view alignment, setting it to center gives me the desired animation, but then the buttons don't fill the stack view width anymore and setting the width through AutoLayout results in the same animation again... Also, having just one system button in the stack view results in the same weird animation, but why does it behave differently for two system buttons? What would be a good solution for this problem?
As you've seen, the built-in show/hide animation with UIStackView can be quirky (lots of other quirks when you really get into it).
It appears that, when using a button with UIButton.Configuration, the button's width changes from the width assigned by the stack view to its intrinsic width as the animation occurs.
We can get around that by giving the button an explicit height constraint -- but, what if we want to use the intrinsic height (which may not be known in advance)?
Instead of setting the constraint, set the button's Content Compression Resistance Priority::
button.configuration = config
// add this line
button.setContentCompressionResistancePriority(.required, for: .vertical)
return button
And we no longer get the horizontal sizing:
As you will notice, though, the button doesn't "squeeze" vertically... it gets "pushed up" outside the stack view's bounds.
We can avoid that by setting .clipsToBounds = true on the stack view:
If this effect is satisfactory, we're all set.
However, as we can see, the button is still not getting "squeezed." If that is the visual effect we want, we can use a custom "self-stylized" button instead of a Configuration button:
Of course, there is very little visual difference - and looking closely the button's text is not squeezing. If we really, really, really want that to happen, we need to animate a transform instead of using the stack view's default animation.
And... if we are taking advantage of some of the other conveniences with Configurations, using a self-stylized UIButton might not be an option.
If you want to play with the differences, here's some sample code:
class ViewController : UIViewController {
var btnStacks: [UIStackView] = []
override func viewDidLoad() {
view.backgroundColor = .systemYellow
let outerStack = UIStackView()
outerStack.axis = .vertical
outerStack.spacing = 12
for i in 1...3 {
let cv = UIView()
cv.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
let label = UILabel()
label.backgroundColor = .yellow
label.font = .systemFont(ofSize: 15, weight: .light)
let st = UIStackView()
st.axis = .vertical
st.spacing = 8
if i == 1 {
label.text = "Original Configuration Buttons"
for _ in 1...2 {
st.addArrangedSubview(makeOrigConfigurationButton())
}
}
if i == 2 {
label.text = "Resist Compression Configuration Buttons"
for _ in 1...2 {
st.addArrangedSubview(makeConfigurationButton())
}
}
if i == 3 {
label.text = "Custom Buttons"
for _ in 1...2 {
st.addArrangedSubview(makeCustomButton())
}
}
st.translatesAutoresizingMaskIntoConstraints = false
cv.addSubview(st)
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalToConstant: 28.0),
st.topAnchor.constraint(equalTo: cv.topAnchor),
st.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
st.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
cv.heightAnchor.constraint(equalToConstant: 100.0),
])
btnStacks.append(st)
outerStack.addArrangedSubview(label)
outerStack.addArrangedSubview(cv)
outerStack.setCustomSpacing(2.0, after: label)
}
// a horizontal stack view to hold a label and UISwitch
let ctbStack = UIStackView()
ctbStack.axis = .horizontal
ctbStack.spacing = 8
let label = UILabel()
label.text = "Clips to Bounds"
let ctbSwitch = UISwitch()
ctbSwitch.addTarget(self, action: #selector(switchChanged(_:)), for: .valueChanged)
ctbStack.addArrangedSubview(label)
ctbStack.addArrangedSubview(ctbSwitch)
// put the label/switch stack in a view so we can center it
let ctbView = UIView()
ctbStack.translatesAutoresizingMaskIntoConstraints = false
ctbView.addSubview(ctbStack)
// button to toggle isHidden/alpha on the first
// button in each stack view
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.backgroundColor = .white
button.addAction(buttonAction, for: .primaryActionTriggered)
outerStack.addArrangedSubview(ctbView)
outerStack.addArrangedSubview(button)
outerStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerStack)
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
ctbStack.topAnchor.constraint(equalTo: ctbView.topAnchor),
ctbStack.bottomAnchor.constraint(equalTo: ctbView.bottomAnchor),
ctbStack.centerXAnchor.constraint(equalTo: ctbView.centerXAnchor),
])
}
#objc func switchChanged(_ sender: UISwitch) {
btnStacks.forEach { v in
v.clipsToBounds = sender.isOn
}
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0) {
guard let self = self else { return }
self.btnStacks.forEach { st in
st.arrangedSubviews[0].isHidden.toggle()
st.arrangedSubviews[0].alpha = st.arrangedSubviews[0].isHidden ? 0 : 1
}
}
}
}
private func makeOrigConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
config.title = "Configuration Button"
button.configuration = config
return button
}
private func makeConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
config.title = "Configuration Button"
button.configuration = config
// add this line
button.setContentCompressionResistancePriority(.required, for: .vertical)
return button
}
private func makeCustomButton() -> UIButton {
let button = UIButton()
button.setTitle("Custom Button", for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitleColor(.lightGray, for: .highlighted)
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 6
return button
}
}
Looks like this:
Edit
Quick example of another "quirk" when it comes to hiding a stack view's arranged subview (excess code in here, but I stripped down the above example):
class MyViewController : UIViewController {
var btnStacks: [UIStackView] = []
override func viewDidLoad() {
view.backgroundColor = .systemYellow
let outerStack = UIStackView()
outerStack.axis = .vertical
outerStack.spacing = 12
let cv = UIView()
cv.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
let label = UILabel()
label.backgroundColor = .yellow
label.font = .systemFont(ofSize: 15, weight: .light)
let st = UIStackView()
st.axis = .vertical
st.spacing = 8
let colors: [UIColor] = [
.cyan, .green, .yellow, .orange, .white
]
label.text = "Labels"
for j in 0..<colors.count {
let v = UILabel()
v.text = "Label"
v.textAlignment = .center
v.backgroundColor = colors[j]
if j == 2 {
v.text = "Height Constraint = 80.0"
v.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
}
st.addArrangedSubview(v)
}
st.translatesAutoresizingMaskIntoConstraints = false
cv.addSubview(st)
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalToConstant: 28.0),
st.topAnchor.constraint(equalTo: cv.topAnchor),
st.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
st.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
cv.heightAnchor.constraint(equalToConstant: 300.0),
])
btnStacks.append(st)
outerStack.addArrangedSubview(label)
outerStack.addArrangedSubview(cv)
outerStack.setCustomSpacing(2.0, after: label)
// button to toggle isHidden/alpha on the first
// button in each stack view
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.backgroundColor = .white
button.addAction(buttonAction, for: .primaryActionTriggered)
outerStack.addArrangedSubview(button)
outerStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerStack)
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0) {
guard let self = self else { return }
self.btnStacks.forEach { st in
st.arrangedSubviews[2].isHidden.toggle()
}
}
}
}
}
When this is run and the "Toggle" button is tapped, it will be painfully obvious what's "not-quite-right."
You should add height constraint to buttons and update this constraint while animating. I edit your code just as below.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private weak var contentStackView: UIStackView!
override func viewDidLoad() {
view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
view.backgroundColor = .white
let contentStackView = UIStackView()
contentStackView.spacing = 8
contentStackView.axis = .vertical
for _ in 1...2 {
contentStackView.addArrangedSubview(makeConfigurationButton())
}
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.addAction(buttonAction, for: .primaryActionTriggered)
view.addSubview(contentStackView)
view.addSubview(button)
contentStackView.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentStackView.topAnchor.constraint(equalTo: view.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
self.contentStackView = contentStackView
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0) {
guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return }
toggleElement.isHidden.toggle()
toggleElement.alpha = toggleElement.isHidden ? 0 : 1
toggleElement.heightAnchor.constraint(equalToConstant: toggleElement.isHidden ? 0 : 50)
self?.contentStackView.layoutIfNeeded()
}
}
}
private func makeSystemButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("System Button", for: .normal)
return button
}
private func makeConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 50)
])
config.title = "Configuration Button"
button.configuration = config
return button
}
}
PlaygroundPage.current.liveView = MyViewController()
I have totally different layouts for landscape and portrait.
In portrait the screen does not have the menu view of full height and half width on left and in landscape the screen contains the menu view on left side.
How to do that in iOS?
If you only want to show menu on landscape.
Check the device orientation by:
UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight
Then show the menu by using menu.isHidden = false to show the menu.
You can set the menu constraints either using storyboard or programmatically.
You can do it programmatically, declare your menuContainer view under your class controller like that:
let menuContainer: UIView = {
let v = UIView()
v.backgroundColor = .lightGray
v.alpha = 0
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
Now in viewDidLoad set constraints:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
view.addSubview(menuContainer)
menuContainer.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
menuContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
menuContainer.trailingAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
menuContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
after that write the detect func (I add animation but you can't do it):
fileprivate func detectRotation() {
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
print("landscape left")
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.menuContainer.alpha = 1
self.view.layoutIfNeeded()
}, completion: nil)
} else if UIDevice.current.orientation == UIDeviceOrientation.landscapeRight {
print("landscape right")
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.menuContainer.alpha = 1
self.view.layoutIfNeeded()
}, completion: nil)
} else {
print("porrait")
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.menuContainer.alpha = 0
self.view.layoutIfNeeded()
}, completion: nil)
}
}
override viewWillTransition func and call detectRotation func:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
detectRotation()
}
This is the result:
portrait
Landscape
To show in landscape and portrait:
let myButton: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Section 1", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.titleLabel?.minimumScaleFactor = 0.5
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let myButton2: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Section 2", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.titleLabel?.minimumScaleFactor = 0.5
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let myButton3: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Section 3", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.titleLabel?.minimumScaleFactor = 0.5
b.titleLabel?.textAlignment = .left
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let myButton4: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Section 4", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.titleLabel?.minimumScaleFactor = 0.5
b.titleLabel?.textAlignment = .left
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let menuContainer: UIView = {
let v = UIView()
v.backgroundColor = .lightGray
v.alpha = 1
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
view.addSubview(menuContainer)
menuContainer.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
menuContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
menuContainer.trailingAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
menuContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
let stackViewMenu = UIStackView(arrangedSubviews: [myButton, myButton2, myButton3, myButton4])
stackViewMenu.axis = .vertical
stackViewMenu.distribution = .fillEqually
stackViewMenu.spacing = 2
stackViewMenu.translatesAutoresizingMaskIntoConstraints = false
menuContainer.addSubview(stackViewMenu)
stackViewMenu.topAnchor.constraint(equalTo: menuContainer.safeAreaLayoutGuide.topAnchor).isActive = true
stackViewMenu.leadingAnchor.constraint(equalTo: menuContainer.leadingAnchor).isActive = true
stackViewMenu.trailingAnchor.constraint(equalTo: menuContainer.trailingAnchor).isActive = true
stackViewMenu.heightAnchor.constraint(equalTo: menuContainer.heightAnchor, multiplier: 0.5).isActive = true
}
The result:
When its clicked on some cell, DetailViewController will be opened. Problem is that context from DetailViewController is shown while transitioning between controllers.
Picture below presents problem:
image
DetailViewController is called in function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let singleMovie = movieList[indexPath.row]
getSingleMovie(movieId: singleMovie.id, completion: {})
getDirector(movieId: singleMovie.id, completion: { [weak self] in
guard let self = self else { return }
var directorName = ""
self.creditResponse?.crew.forEach({ singleCredit in
if singleCredit.knownForDepartment == .directing {
directorName = singleCredit.name
}
})
guard let safeMovie = self.detailMovie else {return}
let detailVc = DetailViewController(movie: safeMovie, groups: self.checkGroups(groups: singleMovie.genreIds), director: directorName, movieIndex: indexPath.row)
self.navigationController?.pushViewController(detailVc, animated: true)
})
}
DetailViewController:
import UIKit
class DetailViewController: UIViewController {
let movie: Details?
let groupsValue: String?
let directorValue: String?
let movieIndex: Int?
var checkButton: Bool = false
var favoriteButton: Bool = false
let backButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "Icon"), for: .normal)
button.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
return button
}()
var movieTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Bold", size: 40)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
var groupsLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Regular", size: 20)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var directorLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Bold", size: 20)
label.textColor = .white
label.text = "Director: "
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var directorNameLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Regular", size: 20)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var descriptionLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Regular", size: 20)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
var movieImageView: UIImageView = {
let iv = UIImageView()
iv.layer.cornerRadius = 15
iv.layer.masksToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let buttonChecked: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.addTarget(self, action: #selector(checkButtonTapped), for: .touchUpInside)
return button
}()
let buttonFavorite: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.addTarget(self, action: #selector(favoriteButtonTapped), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationItem.hidesBackButton = true
setupUI()
}
init(movie:Details, groups: String, director: String, movieIndex: Int) {
self.movie = movie
self.groupsValue = groups
self.directorValue = director
self.movieIndex = movieIndex
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension DetailViewController{
#objc func backButtonTapped() {
navigationController?.popToRootViewController(animated: true)
}
}
extension DetailViewController {
func setupUI(){
view.addSubview(movieTitleLabel)
view.addSubview(groupsLabel)
view.addSubview(directorLabel)
view.addSubview(descriptionLabel)
view.addSubview(directorNameLabel)
view.addSubview(movieImageView)
view.addSubview(backButton)
view.addSubview(buttonChecked)
view.addSubview(buttonFavorite)
setupValues()
setupConstraints()
setupButtons()
}
}
extension DetailViewController {
func setupConstraints(){
let bottomImageConstraint = movieImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
bottomImageConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
movieImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
movieImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
movieImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bottomImageConstraint,
movieImageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3),
movieTitleLabel.topAnchor.constraint(equalTo: movieImageView.bottomAnchor, constant: 10),
movieTitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
movieTitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
groupsLabel.topAnchor.constraint(equalTo: movieTitleLabel.bottomAnchor, constant: 10),
groupsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
directorLabel.topAnchor.constraint(equalTo: groupsLabel.bottomAnchor, constant: 10),
directorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
directorLabel.topAnchor.constraint(equalTo: groupsLabel.bottomAnchor, constant: 10),
directorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
directorNameLabel.topAnchor.constraint(equalTo: groupsLabel.bottomAnchor, constant: 10),
directorNameLabel.leadingAnchor.constraint(equalTo: directorLabel.trailingAnchor, constant: 10),
descriptionLabel.topAnchor.constraint(equalTo: directorLabel.bottomAnchor, constant: 10),
descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
backButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
backButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
backButton.heightAnchor.constraint(equalToConstant: 40),
backButton.widthAnchor.constraint(equalToConstant: 40),
buttonFavorite.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
buttonFavorite.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
buttonChecked.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
buttonChecked.trailingAnchor.constraint(equalTo: buttonFavorite.trailingAnchor, constant: -50),
])
}
func setupValues(){
movieTitleLabel.text = movie?.title
guard let safeUrl = movie?.posterPath else {return}
movieImageView.downloadImage(from: safeUrl)
descriptionLabel.text = movie?.overview
groupsLabel.text = groupsValue
directorNameLabel.text = directorValue
}
func setupButtons(){
let checkValue = UserDefaults.standard.bool(forKey: "checkButton\(movieIndex ?? 0)")
let favoriteValue = UserDefaults.standard.bool(forKey: "favoriteButton\(movieIndex ?? 0)")
if checkValue == true {
buttonChecked.setImage(UIImage(systemName: "checkmark.seal.fill"), for: .normal)
}
else {
buttonChecked.setImage(UIImage(systemName: "checkmark.seal"), for: .normal)
}
if favoriteValue == true {
buttonFavorite.setImage(UIImage(systemName: "star.fill"), for: .normal)
}
else {
buttonFavorite.setImage(UIImage(systemName: "star"), for: .normal)
}
}
}
extension DetailViewController{
#objc func checkButtonTapped() {
checkButton = !checkButton
if checkButton == true {
DispatchQueue.main.async {
self.buttonChecked.setImage(UIImage(systemName: "checkmark.seal.fill"), for: .normal)
}
}
else {
DispatchQueue.main.async {
self.buttonChecked.setImage(UIImage(systemName: "checkmark.seal"), for: .normal)
}
}
UserDefaults.standard.set(checkButton, forKey: "checkButton\(movieIndex ?? 0)")
}
#objc func favoriteButtonTapped() {
favoriteButton = !favoriteButton
if favoriteButton == true {
DispatchQueue.main.async {
self.buttonFavorite.setImage(UIImage(systemName: "star.fill"), for: .normal)
}
}
else {
DispatchQueue.main.async {
self.buttonFavorite.setImage(UIImage(systemName: "star"), for: .normal)
}
}
UserDefaults.standard.set(favoriteButton, forKey: "favoriteButton\(movieIndex ?? 0)")
}
}
How to setup animations so it transits normally?
Transition GIF
It looks like your DetailViewController.view has no backgroundColor set (OR it is .clear).
Please assign a backgroundColor to your view like this.
override func viewDidLoad() {
super.viewDidLoad()
// For testing, you may want to set this to something else like `.red` / `.yellow`
// This will help you in identifying where the issue is
self.view.backgroundColor = .black
}
I'm following a tutorial to create an interactive popup animation (http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/), and now would like to add buttons to the popup rather than have static text as in the tutorial.
The animation works fine, but the buttons are not detecting touch. Touching the button causes the animation to reverse, instead of printing my "test" statement.
From research, it looks to either be an issue with view hierarchies, the animation disabling the button touch, or layout/constraint issues with the button. I've tried addressing the above issues, but nothing has worked, was hoping someone might be able to help?
I've left out the code pertaining to the animation since I think the issue is to do with layout (and the animation seems to be working fine) - but it's still a lot; apologies in advance for the large amount of code.
class ViewController: UIViewController {
private let popupOffset: CGFloat = 440
private lazy var contentImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "Background")
return imageView
}()
private lazy var overlayView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = 0
return view
}()
private lazy var popupView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.1
view.layer.shadowRadius = 10
return view
}()
private lazy var closedTitleLabel: UILabel = {
let label = UILabel()
label.text = "Hello"
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
label.textColor = UIColor.darkGray
label.textAlignment = .center
return label
}()
private lazy var openTitleLabel: UILabel = {
let label = UILabel()
label.text = "Which door will you choose?"
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
label.textColor = UIColor.darkGray
label.textAlignment = .center
label.alpha = 0
label.transform = CGAffineTransform(scaleX: 1.6, y: 1.6).concatenating(CGAffineTransform(translationX: 0, y: 15))
return label
}()
private lazy var reviewsImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "LabelBackground")
return imageView
}()
let stackView = UIStackView()
let buttonA = UIButton()
let buttonB = UIButton()
let buttonC = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
layout()
popupView.addGestureRecognizer(panRecognizer)
}
override var prefersStatusBarHidden: Bool {
return true
}
//Layout
private var bottomConstraint = NSLayoutConstraint()
private func layout() {
contentImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentImageView)
contentImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
contentImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
contentImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
overlayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(overlayView)
overlayView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
overlayView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
overlayView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
overlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
popupView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(popupView)
popupView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
popupView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bottomConstraint = popupView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: popupOffset)
bottomConstraint.isActive = true
popupView.heightAnchor.constraint(equalToConstant: 500).isActive = true
closedTitleLabel.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(closedTitleLabel)
closedTitleLabel.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
closedTitleLabel.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
closedTitleLabel.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 20).isActive = true
openTitleLabel.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(openTitleLabel)
openTitleLabel.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
openTitleLabel.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
openTitleLabel.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 20).isActive = true
reviewsImageView.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(reviewsImageView)
reviewsImageView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
reviewsImageView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
reviewsImageView.bottomAnchor.constraint(equalTo: popupView.bottomAnchor).isActive = true
reviewsImageView.heightAnchor.constraint(equalToConstant: 428).isActive = true
buttonA.backgroundColor = UIColor.clear
let heightConstraintA = buttonA.heightAnchor.constraint(equalToConstant: 135)
heightConstraintA.isActive = true
heightConstraintA.priority = UILayoutPriority(rawValue: 999)
buttonA.translatesAutoresizingMaskIntoConstraints = false
buttonA.setTitle("A", for: .normal)
buttonA.setTitleColor(UIColor.darkGray, for: .normal)
buttonA.backgroundColor = UIColor.clear
buttonA.addTarget(self, action: #selector(buttonATapped(sender:)), for: .touchDown)
//self.popupView.addSubview(buttonA)
buttonB.backgroundColor = UIColor.clear
let heightConstraintB = buttonB.heightAnchor.constraint(equalToConstant: 135)
heightConstraintB.isActive = true
heightConstraintB.priority = UILayoutPriority(rawValue: 999)
buttonB.translatesAutoresizingMaskIntoConstraints = false
buttonB.setTitle("B", for: .normal)
buttonB.setTitleColor(UIColor.darkGray, for: .normal)
buttonB.backgroundColor = UIColor.clear
//self.popupView.addSubview(buttonB)
buttonC.backgroundColor = UIColor.clear
let heightConstraintC = buttonC.heightAnchor.constraint(equalToConstant: 135)
heightConstraintC.isActive = true
heightConstraintC.priority = UILayoutPriority(rawValue: 999)
buttonC.translatesAutoresizingMaskIntoConstraints = false
buttonC.setTitle("C", for: .normal)
buttonC.setTitleColor(UIColor.darkGray, for: .normal)
buttonC.backgroundColor = UIColor.clear
//self.popupView.addSubview(buttonC)
popupView.addSubview(stackView)
stackView.backgroundColor = UIColor.clear
stackView.addArrangedSubview(buttonA)
stackView.addArrangedSubview(buttonB)
stackView.addArrangedSubview(buttonC)
stackView.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: popupView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 428).isActive = true
stackView.axis = .vertical
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
}
#objc func buttonATapped(sender: UIButton) {
print ("test")
}
private func animateTransitionIfNeeded(to state: State, duration: TimeInterval) {
//Animation code
}
#objc private func popupViewPanned(recognizer: UIPanGestureRecognizer) {
//Animation code
}
}
***For anyone else having the same issue, here is how I solved it, thanks to #OverD:
I removed the reviewsImageView completely (because that was only for color in my case, and I can easily add the color to the UIButton instead) Then instead of adding the buttons to the popupView, I added them to the stack view, and the stack view to the popupView. Lastly, my syntax for addTarget was not correct, and for some reason changing it to touchDown instead of touchUpInside made it work.
I noticed from the provided code that you are adding the same buttons multiple times, once as a subview of the popView and once in the stackView. Also you are not assigning any targets for the buttons.
I hope this helps
Start app.
Choice of hour and secondButton. I want different hour.
I have two buttons and one DatePicker. set datePickerMode = .time.
I need to set two different hours on different buttons.
Unfortunately, I could not find solutions for this.
This is my code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(firstButton)
firstButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true
firstButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
firstButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
firstButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.addSubview(secondButton)
secondButton.topAnchor.constraint(equalTo: firstButton.bottomAnchor, constant: 10).isActive = true
secondButton.leftAnchor.constraint(equalTo: firstButton.leftAnchor).isActive = true
secondButton.rightAnchor.constraint(equalTo: firstButton.rightAnchor).isActive = true
view.addSubview(myDatePicker)
myDatePicker.topAnchor.constraint(equalTo: secondButton.bottomAnchor, constant: 10).isActive = true
myDatePicker.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10).isActive = true
myDatePicker.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
}
// MyFirstButton
let firstButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("First", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = .white
button.tag = 0
button.addTarget(self, action: #selector(handleButton), for: .touchUpInside)
return button
}()
// MySecondButton
let secondButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Second", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = .white
button.tag = 1
button.addTarget(self, action: #selector(handleButton), for: .touchUpInside)
return button
}()
// Target First and Second Button
#objc func handleButton() {
view.layoutIfNeeded()
UIView.animate(withDuration: 0.25, animations: {
self.view.layoutIfNeeded()
}) { (completed) in
UIView.animate(withDuration: 0.25, animations: {
self.myDatePicker.alpha = 1
self.view.layoutIfNeeded()
})
}
}
// DataPicker
let myDatePicker: UIDatePicker = {
let pv = UIDatePicker()
pv.timeZone = .current
pv.datePickerMode = .time
pv.backgroundColor = .white
pv.translatesAutoresizingMaskIntoConstraints = false
pv.addTarget(self, action: #selector(handleMyDatePicker), for: .valueChanged)
return pv
}()
// Target
#objc func handleMyDatePicker(sender: UIDatePicker) {
let formatter = DateFormatter()
formatter.timeStyle = .short
let selectedTime = formatter.string(from: sender.date)
firstButton.setTitle(selectedTime, for: .normal)
// here is the problem
secondButton.setTitle(selectedTime, for: .normal)
myDatePicker.alpha = 0
}
How to achieve this effect?
Set your myDatePicker tag to same with button, that you touched
#objc func handleButton(button: UIButton) {
view.layoutIfNeeded()
UIView.animate(withDuration: 0.25, animations: {
self.view.layoutIfNeeded()
}) { (completed) in
UIView.animate(withDuration: 0.25, animations: {
self.myDatePicker.alpha = 1
self.myDatePicker.tag = button.tag
self.view.layoutIfNeeded()
})
}
}
#objc func handleMyDatePicker(sender: UIDatePicker) {
let formatter = DateFormatter()
formatter.timeStyle = .short
let selectedTime = formatter.string(from: sender.date)
let button = self.view.viewWithTag(sender.tag) as? UIButton
button?.setTitle(selectedTime, for: .normal)
myDatePicker.alpha = 0
}
and small change in your addTarget code
button.addTarget(self, action: #selector(handleButton(button:)), for: .touchUpInside)