Calling a function to the alter constraints using UITapGestureRecognizer in Swift - arrays

This code has a button when pressed a large blue box becomes a small blue box. How can the code be written so that when the blue box is touched it will transform into the small box without the need for a button.
import UIKit
class ViewController: UIViewController {
let colorview = UIView()
var initialc = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
colorview.translatesAutoresizingMaskIntoConstraints = false
colorview.backgroundColor = UIColor.blueColor()
self.view.addSubview((colorview))
let leadingc = colorview.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor)
let trailingC = colorview.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor)
let topc = colorview.topAnchor.constraintEqualToAnchor(self.view.topAnchor)
let bottomc = colorview.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor, constant: -50)
initialc.appendContentsOf([leadingc,trailingC,topc,bottomc])
NSLayoutConstraint.activateConstraints(initialc)
}
#IBAction func changethebleep(sender: AnyObject) {
NSLayoutConstraint.deactivateConstraints(initialc)
let widthc = colorview.widthAnchor.constraintEqualToConstant(100)
let heightc = colorview.heightAnchor.constraintEqualToConstant(100)
let centerxc = colorview.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor)
let centeryc = colorview.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor)
NSLayoutConstraint.activateConstraints([widthc,heightc,centerxc,centeryc])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

In viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target:self, action:#selector(ViewController.myFunction(_:)))
colorview.userInteractionEnabled = true
colorview.addGestureRecognizer(tapGestureRecognizer)
In myFunction()
func myFunction(sender: AnyObject) {
NSLayoutConstraint.deactivateConstraints(initialc)
let widthc = colorview.widthAnchor.constraintEqualToConstant(100)
let heightc = colorview.heightAnchor.constraintEqualToConstant(100)
let centerxc = colorview.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor)
let centeryc = colorview.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor)
NSLayoutConstraint.activateConstraints([widthc,heightc,centerxc,centeryc])
}
By adding a tap gesture recognizer, you do not need a button. The tap gesture calls the function that handles the constraints. I've tested the code in my own project and it all works as it should.
Good Luck!

Related

Removing items from array in custom tableViewCell

I have a custom tableviewHeaderFooterView where I set up a target event for the button in the custom tableViewCell class (checkButton is the button and its background image changes to a checkmark when a user clicks on it).
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let userModel = Data.userModels[section]
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId") as! SectionHeader
cell.setup(model: userModel)
cell.checkButton.tag = section
cell.checkButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
return cell.contentView
}
And in that function I want to create or remove items from an array depending on whether the user taps on a cell or not (i.e. if they tap on the button, then add something to the array, but if they tap on that button again, then remove that object from the array.)
#objc func handleTap(sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected == true {
let model = ItemModel(itemName: item, price: price)
ItemModelFunctions.createItem(for: sender.tag, using: model)
}
if sender.isSelected == false {
ItemModelFunctions.removeFromUser(from: sender.tag)
}
print(sender.tag)
}
Here are the createItem and removeFromUser functions:
struct ItemModelFunctions {
static func createItem(for userIndex: Int, using itemModel: ItemModel) {
Data.userModels[userIndex].itemModels.append(itemModel)
}
static func removeFromUser(from userIndex: Int) {
Data.itemModels.remove(at: userIndex)
}
}
When I tap on the button twice to remove it from the array, I get an error saying Data.itemModels.remove(at: userIndex) is out of range.
I know using a prototype cell for a tableViewHeaderFooterView isn't exactly the correct way, but I've seen other programmers and YouTubers do this with success. Are my issues coming from using a prototype cell? Or am I removing the item from the array in the wrong way? Thank you all for your help!
// Please maintain one more array i.e selectedIndexArray and follow below code.
var selectedIndexArray = [Integer]()
#IBAction func buttonTapped(_ sender: UIButton) {
let button = sender
if selectedIndexArray.contains(button.tag) {
button.tag --> Remove this tag from selectedIndexArray
let model = ItemModel(itemName: item, price: price)
ItemModelFunctions.createItem(for: sender.tag, using: model)
} else
{
selectedIndexArray.append(button.tag)
ItemModelFunctions.removeFromUser(from: sender.tag)
}
//reload tableview.
self.tableView.reloadData()
}
The checkButton.addTarget function will run each time your section header is reused.
Then it will duplicate event for cell when reuse many times.
I think you should not use this solution. Instead of that, I think you should write delegate way to solve your problem.
Ex:
protocol SectionHeaderDelegate: class {
func checkButtonDidTap(section: SectionHeader)
}
class SectionHeader: UITableViewCell {
weak var delegate: SectionHeaderDelegate
#IBOutlet weak var checkButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
checkButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
}
func handleTap() {
self.delegate.checkButtonDidTap(section: self)
}
}
and you set cell.delegate = self in viewForHeaderInSection function. And implement protocol SectionHeaderDelegate.
Thanh Vu's solution works. Another solution would be to add an IBAction to your ViewController:
#IBAction func buttonTapped(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
let cell = sender.superview?.superview as! SectionHeader
if let indexPath = mainTableView.indexPath(for: cell) {
// if sender.isSelected etc...
}
}

Multiple Switches in an Array are activated when only one is pressed Swift

I am working on an attendance app that uploads and stores all of its data in firebase. Most things are currently working except that sometimes when a switch is pressed in the array of students in the club, 2 of the switches activate in the array but the information normally sent when a switch is pressed is only sent for the switch that I actually pressed.
Here is the TableView cell code:
import UIKit
import Firebase
class StudentCell: UITableViewCell {
var student: Student? { didSet { self.updateUI() }}
#IBOutlet weak var lblStudentName: UILabel!
#IBOutlet weak var lblStudentId: UILabel!
#IBOutlet weak var lblStudentAttending: UISwitch!
let ref = Database.database().reference()
var user: Student! {
didSet{
lblStudentName.text = user.FullName()
lblStudentId.text = user.StudentId
lblStudentAttending.isOn = false
}
}
var switchReference: DatabaseReference?
var switchHandle: DatabaseHandle?
func stopObservation() {
if let handle = switchHandle {
switchReference?.removeObserver(withHandle: handle)
}
}
func startObservation() {
stopObservation()
if let uid = student?.StudentId {
switchReference = ref.child("attendance").child(Global.shared.currentDate).child(uid)
switchReference?.observe(.value, with: { [weak self] (snapshot: DataSnapshot) in
DispatchQueue.main.async {
let isOn = (snapshot.value as? Bool) ?? false
self?.lblStudentAttending.isOn = isOn
}
})
}
}
func updateUI() {
startObservation()
self.lblStudentName.text = self.student?.FullName() ?? ""
self.lblStudentId.text = self.student?.StudentId ?? ""
}
#IBAction func `switch`(_ sender: UISwitch) {
switchReference?.setValue(sender.isOn)
}
}
Does anyone know why this might be happening? If you need more information lmk. TIA -Ben
Does anyone know why this might be happening?
Tables reuse their cells so that they don't have to keep allocating new ones, which is expensive when the user is trying to scroll through a lot of rows quickly. You're setting up an observer for the switch in each cell, which means that you need to stop observing that switch when the cell is no longer visible. You could probably call stopObservation() in the cell's prepareForReuse() method and solve the problem.
A better solution would be to avoid observing the switch altogether, and instead use the target/action mechanism that controls typically use to trigger an action when the user changes their value. That way you don't have to worry about constantly starting and stopping observation.

Closure array parameter in function

I need to make a function that takes in an array of buttons and an array of closures. In the end, I am aiming to attach to each button an action from the array, and set up each button in my notification. Here's my function:
func makeButtons(buttons: [UIButton], withActions actions: [() -> Void]){
var bottomAnchor: NSLayoutYAxisAnchor?
bottomAnchor = notificationView.bottomAnchor
buttons.forEach({ (button) in
button.actionHandle(controlEvent: .touchUpInside, action: actions[buttons.index(of: button)!])
notificationView.addSubview(button)
button.bottomAnchor.constraint(equalTo: bottomAnchor!, constant: -20).isActive = true
button.rightAnchor.constraint(equalTo: notificationView.rightAnchor, constant: -20).isActive = true
button.leftAnchor.constraint(equalTo: notificationView.leftAnchor, constant: 20).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
bottomAnchor = button.topAnchor
})
}
I am indeed using an extension to be able to add a closure for UIButton target using actionHandle(). I got it off Stack. Here's that:
extension UIButton {
private func actionHandleBlock(action:(() -> Void)? = nil) {
struct __ {
static var action :(() -> Void)?
}
if action != nil {
__.action = action
} else {
__.action?()
}
}
#objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}
func actionHandle(controlEvent control: UIControlEvents, action:#escaping () -> Void) {
self.actionHandleBlock(action: action)
self.addTarget(self, action: #selector(triggerActionHandleBlock), for: control)
}
}
Also, here's how I'm using the make Buttons function:
func setButtonsWithAction(){
let button1 = UIButton()
let button2 = UIButton()
addButtons(buttons: [button1, button2], withActions: [self.sayHi, self.sayGoodbye])
}
func sayHi(){
print("Hi there")
}
func sayGoodbye(){
print("Goodbye")
}
All sets up ok, just as I want. However, the problem I am facing is that regardless on which button I click, it performs the last function from my closure array. So in this case it prints "Goodbye" regardless of which button I click.
What I need is:
click button1 -> print Hi
click button2 -> print Goodbye
What is the problem, I can't seem to figure this out.
This should definitely show you the way.
Let me know how it goes.
fileprivate func toolbarButton(title: String, closure: Selector ) -> UIButton {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.tintColor = .darkGray
button.addTarget(self, action: closure, for: .touchUpInside)
return button
}
use like this:
lazy var nearButton = toolbarButton(title: "Near Me", closure: #selector(handleToolButtonNear))

How can I show my whole array in DetailView

I am working on a ToDo List application. Currently, the ToDo list shows in Table View as a list and the Detail View shows only the first item, a completed switch and the date completed. I want my Detail View to show all of the items and dates completed, not just the first item.
My app delegate looks like this:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers.first as! UINavigationController
let masterViewController = navigationController.topViewController as! MasterViewController
let detailViewController = splitViewController.viewControllers.last as! DetailViewController
let firstItem = masterViewController.listManager.getFirstItem()
detailViewController.toDoItem = firstItem
return true
}
Now, I've tried to change firstItem to toDoItem to show my whole array but I am getting an error:
Cannot assign value of type '[ToDoItem]' to type 'ToDoItem!'
Also, what to I create in Detail View to accommodate an every changing array?
This is my current Detail View:
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var dateAddedLabel: UILabel!
#IBOutlet weak var completedSwitch: UISwitch!
var toDoItem: ToDoItem! {
didSet(newItem) {
self.refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
refreshUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func refreshUI() {
if(toDoItem == nil) {
return
}
descriptionLabel?.text = toDoItem.itemDescription
completedSwitch?.setOn(toDoItem.completed, animated: true)
dateAddedLabel?.text = toDoItem.getDateAdded()
}

AVAudioPlayer using array to queue audio files - Swift

I am looking for a way to simply play audio files one after another.
AVAudioPlayer using an array seems to be the best solution. In fact, I was able to play the first element of the array using Sneak's recommendations found on this page : Here.
But I don't understand where and how to write the second call to AVAudioPlayer in order to play the second file?
The "audioPlayerDidFinishPlaying" function is not reacting. Why?
Thanks for watching.
import Cocoa
import AVFoundation
var action = AVAudioPlayer()
let path = Bundle.main.path(forResource: "test.aif", ofType:nil)!
let url = URL(fileURLWithPath: path)
let path2 = Bundle.main.path(forResource: "test2.aif", ofType:nil)!
let url2 = URL(fileURLWithPath: path2)
let array1 = NSMutableArray(array: [url, url2])
class ViewController: NSViewController
{
#IBOutlet weak var LanceStop: NSButton!
override func viewDidLoad()
{
super.viewDidLoad()
do
{
action = try AVAudioPlayer(contentsOf: array1[0] as! URL)
action.numberOfLoops = 0
action.prepareToPlay()
action.volume = 1
}catch{print("error")}
}
...
#IBAction func Lancer(_ sender: NSButton)
{
if action.isPlaying == true
{
action.stop()
action.currentTime = 0.0
LanceStop.title = "Lancer"
}
else
{
action.play()
LanceStop.title = "Stopper"
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
if flag == true
{
LanceStop.title = "Lancer"
}
}
}
But I don't understand where and how to write the second call to
AVAudioPlayer in order to play the second file?
So in order to play the second file, you need to write one method where you will need to initialize the audioplayer and invoke the same method inside audioplayer delegate method audioPlayerDidFinishPlaying like this:-
func playAudioFile(_ index: Int) {
do
{
action = try AVAudioPlayer(contentsOf: array1[index] as! URL)
action.numberOfLoops = 0
action.prepareToPlay()
action.volume = 1
} catch{print("error")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
//This delegate method will called once it finished playing audio file.
//Here you can invoke your second file. You can declare counter variable
//and increment it based on your file and stop playing your file acordingly.
counter = counter + 1
playAudioFile(counter)
}
Note:- Set Audioplayer delegate to your ViewController in order to get invoke
audioPlayerDidFinishPlaying method like this.
action.delegate = self
Changed Code...
class ViewController: NSViewController, AVAudioPlayerDelegate
{
#IBOutlet weak var LanceStop: NSButton!
override func viewDidLoad()
{
super.viewDidLoad()
}
override var representedObject: Any?
{
didSet
{
// Update the view, if already loaded.
}
}
func playAudioFile(_ index: Int)
{
do
{
action = try AVAudioPlayer(contentsOf: array1[index] as! URL)
action.delegate = self
action.numberOfLoops = 0
action.prepareToPlay()
action.volume = 1
action.play()
}
catch{print("error")}
}
#IBAction func Lancer(_ sender: NSButton)
{
playAudioFile(0)
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
if flag == true
{
playAudioFile(1)
}
}
}

Resources