isHidden or view.removeFromSuperView are taking a long while - ios

I'm trying to show a UITableView from a button, I tried 2 different ways:
One by showing by a UIStackView with was hidden from the beginning, and just show with "isHidden", another way with a view from another UIViewController, which is called with "didMove(toParentViewController: self)"
With both ways, the tableview is showing instantly, but to hide it again, that's taking forever.
I've tried to put the "isHidden = true" in "DispatchQueue.main.sync" to use the main thread, but still not working as I would like...
Any suggestions?
Here is some code of my application :
#IBAction func ProfilPicture1Pressed(_ sender: UIButton) {
let popUpTeamDog = storyboard?.instantiateViewController(withIdentifier: "selectTeamDogPopUp") as! SelectTeamDogPopUp
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.addChildViewController(popUpTeamDog)
popUpTeamDog.view.frame = self.view.frame
self.view.addSubview(popUpTeamDog.view)
popUpTeamDog.didMove(toParentViewController: self)
}
})
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let dog: Dog
if(searching){
dog = searchArray[indexPath.row]
} else {
dog = dogs[indexPath.row]
}
let nameDog = dog.name
//let imgDog = UIImage(named: dog.image)
print(nameDog)
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.view.removeFromSuperview()
}
})
So I can add that when I click on the searchBar, then select a dog, the tableView / View disappear instantly, so when the keyboard is activate, that's working well... Stranger things....

Your code uses main thread by default. So you don't have to use this DispatchQueue.global() and DispatchQueue.main. It's too slow.
So replace this
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.addChildViewController(popUpTeamDog)
popUpTeamDog.view.frame = self.view.frame
self.view.addSubview(popUpTeamDog.view)
popUpTeamDog.didMove(toParentViewController: self)
}
})
with
self.addChildViewController(popUpTeamDog)
popUpTeamDog.view.frame = self.view.frame
self.view.addSubview(popUpTeamDog.view)
popUpTeamDog.didMove(toParentViewController: self)
and do the same with self.view.removeFromSuperview(). Replace this
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.view.removeFromSuperview()
}
})
with
self.view.removeFromSuperview()

Related

How do you change an image on one view controller from another view controller in Swift 5

I'm currently working on a project for an end of the year assignment and I need help to change a UIImage in my first view controller when the user clicks an item in a list view on the second view controller. I'm using a normal show segue to get to the menu when a button is clicked and this to get back:
Code on second view that goes back to the first view
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss (animated: true, completion: nil)
building = true
print("\(squareType)")
print("BUILDING = \(building)")
}
How do I get this to activate code on my first view to change images. I already have perimeters that run through both views to tell if it's been clicked and what to make based off of it, but I can't update my images
This is the code I want the other view to activate:
code on my first view that I want to trigger after the second view is dismissed
func farmCreator() {
for i in 0...24 {
if allCells[i].material == "Forest" && squareType == "Cabin" {
imageBottom[i].image = UIImage(named:"Farms/2")
}
if allCells[i].material == "Forest" && squareType == "Forest Mine" {
imageBottom[i].image = UIImage(named:"Farms/1") //Change
}
if allCells[i].material == "Rock" && squareType == "Mine" {
imageBottom[i].image = UIImage(named:"Farms/1")
}
if allCells[i].material == "Water" && squareType == "Fishmonger" {
imageBottom[i].image = UIImage(named:"Farms/3")
}
if allCells[i].material == "Water" && squareType == "Water Mine" {
imageBottom[i].image = UIImage(named:"Farms/1") //Change
}
if allCells[i].material == "Plains" && squareType == "Farm"{
imageBottom[i].image = UIImage(named:"Farms/4")
}
if allCells[i].material == "Plains" && squareType == "Plain Mine"{
imageBottom[i].image = UIImage(named:"Farms/1") //Change
}
}
}
Ignore how poorly optimized it is, I'm new
I've tried messing with all of the overrides. These things:
override func viewDidDisappear(_ animated: Bool) {
print("VIEW DID DIS V1")
}
override func viewWillDisappear(_ animated: Bool) {
print("VIEW WILL DIS V1")
}
override func viewDidAppear(_ animated: Bool) {
print("VIEW DID APP V1")
}
override func viewWillAppear(_ animated: Bool) {
print("VIEW WILL APP V1")
}
But they only work on the view that is disappearing with my dismiss and not the view that it moves to. Would I have to use an unwind? How would I implement that??
You need to use delegation here. I can't see all your code so I'll have to come up with different names for some things.
Step 1: Create a protocol on top of your ViewController 2 class
protocol ViewController2Protocol {
func didTapRow(squareType: String, material: String)
}
class ViewController2: UIViewController {
...
// Contents of ViewController 2
}
Step 2: Create a weak var delegate that's the type of Protocol declared above
protocol ViewController2Protocol {
func didTapRow(squareType: String, material: String)
}
class ViewController2: UIViewController {
weak var delegate: ViewController2Protocol? // 2. Add this
...
// Contents of ViewController 2
}
Step 3: Call your delegate method and pass the required parameters in didSelectRow before dismissing
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Assuming you have: var material: String, and var building: String
delegate?.didTapRow(material: material, building: building) // 3. Not sure what parameters you need to send to ViewController1. Just guessing.
dismiss (animated: true, completion: nil)
}
Step 4: Conform to, and implement the protocol in ViewController 1
class ViewController1: UIViewController, ViewController2Protocol { // Conform to protocol
func didTapRow(squareType: String, material: String) { // Implement it
// Call the method you want to get triggered by tapping here
farmCreator(squareType: squareType, building: building) // Change this method to accept the parameters as needed
}
}
Step 5. Make sure you set ViewController1 as the delegate wherever you're transitioning to ViewController2 in your ViewController1 class. Again not sure what your navigation code looks like so, add this there. For example
// Given ViewController 2
let vc2 = ViewController2()
vc2.delegate = self
I can give more detail in step 5 if I know how your transitioning:
Is your segue from a UI element in your storyboard?
Is your segue from ViewController1 to ViewController2 in your storyboard?

How to switch to another view controller if some value in a table row is updated?

I have a table view in one view controller, this contains an array of type items, so whenever some property like the beacon accuracy (shown below) of an item is updated(constantly updating), I want to be able to switch to another view controller. But i don't know which tableview delegate method should I use for this.
I tried to do it with didSelectRowAt method and that works but I want to be able to transition without selecting, just when the accuracy for an item is less than some value for a specific item i want to be able to transition.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = items[indexPath.row]
let beac = item.beacon
let acc = Double(beac!.accuracy)
if acc < 3.00 {
if let Level2 = self.storyboard!.instantiateViewController(withIdentifier: "ReportVC") as? UIViewController {
self.present(Level2, animated: true, completion: nil)
}
}
}
This works!
But need a tableview delegate method or some other way where I dont have to actually select a row, but it still performs the above.
You can call a function with a timer like this :
var timer = NSTimer()
override func viewDidLoad() {
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 60 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("updateCounting"), userInfo: nil, repeats: true)
}
func updateCounting(){
for item in items {
let beac = item.beacon
let acc = Double(beac!.accuracy)
if acc < 3.00 {
if let Level2 = self.storyboard!.instantiateViewController(withIdentifier: "ReportVC") as? UIViewController {
self.present(Level2, animated: true, completion: nil)
break
}
}
}
}
Here the update counting will be called every minute and in this function and if the accuracy is lesser then the second controller will be presented.
The timer duration you can decide which suits you best and if you are getting some event or delegate for the accuracy change then you can present the second view controller there also.
Hope this helps.
didSet property observer is used to execute some code when a property has just been set or changed.
Add a property observer didSet for the array items, and check the accuracy. If any beacon's accuracy is less than 3.00 you can present another view controller
var items: [CLBeacon] = [] {
didSet {
if items.contains(where: { $0.accuracy < 3.00 }) {
if let reportVC = self.storyboard?.instantiateViewController(withIdentifier: "ReportVC") {
self.present(reportVC, animated: true, completion: nil)
}
}
}
}
If you want to pass that particular beacon which has accuracy less than 3.0 to the new view controller, try this
var items: [CLBeacon] = [] {
didSet {
if let beacon = items.first(where: { $0.accuracy < 3.00 }) {
if let reportVC = self.storyboard?.instantiateViewController(withIdentifier: "ReportVC") {
reportVC.selectedBeacon = beacon
self.present(reportVC, animated: true, completion: nil)
}
}
}
}

How to transition to AVPlayerViewController from a custom view?

I have a custom view:
class MediaPlayerView: UIView {
var mediaURL: URL? {
didSet {
determineMediaType()
}
}
let videoExtensions = ["mov"]
override init (frame : CGRect) {
super.init(frame : frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func determineMediaType() {
let url = self.mediaURL
let pathExtention = url?.pathExtension
if videoExtensions.contains(pathExtention!) {
print("Movie URL: \(String(describing: url))")
setupVideo(url: url!)
} else {
print("Image URL: \(String(describing: url))")
setupImage(url: url!)
}
}
func setupVideo(url: URL) {
let playButton = UIImage(named: "Play Triangle")
let playButtonView = UIImageView(image: playButton!)
let singleTap = UITapGestureRecognizer(target: self, action: #selector(tapDetected))
playButtonView.addGestureRecognizer(singleTap)
playButtonView.center = self.center
playButtonView.frame.size.width = self.frame.size.width/5
playButtonView.frame.size.height = self.frame.size.height/5
playButtonView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
playButtonView.isUserInteractionEnabled = true
self.addSubview(playButtonView)
}
#objc func tapDetected() {
print("tap!")
let player = AVPlayer(url: self.mediaURL!)
let controller = AVPlayerViewController()
controller.player = player
self.window?.rootViewController?.present(controller, animated: true) {
player.play()
}
}
func setupImage(url: URL) {
let imageView = UIImageView()
imageView.frame = self.bounds
imageView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
self.addSubview(imageView)
imageView.kf.setImage(with: url)
}
}
However when I click the play button, I get the following error:
Warning: Attempt to present <AVPlayerViewController: 0x7fe8b200b000> on <SweatNet.MainTabBarController: 0x7fe8b4816400> whose view is not in the window hierarchy!
It comes on this line: self.window?.rootViewController?.present. I think It is getting confused by me calling the rootViewController (which seems to be SweatNet.MainTabBarController). I want to it to call SweatNet.TagViewController. This is the one which contains the cell which contains the custom MediaPlayerView, but I don't understand how to get a reference to this.
Try this:
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
Write your method as:
#objc func tapDetected() {
print("tap!")
let player = AVPlayer(url: self.mediaURL!)
let controller = AVPlayerViewController()
controller.player = player
topViewController().present(controller, animated: true) {
player.play()
}
}
This will return you top vc in hierarchy. Hope it helps!
I would be interested in some feedback on this answer as I think its an example of me thinking about the iOS system the wrong way. What I eventually realized was that I was trying to put view controller code inside of a custom view. View controller code being the code to launch the AVPlayerViewController. Rather I should put this logic in my view controller.
What I did which works is use the collectionView delegate method didSelectItemAt indexPath: IndexPath inside of the view controller where the view controller logic for the collection view is held like so:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.collectionView {
let post = posts[indexPath.row]
if post.isVideo == true {
self.performSegue(withIdentifier: "playVideo", sender: nil)
} else {
print("is image. might go full screen one day here")
}
}
This works and I believe it is better than writing a custom aspect of the view to launch the AVPlayerViewController. But it also could be that both approaches work - I really don't know enough about iOS development to make that call. Any comments are welcome.

Portrait/Landscape display ios swift 3

I have an application with 2 views.
On the first view, the user enter a login, hits on search button.
The application retrieves through an api information about the login,
is redirected on the second view where it displays information about the login.There is a back button on the second view to go back on the first view to be able to search a different login
In the second view, I treat both cases (Landscape/Portrait)
What happens is the following : first time I enter the login, Portrait and landscape cases are well treated on the result view.
When I go back and search a new login, It will only display the mode in which you entered the second time (if you entered in portrait mode, it will
only display in portrait mode, the same with landscape) Is there something that I should be doing ?
here is the code for the first view :
var login = ""
class FirstViewController: UIViewController {
#IBOutlet weak var Input: UITextField!
#IBAction func sendlogin(_ sender: UIButton) {
if let text = Input.text, !text.isEmpty {
login = text
performSegue(withIdentifier: "callaffiche", sender: self)
return
} else {
return
}
}
here is the code for the second view (I will only post viewDidLoad, the back button creation along its function and the creation of the imageuser as there are many objects. But all of them are created with the same principle ) AfficheElemts calls the creation of every single object( ButtonBackAffiche, ImageAffiche are called within AfficheElements
override func viewDidLoad() {
super.viewDidLoad()
print("ds viewdidload")
let qos = DispatchQoS.userInitiated.qosClass
let queue = DispatchQueue.global(qos: qos)
queue.sync {
self.aut.GetToken(completionHandler: { (hasSucceed) in
if hasSucceed {
print("good")
DispatchQueue.main.async {
self.AfficheElements()
}
} else {
print("bad")
}
})
}
}
func ButtonBackAffiche () {
let button = UIButton()
button.restorationIdentifier = "ReturnToSearch"
button.setTitle("Back", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.isEnabled = true
button.addTarget(self, action: #selector(ReturnToSearch(button:)), for: .touchUpInside)
if UIDevice.current.orientation.isPortrait {
button.frame = CGRect (x:CGFloat(180),y:CGFloat(600),width:50,height:30)
} else {
button.frame = CGRect (x:CGFloat(350),y:CGFloat(360),width:50,height:30)
}
self.view.addSubview(button)
}
func ImageAffiche () {
//Get image (Data) from URL Internet
let strurl = NSURL(string: image_url)!
let dtinternet = NSData(contentsOf:strurl as URL)!
let bongtuyet:UIImageView = UIImageView ()
if UIDevice.current.orientation.isPortrait {
print("portrait ds imageview")
bongtuyet.frame = CGRect (x:10,y:80,width:30,height:30)
} else {
print("landscape ds imageview")
bongtuyet.frame = CGRect (x:CGFloat(200),y:CGFloat(80),width:90,height:120)
}
bongtuyet.image = UIImage(data:dtinternet as Data)
self.view.addSubview(bongtuyet)
}
func ReturnToSearch(button: UIButton) {
performSegue(withIdentifier: "ReturnToSearch", sender: self)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("ds viewwilltransition")
if UIDevice.current.orientation.isPortrait {
print("portrait")
} else {
print("landscape")
}
while let subview = self.view.subviews.last {
subview.removeFromSuperview()
}
AfficheElements()
}
One last thing, I have put traces in the code and the creation of the objects, it goes through the right portion of the code but does not display the objects at the right locations.
Thanks #Spads for taking time to help me.
I finally figured it out. I was not calling the function AfficheElements
int the main queue.
DispatchQueue.main.async {
self.AfficheElements()
}
in viewWillTransition solved it.

UISwitch not working properly in swift

I have taken a UISwitch control in my new swift project. And the default state of this is set as Off from storyboard. I have bounded it as IBOutlet
#IBOutlet weak var SwitchRemFacility: UISwitch!
I have also bounded it to a method by Value change event from storyboard.
#IBAction func rememberFacilityClick(sender: AnyObject) {
print(SwitchRemFacility.on)
if SwitchRemFacility.on {
SwitchRemFacility.setOn(false, animated: true)
} else {
SwitchRemFacility.setOn(true, animated: true)
}
}
The Problem is that SwitchRemFacility.on returns always true here. I have checked by breakpoint and also I have printed it to console. Any hep will be appreciated.
Thanks in advance.
You are testing it when value is already changed. Switch will do it's on and off work by it self.
If you want to do it manually you can do like :
#IBAction func switchChangeVal(sender: AnyObject) {
let swh : UISwitch = sender as! UISwitch
if(swh.on){
swh.setOn(true, animated: true)//But it will already do it.
}
else{
swh.setOn(false, animated: true)
}
}
Actually no need of code. But if you want to condition on the on or off state then you have to do code.
For Example
#IBAction func switchChangeVal(sender: AnyObject) {
let swh : UISwitch = sender as! UISwitch
if(swh.on){
//Do the code for when switch is on
}
else{
//Do the code for when switch is off
}
}

Resources