I am currently working with an UITableview which loads a users wall feed from Facebook. The problem I have is, the images that get put into the tableview load up fine but when the user scrolls, they turn completely black. Here is my cellForRow Method:
if ([[objectTypes objectAtIndex:indexPath.row] isEqualToString:#"photo"]) {
//this is a picture
if ([facebookPictureObjectIDs objectAtIndex:indexPath.row])
{
if ([sourceArray objectAtIndex:indexPath.row]) {
if ([[sourceArray objectAtIndex:indexPath.row] rangeOfString:[facebookPictureObjectIDs objectAtIndex:indexPath.row]].location != NSNotFound) {
imageCell.imageview.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[sourceArray objectAtIndex:indexPath.row]]]];
NSLog(#"source array objectwith image ID: %#",[facebookPictureObjectIDs objectAtIndex:indexPath.row]);
}else {
NSLog(#"Same Image object id is: %#", [facebookPictureObjectIDs objectAtIndex:indexPath.row]);
}
} else {
NSLog(#"Source Array object NIL NIL NIL");
}
}else {
NSLog(#"Else Called");
}
[imageCell layoutSubviews];
returnCell = imageCell;
}
Thanks for any help :)
Related
Hi I am creating a photo album app as a learning project and I am having some issues with the PHPhotoLibrary.shared().performChangesAndWait; the main issue is that when the method saveImage which has the PHPhotoLibrary.shared().performChangesAndWait code; the method exits before PHPhotoLibrary.shared().performChangesAndWait finishes saving the image. It seems that code gets execute asynchronously even when I am using the performChangesAndWait method which according to the documentation says it should wait until it finishes.
Here is the code of the saveImage method in my class:
func saveImage(_ image: UIImage) ->Bool {
print("Executing saveImage")
var result:Bool = false
if assetCollection == nil {
print("the album is nil")
return result
}
print("saveImage: we will start saving the image")
//TODO: the return false is happening before the photo is save.
do
{
try PHPhotoLibrary.shared().performChangesAndWait({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
print("saveImage: image was save without issues")
result = true
print("saveImage: result value after saving the image: \(result)")
})
}
catch let error
{
result = false
print("saveImage: there was a problem: \(error.localizedDescription)")
}
print("saveImage: result value before exiting the method: \(result)")
return result
}
Basically again when the app runs for the first time the return result gets execute before the saving the image block finishes.
Is there any way that I can force the saveImage method to actually wait until the performChangesAndWait method actually finishes?
Thanks in advance for your help.
performChangesAndWait is an async callback method. The changes made on result inside performChangesAndWait will not be reflected on return , as those two are in different threads.
One way to wait for saveImage method to finish is, implementing callback. Something like:
func saveImage(_ image: UIImage, callback: #escaping(_ result: Bool)->Void) {
print("Executing saveImage")
if assetCollection == nil {
print("the album is nil")
callback(false)
}
print("saveImage: we will start saving the image")
//TODO: the return false is happening before the photo is save.
do {
try PHPhotoLibrary.shared().performChangesAndWait({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
print("saveImage: image was save without issues")
callback(true)
})
}
catch let error {
print("saveImage: there was a problem: \(error.localizedDescription)")
callback(false)
}
}
Let us know what you got.
Here is the solution (Swift 5):
#objc private func handleSavePhoto() {
guard let previewImage = self.photoImageView.image else { return }
PHPhotoLibrary.requestAuthorization { (status) in
if status == .authorized {
do {
try PHPhotoLibrary.shared().performChangesAndWait {
PHAssetChangeRequest.creationRequestForAsset(from: previewImage)
}
} catch let error {
print("failed to save photo in library: ", error)
}
} else {
print("Something went wrong with permission...")
}
}
}
I am working with JSQMessagesViewController and have implemented three bubble colors. The extra color is designed to indicate an unapproved message in a moderated chat room.
I am running a Firebase backend and updating an approved flag when the chat message entries changes.
All is going well and the data is being changed real time. The problem is with the chat bubble colors, no matter what I do they will not change.
I've tried invalidating the layout, reloaddata, accessing the cell directly (comes up read only) and nothing seems to change the color other than leaving the chat view and coming back.
messageRef.observe(.childChanged, with: { (snapshot) in
let key = snapshot.key
if let dict = snapshot.value as? [String: AnyObject] {
let approved = (dict["approved"]?.boolValue ?? true)
let indexOfMesage = self.messages.index(where:{$0.key == key})
var message = self.messages[indexOfMesage!]
message.approved = approved
print(message)
self.collectionView.performBatchUpdates({ () -> Void in
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.reloadData()
}, completion:nil)
}
Any help would be appreciated. The code above is just one of many attempts.
Adding my "messageBubbleImageDataForItemAt" call for additional info after response below.
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.messageItem.senderId == senderId { // 2
if (message.approved == true){
return outgoingBubbleImageView
}else{
return outgoingUnnaprovedBubbleImageView
}
}else if (self.superUsers.contains(message.messageItem.senderId)){
return incomingAdminBubbleImageView
}else { // 3
if (message.approved == true){
return incomingBubbleImageView
}else{
return incomingUnnapprovedBubbleImageView
}
}
}
Invalidating the layout will not do anything for the color of the bubble. I would suggest that you modify you messageDataObject all you have to do is conform to the JSQMessageData protocol and I would add a property for a messages approval status.
I am going to assume that you have three different message bubbles defined
something like this
var incomingBubble: JSQMessagesBubbleImage!
var outgoingBubble: JSQMessagesBubbleImage!
var approvedBubble: JSQMessagesBubbleImage!
then in your view did load you actually define them.
incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
outgoingBubble = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.lightGray)
approvedBubble = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.red)
Then in the overridden function messageBubbleImageDataForItemAt you should provide logic for which bubble to use.
override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource {
//Sent by the current user
if messages[indexPath.item].senderId == self.senderId(){
return outgoingBubble
}
//Check if the message is approved from that property earlier.
if messages[indexPath.item].approved {
return approvedBubble
}
// its just a normal message return incoming message.
return incomingBubble
}
Other wise you need to fetch from firebase at this point if a message is approved or not.
Then calling self.collectionView.reloadData() should update your colors as long as you have pulled the latest from firebase.
I hope this helps you out. Let me know if you have more questions and keep on keeping on. 🖖🏽
I am looping through an array of string arrays. I am comparing the element at index 0 to the title of the button pressed on the page(ideally). But I am getting an Unresolved Identifier error, which means I am doing something wrong. How Can I compare the element in the loop to the title of the button made programatically on the page. Here is my code! The issue is with the function at the bottom, in the if statement that is in the for loop. I don't know how to say 'if this index position of the element is equal too the title of the button pressed'.
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
var songArray: [Array<String>] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//connect to website
let url = URL(string:"*******")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("error")
}
else
{
if let content = data
{
do
{
//download JSON data from php page, display data
let JSON = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [[String]]
print(JSON)
//Make buttons with JSON array
var buttonY: CGFloat = 20
for song in JSON {
self.songArray.append(song)
let SongButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // 50px spacing
SongButton.layer.cornerRadius = 10 //Edge formatting for buttons
SongButton.backgroundColor = UIColor.darkGray //Color for buttons
SongButton.setTitle("\(song[0])", for: UIControlState.normal) //button title
SongButton.titleLabel?.text = "\(song[0])"
SongButton.addTarget(self,action: #selector(self.songButtonPressed(_:)), for: UIControlEvents.touchUpInside) //button press / response
self.view.addSubview(SongButton) // adds buttons to view
}
}
catch
{
}
}
}
}
task.resume()
print(songArray)
}
func songButtonPressed(_ sender:UIButton!) { // function for buttons
for song in songArray {
if "\(song[0])" == SongButton.titleLabel?.text {
let URL = NSURL(string: "\(song[1])")
let player = AVPlayer(url: URL! as URL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
player.play()
}
}
}
}
My train of thought is to loop through the array of arrays, compare index position 0 to all button titles on the page, and if it matches, plus index position 2 into the AV player. Thanks for any help or advice on the logic behind my code, I am a beginner and I know this level of programming is a bit over my head
It's most likely
for song in songArray {
// the string interpolation "\()" is redundant
if song[0] == sender.titleLabel?.text { ... }
or maybe
if song[0] == sender.title { ... }
SongButton is not related to the action method.
I am using swift 3. When I hit a specific string while going through a for loop of iterated geojson data (jsonObj[i]) I need it to call the right image file. I am able to return the right pass type and load my markers in the map correctly. I just can't seem to figure out how to get the app to load the right color per marker type.
I have tried a switch statement but with no luck. I have looked at the if statement documentation among many other stack overflow posts and tutorials.
var pass : String?
var brown = UIImage(named: "brown.png")
var purple = UIImage(named: "purple.png")
var green = UIImage(named: "green.png")
var image : UIImage?
The block of code below is in the for loop. So each record that is iterated through loads that into a marker as a title and subtitle. With this code it reads the first marker color "purple" and loads all points with the purple marker instead of selecting based on pass1, pass2 that is returned.
var pass = jsonObj[i]["properties"]["Pass"]
print("propertiesPassString: ", pass)
if pass == "pass1" {
print("Pass1!")
image = purple
}
else if pass == "pass2" {
print("Pass2")
image = brown
}
else if pass == "" {
print("Pass3")
image = green
}
Here is the func that loads the marker, not sure if this is needed but it applies or loads the "image" or custom marker/variable in each iteration of the for loop. This is largely copy and pasted from mapbox ios sdk
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
// Always try to show a callout when an annotation is tapped.
return true
}
func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
// Try to reuse the existing ‘point’ annotation image, if it exists.
var annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: "point")
if annotationImage == nil {
print("annotationImage")
image = image?.withAlignmentRectInsets(UIEdgeInsets(top: 0, left: 0, bottom: (image?.size.height)!/2, right: 0))
// Initialize the ‘point’ annotation image with the UIImage we just loaded.
annotationImage = MGLAnnotationImage(image: image!, reuseIdentifier: "point")
}
//image = nil
return annotationImage!
}
If you have any advice/suggestions for a newer programmer in swift 3 I would certainly appreciate it. I've been stuck on this for several days now. I am still learning how to utilize stack overflow as well so please let me know if there is a better way to go about posting this question, I am open to constructive critiques.
I have a uitabbarcontroller with 4 tab bar items and each tab bar item has a uinavigationcontroller.
I needed to lock the orientation of one uitabbar item only to Portrait. So i implemented the following code:
Created a custom tab bar controller and added the following code in it:
MainTabBarController.m
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// You do not need this method if you are not supporting earlier iOS Versions
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
-(NSUInteger)supportedInterfaceOrientations
{
if (self.selectedViewController)
return [self.selectedViewController supportedInterfaceOrientations];
return UIInterfaceOrientationMaskPortrait;
}
-(BOOL)shouldAutorotate
{
return YES;
}
Created a custom navigation controller to use in one of the uitabbaritems and added the following code in it:
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
-(BOOL)shouldAutorotate
{
return YES;
}
and for the uiviewcontroller in the custom navigation controller i added the following code:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
The above code works fine. My issue is, if you go to the tabbar item (whose orientation is locked to Protrait) when the device is already in Landscape mode the orientation changes to Landscape. Can anyone please help me how to solve my issue.
Thanks,
Anand.
FYI!!!
I've found a way for my problem. I added the following function to the viewcontroller for which i want the display only in protrait mode:
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]))
{
if ([[UIDevice currentDevice] respondsToSelector:#selector(setOrientation:)])
{
int orientationPortrait = UIInterfaceOrientationPortrait;
NSMethodSignature *sig = [[UIDevice currentDevice] methodSignatureForSelector:#selector(setOrientation:)];
NSInvocation* invo = [NSInvocation invocationWithMethodSignature:sig];
[invo setTarget:[UIDevice currentDevice]];
[invo setSelector:#selector(setOrientation:)];
[invo setArgument:&orientationPortrait atIndex:2];
[invo invoke];
}
}
}