iOS - UIButton retains background image - ios

I am presenting some buttons with big images. When the button is clicked, it is removed from the superview successfully. The button is also being de initialised. Still the image is kept in memory, and if several buttons are presented afterwards, the app crashes due to memory constraints. The button gets removed from the superview, but the image is not released.
Test Code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let button = UIButton(frame: view.bounds)
button.setBackgroundImage(getImage(), for: .normal)
view.addSubview(button)
button.addAction(UIAction { [weak button] _ in
button?.removeFromSuperview()
}, for: .touchUpInside)
}
func getImage() -> UIImage? {
if let path = Bundle.main.path(forResource: "super", ofType: "png") {
return UIImage(contentsOfFile: path)
}
return nil
}
}
I don't see any retain cycles there, for the image to be kept in memory.
And IMPORTANT: If I call button?.removeFromSuperview() from any other button or method, the image gets successfully released:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let buttonWithImage = UIButton(frame: view.bounds)
buttonWithImage.setBackgroundImage(getImage(), for: .normal)
view.addSubview(buttonWithImage)
let dismissingButton = UIButton(frame: view.bounds)
view.addSubview(dismissingButton)
dismissingButton.addAction(UIAction { [weak buttonWithImage] _ in
buttonWithImage?.removeFromSuperview()
}, for: .touchUpInside)
}
func getImage() -> UIImage? {
if let path = Bundle.main.path(forResource: "super", ofType: "png") {
return UIImage(contentsOfFile: path)
}
return nil
}
}
I am out of ideas for this issue. Any help is appreciated. Thanks.

Check if this works for you:
button.addAction(UIAction(handler: { action in
let button = action.sender as! UIButton
button.removeFromSuperview()
}), for: .touchUpInside)

Related

SKStoreProductViewController in Xcode 14.2 Simulator

Eventually, I want to incorporate SKStoreProductViewController into a project I'm working on so to start I thought I'd recreate what was posted here. The only thing is, when I click on my button in the simulator (xcode 14.2) no modal is appearing. Could it be because I need to run this on a real device or maybe because storeViewController.loadProduct is 'silently failing'? Any and all help is much appreciated!
My dummy app:
//
// ViewController.swift
// SKStoreProductViewController Demo
//
import UIKit
import StoreKit
class ViewController: UIViewController {
let testFlightAppURL = URL(string: "https://apps.apple.com/us/app/testflight/id899247664")!
let testFlightProductID = 899247664
private let button: UIButton = {
let button = UIButton()
button.backgroundColor = .darkGray
button.setTitle("Open App Store", for: .normal)
return button
}()
override func viewDidLoad() {
view.addSubview(button)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.frame = CGRect(x: 30,
y: view.frame.height-150-view.safeAreaInsets.bottom,
width: view.frame.size.width-60,
height: 55)
}
#objc func didTapButton() {
openAppStore()
}
func openAppStore() {
// 2. Create an SKStoreProductViewController instance and set its delegate
let storeViewController = SKStoreProductViewController()
storeViewController.delegate = self
// 3. Indicate a specific product by passing its iTunes item identifier
let parameters = [SKStoreProductParameterITunesItemIdentifier: testFlightProductID]
storeViewController.loadProduct(withParameters: parameters) { _, error in
if error != nil {
// In case there is an issue loading the product, open the URL directly
UIApplication.shared.open(self.testFlightAppURL)
} else {
self.present(storeViewController, animated: true)
}
}
}
}
// MARK: - SKStoreProductViewControllerDelegate
// 4. A simple implementation of SKStoreProductViewController.
// Delegate dismisses the view controller when the user completes the purchase.
extension ViewController: SKStoreProductViewControllerDelegate {
func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
viewController.dismiss(animated: true)
}
}
Testing on a real device resolved the issue for me.

How to change the default name of file to share in Swift?

everyone!
I'm new in Swift and I've run into a problem.
I have an app that show images and I want to share some. When I click "share icon" there is default name "JPEG image". How can I change it to something like "defaultImageName".
Image I want to share
Share using default share in iOS
And here's default name that I want to change "JPEG image"
I've tried something like
imageView.image = UIImage(named: "defaultImageName")
But it's not correct, cause imageView.image it's not that I want to change.
Here's my code. I think it should be in #objc func shareTapped(), but don't really know how to do it.
import UIKit
class DetailViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
var selectedImage: String?
var pictures = [String]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Picture \(pictures.firstIndex(of: selectedImage!)!+1) of \(pictures.count)"
navigationItem.largeTitleDisplayMode = .never //
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareTapped))
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.hidesBarsOnTap = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.hidesBarsOnTap = false
}
#objc func shareTapped() {
guard let image = imageView.image?.jpegData(compressionQuality: 0.8) else {
print("No image found")
return
}
let vc = UIActivityViewController(activityItems: [image], applicationActivities: [])
vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
present(vc, animated: true)
}
}

Swift White Screen upon returning from SafariWebController

When I open a safari view controller and then return to my app (when "Done" is pressed), my app renders a blank/white screen instead of the content for that view.
The code below is the code I have tried using in an empty view - the issue happens no matter where I try in my app.
import UIKit
import SafariServices
class SavedViewController: UIViewController, SFSafariViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let link = URL(string: "https://google.com"){
let myrequest = SFSafariViewController(url: link)
myrequest.delegate = self
present(myrequest, animated:true)
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated:true, completion: nil)
}
}
The website loads fine, its when I return to my app that the blank screen appears. Am I doing something wrong?
It works fine.
You just created new UIViewController this way, UIViewController by default have black background. So when you press Done you just come back from SafariViewController to you SavedViewController (UIViewController).
Probably You are looking for UIWebView solution https://developer.apple.com/documentation/uikit/uiwebview.
If u want only display SafariViewController do it as function from your ViewController, You don't need to create new File with UIViewController class to do it.
import UIKit
import SafariServices
class SavedViewController: UIViewController, SFSafariViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .gray
setupViews()
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated:true, completion: nil)
}
private func setupViews(){
view.addSubview(openSafariButton)
openSafariButton.addTarget(self, action: #selector(handleOpenSafariButtonTap), for: .touchUpInside)
openSafariButton.frame = CGRect(x: 0, y: 100, width: view.frame.width, height: 60)
}
#objc func handleOpenSafariButtonTap(){
if let link = URL(string: "https://google.com"){
let myrequest = SFSafariViewController(url: link)
myrequest.delegate = self
present(myrequest, animated:true)
}
}
let openSafariButton: UIButton = {
let button = UIButton()
button.setTitle("openSafari", for: .normal)
button.setTitleColor(.black, for: .normal)
button.backgroundColor = .red
return button
}()
}
That is what i mean.
You can add function:
private func openSafariLink(link: String){
if let link = URL(string: link){
let myrequest = SFSafariViewController(url: link)
present(myrequest, animated:true)
}
}
And call it from any place like that:
openSafariLink(link: "https://google.com")
This way fits more for Your solution.

UIBarButtonItem not Showing

So Im trying to create a UIBarButtonItem with a custom UIView by subclassing it like so.
import UIKit
import SnapKit
class LocationManager: UIBarButtonItem {
let createdView = UIView()
lazy var currentCityLabel: UILabel = {
let currentCityLabel = UILabel()
currentCityLabel.text = "Philadelphia, PA"
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
currentCityLabel.adjustsFontForContentSizeCategory = true
return currentCityLabel
}()
lazy var downArrow: UIImageView = {
let downArrow = UIImageView()
downArrow.contentMode = .scaleAspectFit
downArrow.image = UIImage(named: "downArrow")
return downArrow
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = createdView
createdView.addSubview(currentCityLabel)
currentCityLabel.snp.makeConstraints { (make) in
make.left.equalTo(createdView.snp.left)
make.top.bottom.equalTo(createdView)
}
createdView.addSubview(downArrow)
downArrow.snp.makeConstraints { (make) in
make.left.equalTo(currentCityLabel.snp.right).offset(5)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
However, when I create it and assign it in my viewController I see nothing
import UIKit
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
#objc func setupViews(){
guard let collection = collectionView else {
return
}
collection.backgroundColor = .white
let customLeftBar = LocationManager()
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've looked at other post and none seem to quite match my situation. I'm beginning to think it is because I didn't give the UIView a frame but I am not exactly sure how I would do that in this instance if that is the case. Anyone see anything I don't that could potentially help me solve this problem. Also setting a target doesn't work I tried two different ways and none of them triggers a thing
#objc func setupBarButtonItems(){
let customLeftBar = LocationManager()
customLeftBar.action = #selector(self.leftBarPressed)
customLeftBar.target = self
customLeftBar.customView?.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.leftBarPressed))
customLeftBar.customView?.addGestureRecognizer(tapGesture)
navigationItem.leftBarButtonItem = customLeftBar
}
#objc func leftBarPressed(){
print("left bar tapped")
}
Change your adding line from
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
to
self.navigationItem.leftBarButtonItem = customLeftBar
When add the barItem, you need to add it via navigationItem of the ViewController, not NavigationController
EDITED for add the action
Your custom UIBarButtonItem is a Custom View's BarButtonItem, so the target and selector will not working.
You can add your custom action by adding a button into your customView, and send the action via closure
Init your closure
var didSelectItem: (() -> Void)?
Add the create button code in your #objc func setupViews()
let button = UIButton(type: .custom)
createdView.addSubview(button)
button.snp.makeConstraints { (maker) in
maker.top.bottom.leading.trailing.equalTo(createdView)
}
// button.backgroundColor = UIColor.cyan // uncomment this line for understand about the barbuttonitem's frame
button.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
and add the function
#objc func didTap(_ button: UIButton) {
print("Did tap button")
}
In your viewController, you can get the tap action by
customLeftBar.didSelectItem = { [weak self] in
self?.leftBarPressed()
}
Unfortunately, your barbuttonitem's default frame is 30x30, so you must be set the frame for your barbuttonitem. If not, you can only catch the tap action in 30x30 area (uncomment the code for see it)

UIBarButtonItem changing image for Play/Pause button

I'm trying to make a play/pause button that toggles between custom images as it is pressed. Here is a snippet of the code.
Everything works fine but the button doesn't change. I've also tried using the .image property instead of the .setBackGroundImage method but then the button just goes blank on click.
Here is the code
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
var pauseImage:UIImage?
var playImage:UIImage?
#IBOutlet weak var btnPlayPause: UIBarButtonItem!
#IBOutlet weak var sldVolume: UISlider!
#IBAction func sldVolumeChanged(sender: AnyObject) {
audioPlayer.volume = sldVolume.value
}
#IBAction func actPressedPlayPause(sender: AnyObject) {
if audioPlayer.playing {
audioPlayer.pause()
btnPlayPause.setBackgroundImage(playImage, forState: .Normal, barMetrics: .Default)
} else {
audioPlayer.play()
btnPlayPause.setBackgroundImage(pauseImage, forState: .Normal, barMetrics: .Default)
}
}
#IBAction func actStopPressed(sender: AnyObject) {
audioPlayer.stop()
audioPlayer.currentTime = 0
}
override func viewDidLoad() {
super.viewDidLoad()
var pauseImagePath = NSBundle.mainBundle().pathForResource("pause", ofType: "png")
pauseImagePath = pauseImagePath!.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
pauseImage = UIImage(contentsOfFile: pauseImagePath!)
var playImagePath = NSBundle.mainBundle().pathForResource("play", ofType: "png")
playImagePath = playImagePath!.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
playImage = UIImage(contentsOfFile: pauseImagePath!)
var audioPath = NSBundle.mainBundle().pathForResource("minuetcminor", ofType: "mp3")
audioPath = audioPath!.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
var error : NSError? = nil
audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath!), error: &error)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
had the same problem here! Had two of us confused as to why it wasn't working, I think it's a change in Swift 2/Xcode 7 beta
Just do this:
btnPlayPause.image = UIImage(named: "myImage.png")
Hope that helps!
Seems like this question has got a lot of strange answers. The truth is there's no non-hacky way to change an image in already existing UIBarButtonItem. Instead, unfortunately, you have to re-create it.
I've made a helper method to do this:
func changeBarButtonItemImage(_ item: UIBarButtonItem, image: UIImage, navItem: UINavigationItem) -> UIBarButtonItem? {
let buttonItem = UIBarButtonItem(image: image, style: item.style, target: item.target, action: item.action)
buttonItem.isEnabled = item.isEnabled
if let leftIndex = navItem.leftBarButtonItems?.index(of: item) {
var items: [UIBarButtonItem] = navItem.leftBarButtonItems!
items[leftIndex] = buttonItem
navItem.leftBarButtonItems = items
return buttonItem
}
if let rightIndex = navItem.rightBarButtonItems?.index(of: item) {
var items: [UIBarButtonItem] = navItem.rightBarButtonItems!
items[rightIndex] = buttonItem
navItem.rightBarButtonItems = items
return buttonItem
}
return nil
}
Usage:
if let image = UIImage(named: showingFilter ? "icon_filter_active.png" : "icon_filter.png") {
if let buttonItem = changeBarButtonItemImage(self.filterButton, image: image, navItem: navigationItem) {
self.filterButton = buttonItem
}
}
Take note that I also copy isEnabled property, because, in my case, in some situations, I was changing button icon while in the disabled state.
The #IBAction should use sender as UIButton instead of AnyObject or Any. Also, once that is done, the button can be directly accessed from inside the function using sender. (This is Swift 3)
#IBAction func actPressedPlayPause(sender: UIButton) {
if audioPlayer.playing {
audioPlayer.pause()
sender.setBackgroundImage(UIImage(named: "playimage.png"), for: .normal)
} else {
audioPlayer.play()
sender.setBackgroundImage(UIImage(named: "pauseimage.png"), for: .normal)
}
}
Try using below line of code:
btnPlayPause.setImage(image, forState: .Normal)
this works for me.

Resources