UISegmentedControl: segment overlapping when pass data back - swift

thanks Duncan C. I edit my question:
i have a ViewControllerA with a 3 segments SegmentedControl, on ViewControllerB you can delete one segment and go back to VCA.
when I do so, I have, in VCA, 2 segments (the new segments after deletion) over the 3 segments (the 3 segments initially loaded).
VCA before change
VCA after change
I understand that it has to do with the view cycle, the initial ViewDidLoad persists, but even passing through viewWillAppear I still have this result.
this project is not a real project, just a test to solve the problem I have on my initial project.
my code for VCA:
class VCA: UIViewController, Info {
// array which contains the name for the segmentedControl
var array = ["Peter", "Bob", "Jim"]
var segmentedControl: UISegmentedControl!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setView(array: array)
}
override func viewDidLoad() {
super.viewDidLoad()
}
// delegate protocol: receive data from VCB
func passData(array: [String]) {
self.array = array
}
func setView(array: [String]) {
segmentedControl = UISegmentedControl(items: array)
segmentedControl.selectedSegmentIndex = 0
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("OK", for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 50
stackView.addArrangedSubview(segmentedControl)
stackView.addArrangedSubview(button)
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20)])
}
#objc func buttonTapped() {
let destinationVC = VCB()
destinationVC.array = array
destinationVC.infoDelegate = self
destinationVC.modalPresentationStyle = .fullScreen
present(destinationVC, animated: true, completion: nil)
}
}
my code for VCB:
class VCB: UIViewController {
// array which contains the name of the segments
var array = [String]()
// delegate to send data back
var infoDelegate: Info!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
setView(array: array)
}
func setLabel(array: [String]) -> String {
var labelText = "the array contains"
for name in array {
labelText += " \(name)"
}
return labelText
}
func setView(array: [String]) {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.text = setLabel(array: array)
view.addSubview(label)
view.backgroundColor = .cyan
NSLayoutConstraint.activate([
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor)])
let button = UIButton(type: .system)
button.setTitle("Delete the first name in the array", for: .normal)
button.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 40),
button.centerXAnchor.constraint(equalTo: label.centerXAnchor)])
}
#objc func backTapped() {
array.remove(at: 0)
infoDelegate.passData(array: array)
dismiss(animated: true, completion: nil)
}
}
with a protocol to send data back to VCB:
protocol Info {
func passData(array: [String])
}
any ideas/help?
thanks a lot

I gather VCA is view controller A and VCB is view controller B. You're saying that from VCB you delete one segment of VCA's segmented controller?
You should not mess with another view controller's views.
You will need to post your code and explain how the user navigates between view controllers in order for us to be able to help you.
(And by the way, those huge segmented controls look absurd. You should leave them at the standard height.)

I think I have found a solution. I remove all segments of the UISegment before repopulating it with the new data received from ViewController B.
func passData(array: [String]) {
self.array =array
segmentedControl.removeAllSegments()
segmentedControl = UISegmentedControl(items: array)
}
If the solution seems to work, it is not very elegant.

Related

Swift - UITapGestureRecognizer for parent view only

I have a UIViewController with a subview of a UIView. I add a UITapGestureRecognizer to the UIViewController to dismiss the view, but when I tap on the subview, the whole view still dismisses and I only want to dismiss when tapping the parent view.
var backgroundView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
let tap = UITapGestureRecognizer(target: self, action: #selector(didTapOutsideBackgroundView))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
setupUI()
}
func setupUI() {
view.addSubview(backgroundView)
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.layer.cornerRadius = 8
NSLayoutConstraint.activate([
backgroundView.topAnchor.constraint(equalTo: view.topAnchor, constant: distanceFromTop ?? 0),
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
backgroundView.backgroundColor = .systemBackground
// add subviews to backgroundView
backgroundView.addSubview(pageTitle)
backgroundView.addSubview(closeButton)
backgroundView.addSubview(itemLabel)
backgroundView.addSubview(itemNameLabel)
backgroundView.addSubview(priceLabel)
backgroundView.addSubview(quantityLabel)
backgroundView.addSubview(itemQuantityLabel)
backgroundView.addSubview(itemOriginalPriceLabel)
}
#objc func didTapOutsideBackgroundView(sender: UITapGestureRecognizer) {
print("called")
dismiss(animated: true)
}
You can find tapped view and ignore the tap if its a backgroundView with this in your didTapOutsideBackgroundView
#objc func didTapOutsideBackgroundView(sender: UITapGestureRecognizer) {
let location = sender.location(in: self.view)
if let view = self.view.hitTest(location, with: nil), let guester = view.gestureRecognizers {
if guester.contains(sender) {
print("Parent tapped")
dismiss(animated: true)
}
} else {
print("Ignore tap")
}
}
One solution can be check if it is subview or not & ignore based on that.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view.isDescendantOfView(yourSubView){ // this will check if this is subview of the main view or not..
return false
}
return true
}

userDefualt not saving switch when class is segued

My swift code below which is all code no storyboard. Tries to save a switch's state using user defaults. So when the user segues to twoViewController and then segues backs to ViewController. The users switch setting is not being saved. There are no errors presented and I don't know what is going on.
import UIKit
class ViewController: UIViewController {
var nxtBTn = UIButton()
var mySwitch = UISwitch()
let userDefaults = UserDefaults.standard
var firstTimeAppLaunch: Bool {
get {
// Will return false when the key is not set.
return userDefaults.bool(forKey: "firstTimeAppLaunch")
}
set {}
}
#objc func switchAction(_ sender: UISwitch) {
userDefaults.set(sender.isOn, forKey: "mySwitchValue")
}
#objc func press() {
let segue = twoViewController()
segue.modalPresentationStyle = .fullScreen // actually .fullScreen would be better
self.present(segue, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
[nxtBTn,mySwitch].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
$0.backgroundColor = .green
}
if !firstTimeAppLaunch {
// This will only be trigger first time the application is launched.
userDefaults.set(true, forKey: "firstTimeAppLaunch")
userDefaults.set(true, forKey: "mySwitchValue")
}
NSLayoutConstraint.activate([
nxtBTn.leadingAnchor.constraint(equalTo: view.leadingAnchor),
nxtBTn.topAnchor.constraint(equalTo: view.topAnchor),
nxtBTn.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 7/7, constant: 0),
nxtBTn.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4, constant: 0),
mySwitch.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mySwitch.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mySwitch.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 7/7, constant: 0),
mySwitch.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4, constant: 0),
])
mySwitch.addTarget(self, action: #selector(switchAction(_:)), for: .touchDown)
nxtBTn.addTarget(self, action: #selector(press), for: .touchDown)
view.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
mySwitch.isOn = userDefaults.bool(forKey: "mySwitchValue")
}
}
class twoViewController : UIViewController{
var backBtn = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
backBtn.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(backBtn)
backBtn.backgroundColor = .systemGreen
NSLayoutConstraint.activate([
backBtn.leadingAnchor.constraint(equalTo: view.leadingAnchor),
backBtn.topAnchor.constraint(equalTo: view.topAnchor),
backBtn.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 7/7, constant: 0),
backBtn.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4, constant: 0),
])
backBtn.addTarget(self, action: #selector(oneVC), for: .touchDown)
}
#objc func oneVC(){
let segue = ViewController()
segue.modalPresentationStyle = .fullScreen // actually .fullScreen would be better
self.present(segue, animated: true)
}
}
Everywhere you are saying for: .touchDown it is wrong. Change all of them to for: .primaryActionTriggered and things will improve.

How do I call a fromRootViewController method within a UIView class?

EDIT: This is a very beginner level question, so I apologise in advance
This is a sub part of a homework assignment I'm trying to crack but seems kind of impossible. I have implemented GADRewardBasedVideoAdDelegate to my UIView for Rewarded ads on Google Admob, and managed to implement all other instances of this delegate within "PopUp", which is a subclass of UIView.
However, this one method seems impossible to implement, which is:
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self)
}
where the issue is the ".present(fromRootViewController: self) portion of the code since the self refers not to a ViewController but to a UIView().
I honestly don't know how to tackle this situation, so I do not have anything to show for my attempts at this issue. I would really be grateful for any guidance that I can get on this topic, I've been stuck here for the better part of 2 days.
Here is the popUp class in case it helps make my code clearer
import UIKit
import GoogleMobileAds
class Popup: UIView, GADRewardBasedVideoAdDelegate {
func rewardBasedVideoAd(_ rewardBasedVideoAd: GADRewardBasedVideoAd, didRewardUserWith reward: GADAdReward) {
let vc = MainVC()
vc.lifeNumber += 3
vc.numberOfLivesLabel.text = "\(vc.lifeNumber)"
}
func rewardBasedVideoAdDidClose(_ rewardBasedVideoAd: GADRewardBasedVideoAd) {
GADRewardBasedVideoAd.sharedInstance().load(GADRequest(), withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
}
let playButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "icon"), for: .normal)
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
#objc func play() {
print("Play button pressed.")
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self) //<---------ERRONEOUS CODE
}
}
override init(frame: CGRect) {
super.init(frame: frame)
GADRewardBasedVideoAd.sharedInstance().load(GADRequest(), withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
GADRewardBasedVideoAd.sharedInstance().delegate = self
self.addSubview(popUpContainer)
playButton.centerXAnchor.constraint(equalTo: popUpContainer.centerXAnchor),
playButton.topAnchor.constraint(equalTo: popUpContainer.topAnchor, constant: 15),
playButton.heightAnchor.constraint(equalToConstant: 300)
playButton.widthAnchor.constraint(equalToConstant: 300)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Could not run init(coder)")
}
}
And here is my MainVC()
class MainVC: UIViewController {
var lifeNumber: Int = 3
lazy var numberOfLivesLabel: UILabel = {
let label = UILabel()
label.text = "\(lifeNumber)"
label.font = UIFont(name: "Copperplate", size: 25)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let buttonOne: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Hit me!", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
#objc func buttonPressed() {
let popUpView = Popup()
self.view.addSubview(popUpView)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(buttonOne)
view.addSubview(numberOfLivesLabel)
buttonOne.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
buttonOne.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
buttonOne.heightAnchor.constraint(equalToConstant: 300).isActive = true
buttonOne.widthAnchor.constraint(equalToConstant: 300).isActive = true
numberOfLivesLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
numberOfLivesLabel.bottomAnchor.constraint(equalTo: buttonOne.topAnchor, constant: 50).isActive = true
}
}
This code from hacking with swift will help.
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
Use the extension to get the popups parent view controller.
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self.findViewController())
}
You might want to put it on the main queue as well
if GADRewardBasedVideoAd.sharedInstance().isReady == true {
DispatchQueue.main.async {
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self.findViewController())
}
}

Going back to the previous page programatically

enter image description hereI am making a user login page for my IOS App. I am trying to make everything programmatically without using a segue in the main story board. I have managed to go to the next page as you can see in the code and create a custom left back bar item. But I don't know how to go back to the first page when I click on that button. I have attached the screen shot of my story board and app below. Can you please tell me how I can go back to the first page by clicking the X button?
Here is my code:
import UIKit
class FirstPage: UIViewController {
let hgt = UIScreen.main.bounds.height/2
private let loginSignup: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Login or Sign Up", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
button.setTitleColor(.green, for: .normal)
button.addTarget(self, action: #selector(loginSignupBtn), for: .touchUpInside)
return button
}()
#objc private func loginSignupBtn() {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "Login") as! Login
vc.loadViewIfNeeded()
self.navigationController?.pushViewController(vc, animated: true)
self.navigationController?.isNavigationBarHidden = false
let backItem = UIBarButtonItem(title: "X ", style: UIBarButtonItem.Style.done, target: nil, action: nil)
let font = UIFont.boldSystemFont(ofSize: 26)
backItem.setTitleTextAttributes([NSAttributedString.Key.font:font] ,for: .normal)
backItem.tintColor = UIColor.green
vc.navigationItem.leftBarButtonItem = backItem
}
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
}
override func viewWillAppear(_ animated: Bool) {
}
private func setupLayout() {
let topImageContainerView = UIView()
//topImageContainerView.backgroundColor = .yellow
view.addSubview(topImageContainerView)
//enable auto layout
topImageContainerView.translatesAutoresizingMaskIntoConstraints = false
topImageContainerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topImageContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
topImageContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
topImageContainerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
view.addSubview(loginSignup)
loginSignup.translatesAutoresizingMaskIntoConstraints = false
loginSignup.topAnchor.constraint(equalTo: view.topAnchor,constant:hgt/2).isActive = true
loginSignup.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
loginSignup.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
loginSignup.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
}
}
enter image description here
Here you need to add target and selector for when the x button is clicked
let backItem = UIBarButtonItem(title: "X ",
style: UIBarButtonItem.Style.done,
target: self, action:#selector(goBack))
#objc func goBack(_ bar:UIBarButtonItem) {
self.navigationController?.popToRootViewController(animated:true)
}

Buttons Programmatically on Swift 4

A quick question, I am trying to create a function called setupHeader() in which when called, setups up all the views and buttons of the header. The problem is with the Log Out button, for some reason I cannot get it right.
The first error is that the addTarget of the button doesn't recognize "self". Any suggestions?
The second one is that the #selector only works with the #objc func and the function cannot be in the main setupHeader function. Where should I put it? here is a snippet of my code:
import Foundation
import UIKit
func setupHeader(vc: UIViewController) {
let headerFiller: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = supaGray
return view
}()
let header: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = supaGray
return view
}()
let logOutButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Log Out", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .left
button.addTarget(self, action: #selector(handleLogOutButton), for: .touchUpInside)
return button
}()
func handleLogOutButton() {
}
vc.view.addSubview(header)
header.topAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.topAnchor).isActive = true
header.leftAnchor.constraint(equalTo: vc.view.leftAnchor).isActive = true
header.rightAnchor.constraint(equalTo: vc.view.rightAnchor).isActive = true
header.heightAnchor.constraint(equalToConstant: 40).isActive = true
header.addSubview(logOutButton)
logOutButton.centerYAnchor.constraint(equalTo: header.centerYAnchor, constant: -5).isActive = true
logOutButton.leftAnchor.constraint(equalTo: header.leftAnchor, constant: 7).isActive = true
logOutButton.widthAnchor.constraint(equalTo: header.widthAnchor, multiplier: 0.17).isActive = true
logOutButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
vc.view.addSubview(headerFiller)
headerFiller.topAnchor.constraint(equalTo: vc.view.topAnchor).isActive = true
headerFiller.leftAnchor.constraint(equalTo: vc.view.leftAnchor).isActive = true
headerFiller.rightAnchor.constraint(equalTo: vc.view.rightAnchor).isActive = true
headerFiller.bottomAnchor.constraint(equalTo: header.topAnchor).isActive = true
}
Declare your button press callback this way.
#objc func handleLogOutButton() {
}
And For self error use vc as your passing as argument above in function.
It looks like you're passing a view controller into your function so using self won't work. Try using 'vc'
Pretty straight forward:
Modify your code as shown below:
lazy var logOutButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Log Out", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .left
button.addTarget(self, action: #selector(handleLogOutButton), for: .touchUpInside)
return button
}()
#objc func handleLogOutButton() {
// Do your things here
}