How to send Google Pay or Bhim UPI request via app . I know about this but I want to send payment request

try{
            Uri uri = Uri.parse("upi://pay?pa="+payeeAddress+"&pn="+payeeName+"&tn="+transactionNote+
                    "&am="+amount+"&cu="+currencyUnit);
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            startActivityForResult(intent,1);}

catch (Exception e){
                Toast.makeText(MainActivity.this, "Sorry! Please Install GPAY or BHIM", Toast.LENGTH_SHORT).show();e.printStackTrace();}
        }

I am using a Broadcast Receiver to intercept incoming messages. I have passed the intent data from the OnReceive method to a WorkManager class. I want to get a SmsMessage from the pdu but since the data being received on the doWork method is String even the pdu object is a string therefore this becomes hard to create an SmsMessage object from such PDU. Below is my code for the Broadcast receiver;

      if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
           final Bundle data = intent.getExtras();



            if (data != null){
                Data.Builder dataBuilder = new Data.Builder();
                for ( String key : data.keySet()){
                    dataBuilder.putString(key,String.valueOf(data.get(key)));
                    Log.d(TAG, "onReceive:  key:" +key+ " and keyValue "+data.get(key));
                }
                WorkManager mWorkManager = WorkManager.getInstance();
                OneTimeWorkRequest mRequest = new OneTimeWorkRequest
                        .Builder(MessageSyncWorker.class)
                        .setInputData(dataBuilder.build())
                        .build();
                mWorkManager.enqueue(mRequest);

                Log.d(TAG, "onReceive: Data: " +data);

            }

```


This is what am doing 
on the WorkManager class

```
public class MessageSyncWorker extends Worker {

    String sender, timestamp, msgID, text_message;
    private static final String TAG = "MessageSyncWorker";
    private static int counter = 0;
    public MessageSyncWorker(@NonNull Context context,
                             @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        Data inputData = getInputData();
        Log.d(TAG, "doWork: " +" inputdata "+inputData +
                " DataValueMAP "+inputData.getKeyValueMap());

        Bundle extras = new Bundle();
        for (String key : inputData.getKeyValueMap().keySet()) {
//      extras.putString(key,String.valueOf(inputData.getString(key))); //This also works well
            extras.putString(key,inputData.getString(key));
            Log.d(TAG, "doWork: ExtrasKeySet " +extras.keySet());
            Log.d(TAG, "doWork: Extra Extra: "+inputData.getString(key));

        }
        Log.d(TAG, "doWork:  Extras: Tena "+extras);

        processBundleData(extras);

        return Result.success();
    }

    private void processBundleData (Bundle bundle){
        if (bundle !=null){
            Object[] newpdusObj = new Object[] {  bundle.get("pdus") };
            Log.d(TAG, "processBundleData: Bundle "+newpdusObj);
            if (newpdusObj !=null){
                for (int i = 0; i < newpdusObj.length; i++){
                    SmsMessage currentMessage =
                            SmsMessage.createFromPdu((byte[]) newpdusObj[i]);
                    Log.d(TAG, "processBundleData: CurrentSMS "+currentMessage);

                }
            }

        }

    }

```

Below is my logs showing the Exception,
```
2019-10-21 19:53:34.597 2364-2532/com.techweezy.smartsync D/MessageSyncWorker: processBundleData: Bundle [Ljava.lang.Object;@38e5886
2019-10-21 19:53:34.837 2364-2415/com.techweezy.smartsync E/WM-WorkerWrapper: Work [ id=66ed12df-eb08-4714-9360-932ad2280873, tags={ com.techweezy.smartsync.utils.MessageSyncWorker } ] failed because it threw an exception/error
    java.util.concurrent.ExecutionException: java.lang.ClassCastException: java.lang.String cannot be cast to byte[]
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:290)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to byte[]
        at com.techweezy.smartsync.utils.MessageSyncWorker$override.processBundleData(MessageSyncWorker.java:74)
        at com.techweezy.smartsync.utils.MessageSyncWorker$override.doWork(MessageSyncWorker.java:63)
        at com.techweezy.smartsync.utils.MessageSyncWorker$override.access$dispatch(Unknown Source:119)
        at com.techweezy.smartsync.utils.MessageSyncWorker.doWork(Unknown Source:12)

TextView count, trye, timer;

Button btn_one, btn_two, btn_three, btn_four,back;
ImageView tv_question;
int counter = 1;
int next = 1;
int tries = 3;
CountDownTimer yourCountDownTimer;
private Animalquestions question = new Animalquestions();
private String answer;
private int questionLength = question.questions.length;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_animalquiz);

    count = findViewById(R.id.count);
    btn_one = (Button) findViewById(R.id.btn_one);
    btn_one.setOnClickListener(this);
    btn_two = (Button) findViewById(R.id.btn_two);
    btn_two.setOnClickListener(this);
    btn_three = (Button) findViewById(R.id.btn_three);
    btn_three.setOnClickListener(this);
    btn_four = (Button) findViewById(R.id.btn_four);
    trye = findViewById(R.id.tries);
    timer = findViewById(R.id.timer);
    btn_four.setOnClickListener(this);
    back = findViewById(R.id.back);

    tv_question = (ImageView) findViewById(R.id.tv_question);

    back.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            finish();
            return;
        }
    });
    yourCountDownTimer = new CountDownTimer(15000, 1000) {

        public void onTick(long millisUntilFinished) {
            timer.setText("" + millisUntilFinished / 1000);
        }

        public void onFinish() {
            TryAgain();

        }

    };
    NextQuestion(0);
    yourCountDownTimer.start();

}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_one:
            if (btn_one.getText() == answer) {
                Toast.makeText(Animalquiz.this, "You Are Correct", Toast.LENGTH_SHORT).show();
                if (next >= questionLength) {
                    Finished();
                } else {
                    yourCountDownTimer.cancel();
                    yourCountDownTimer.start();
                    NextQuestion(next);
                    next++;
                    counter++;
                    trye.setText(" Tries  " + String.valueOf(tries));
                    count.setText("Score  " + String.valueOf(counter));
                }
            } else if (tries <= 0) {
                GameOver();
            } else {
                TryAgain();
            }

            break;

        case R.id.btn_two:
            if (btn_two.getText() == answer) {
                Toast.makeText(Animalquiz.this, "You Are Correct", Toast.LENGTH_SHORT).show();
                if (next >= questionLength) {
                    Finished();
                } else {
                    yourCountDownTimer.cancel();
                    yourCountDownTimer.start();
                    NextQuestion(next);
                    next++;
                    counter++;
                    trye.setText(" Tries  " + String.valueOf(tries));
                    count.setText("Score  " + String.valueOf(counter));
                }
            } else if (tries <= 0) {
                GameOver();
            } else {
                TryAgain();
            }

            break;

        case R.id.btn_three:
            if (btn_three.getText() == answer) {
                Toast.makeText(Animalquiz.this, "You Are Correct", Toast.LENGTH_SHORT).show();
                if (next >= questionLength) {
                    Finished();
                } else {
                    yourCountDownTimer.cancel();
                    yourCountDownTimer.start();
                    NextQuestion(next);
                    next++;
                    counter++;
                    trye.setText(" Tries  " + String.valueOf(tries));
                    count.setText("Score  " + String.valueOf(counter));
                }
            } else if (tries <= 0) {
                GameOver();
            } else {
                TryAgain();
            }

            break;

        case R.id.btn_four:
            if (btn_four.getText() == answer) {
                Toast.makeText(Animalquiz.this, "You Are Correct", Toast.LENGTH_SHORT).show();
                if (next >= questionLength) {
                    Finished();
                } else {
                    yourCountDownTimer.cancel();
                    yourCountDownTimer.start();
                    NextQuestion(next);
                    next++;
                    counter++;
                    trye.setText(" Tries  " + String.valueOf(tries));
                    count.setText("Score  " + String.valueOf(counter));
                }
            } else if (tries <= 0) {
                GameOver();
            } else {
                TryAgain();
            }
            break;
    }
}

private void TryAgain() {
    yourCountDownTimer.cancel();
    final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(Animalquiz.this);
    alertDialogBuilder
            .setMessage("You have " + tries + " tries left")
            .setCancelable(false)
            .setPositiveButton("try again", new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    yourCountDownTimer.start();
                    tries--;

Fruitquiz.class)); // }

                }
            })
            .setNegativeButton("Exit", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    System.exit(0);
                }
            });
    alertDialogBuilder.show();
}

private void GameOver() {
    AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(Animalquiz.this);
    yourCountDownTimer.cancel();
    alertDialogBuilder
            .setMessage("Game Over /n your total score is " + (String.valueOf(counter)))
            .setCancelable(false)
            .setPositiveButton("New Game", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    startActivity(new Intent(getApplicationContext(), Animalquiz.class));
                }
            })
            .setNegativeButton("Exit", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    System.exit(0);
                }
            });
    alertDialogBuilder.show();

}

private void Finished() {
    AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(Animalquiz.this);
    yourCountDownTimer.cancel();
    alertDialogBuilder
            .setMessage("Game Finished your total score is " + (String.valueOf(counter)))
            .setCancelable(false)
            .setPositiveButton("New Game", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    startActivity(new Intent(getApplicationContext(), Animalquiz.class));
                }
            })
            .setNegativeButton("Exit", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    System.exit(0);
                }
            });
    alertDialogBuilder.show();

}

private void NextQuestion(int num) {
    tv_question.setImageResource(question.getQuestion(num));
    btn_one.setText(question.getchoice1(num));
    btn_two.setText(question.getchoice2(num));
    btn_three.setText(question.getchoice3(num));
    btn_four.setText(question.getchoice4(num));

    answer = question.getCorrectAnswer(num);

}

2019-10-21 21:48:33.020 26853-26853/com.whatthemobile.quizforkids E/AndroidRuntime: FATAL EXCEPTION: main Process: com.whatthemobile.quizforkids, PID: 26853 android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@98fa673 is not valid; is your activity running? at android.view.ViewRootImpl.setView(ViewRootImpl.java:806) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94) at android.app.Dialog.show(Dialog.java:329) at android.app.AlertDialog$Builder.show(AlertDialog.java:1125) at com.whatthemobile.quizforkids.Animalquiz.TryAgain(Animalquiz.java:193) at com.whatthemobile.quizforkids.Animalquiz.access$000(Animalquiz.java:15) at com.whatthemobile.quizforkids.Animalquiz$2.onFinish(Animalquiz.java:64) at android.os.CountDownTimer$1.handleMessage(CountDownTimer.java:127) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:198) at android.app.ActivityThread.main(ActivityThread.java:6729) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

I'm using this code to change between different images, but the only image that is visualized is the last one. The TextView "vista_aterrizaje" does not show the expected value in each loop.

I have tried to use invalidate() method before the sleep() one, to try to visualize it in each different loop of the for. But it does not work.

establecerImagen() method is used for changing the imageView.

for (int i = 0; i < 15; i++) {
        cur_aterrizaje = aterrizajes[rand.nextInt(aterrizajes.length)];
        vista_aterrizaje.setText(cur_aterrizaje);
        establecerImagen(cur_aterrizaje);
        vista_aterrizaje.();
        mapa.invalidate();
        try{
            Thread.sleep(50);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

I think that the problem is in the sleep() method but I don't know how to wait a few seconds after every change of the views in order to visualize every change.

I am using FTP Commons library to list files from FTP, I have noticed that sometimes it does not return all of the files on the FTP server (or folders). I have reason to believe that this may be a bug because when I add a new file on the ftp server, all files are returned even if I delete the newly added file afterwards the files that were not showing will now show. Here is how I am listing files in my app:

        FTPClient client = new FTPClient();
        try {
            client.connect(myFTP_Server);
            client.enterLocalPassiveMode();
            client.login(myFTP_Username, myFTP_Password);
            client.changeWorkingDirectory("/public_html" + sky_SNAP+ "/" + username);

            FTPFile[] files = client.listFiles();

The problem is that when I sync a project on Android Studio. I followed all the steps on the Fire base guide, the "ASCII" error occurred.I already paste my google-services.json file is in the app folder.

build.gradle(Project: )

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
        classpath 'com.google.gms:google-services:4.3.2'

    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

build.gradle(Module:app)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.anikr.communityapp"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.google.firebase:firebase-analytics:17.2.0'

}
apply plugin: 'com.google.gms.google-services'

I have an Android application already set up deep linking to Facebook. Everything works fine except this weird behavior.

If I click on the post contains list of item like this: List items example

It will redirect to my app with the information of item that I clicked. This is what I expected but when I click on the post contains only 1 item like this: Single item example

It will open the link with Facebook in-app browser, and I have to click on the Open with 'my app' button in the top right corner of the browser to open this with my app. This is not what I expected.

So can anyone please explain what happened? Did I do something wrong or am I missing something?

I am getting list of images from Room database. It has unexpected behavior. From time to time I am getting strange images, but can't understand when it happened. I think, it is related to cache. I am getting this https://yadi.sk/i/Frl9XX6P0zfvEg image instead of this https://yadi.sk/i/wsVYcRwGzT92gw Thanks for help).

fun deleteCache(context:Context) { try { val dir = context.getCacheDir() deleteDir(dir) } catch (e:Exception) { e.printStackTrace() } } fun deleteDir(dir:File):Boolean { if (dir.exists()&& dir.isDirectory) { val children = dir.list() for (i in children.indices) { val success = deleteDir(File(dir, children[i])) if (!success) { return false } } return dir.delete() } else if (dir.exists() && dir.isFile()) { return dir.delete() } else { return false } } I have add this function in onCreate method to delete the cache, when the app runs the first time.

What to do to fix the problem in the photo below. enter image description here

This is the URL I send to Google.

https://docs.google.com/uc?export=download&id=1vm286JK1mmbN-igPwzIOU7mKKsJ8eEdi

I have filled out this form as shown below enter image description here

But after filling out this form, the email that Google sends me is as pictured below and I've tested several other URLs but it sends me the same error every time. enter image description here

I am trying to have android display a notification when a button is pressed. I have followed an online tutorial but nothing happens when I press the button. Looking online I cannot find anything that would suggest what is wrong and I am new to android.

I have one class


import android.app.Notification;
import android.os.Bundle;
import android.app.NotificationManager;
import android.view.View;
import android.content.Context;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void sendNotification(View view) {

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "M_CH_ID");

        notificationBuilder.setAutoCancel(true)
                .setDefaults(Notification.DEFAULT_ALL)
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.drawable.ic_launcher)
                .setTicker("Hearty365")
                .setContentTitle("Default notification")
                .setContentText("Random words")
                .setContentInfo("Info");

        NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(1, notificationBuilder.build());
    }
} 

and one layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="sendNotification"
        android:text="@string/do_it" />

</LinearLayout>

I would appreciate if someone could explain to me what the issue is or possibly link me to a tutorial that is accurate as of API 29.

I'm trying to preview files using the QLPreviewController class. I basically do something like this:

self.present(qlvc, animated: true)

which works great. However, when I hit the Done button on the left of the navbar, the controller simply disappears (without animation). How can I let the preview controller animate out (like all other modal vcs do, sliding down)?

Thank you!

This method returns a qr code image of a string. It works correctly on Ios 12.0.1 (iphone SE) but it crash on 12.4.2 (iphone 6). The method crash when i try to assign the resultant UIImage to an UIImageView, the resultant UIImage is not nil.

-(UIImage*)get_QR_image :(NSString*)qrString :(UIColor*)ForeGroundCol :(UIColor*)BackGroundCol{

    NSData *stringData = [qrString dataUsingEncoding: NSUTF8StringEncoding];

    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [qrFilter setValue:stringData forKey:@"inputMessage"];
    [qrFilter setValue:@"H" forKey:@"inputCorrectionLevel"];


    CIImage *qrImage = qrFilter.outputImage;
    float scaleX = 320;
    float scaleY = 320;


    CIColor *iForegroundColor = [CIColor colorWithCGColor:[ForeGroundCol CGColor]];
    CIColor *iBackgroundColor = [CIColor colorWithCGColor:[BackGroundCol CGColor]];

    CIFilter * filterColor = [CIFilter filterWithName:@"CIFalseColor" keysAndValues:@"inputImage", qrImage, @"inputColor0", iForegroundColor, @"inputColor1", iBackgroundColor, nil];

    CIImage *filtered_image = [filterColor valueForKey:@"outputImage"];

    filtered_image = [filtered_image imageByApplyingTransform:CGAffineTransformMakeScale(scaleX, scaleY)];

    UIImage *result_image = [UIImage imageWithCIImage:filtered_image
                                                 scale:[UIScreen mainScreen].scale
                                           orientation:UIImageOrientationUp];


    return result_image;
}

the line involved in crash is:

filtered_image = [filtered_image imageByApplyingTransform:CGAffineTransformMakeScale(scaleX, scaleY)];

it generates this log:

warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.

There's something in my method that works only on 12.0.1 ? Or maybe something wrong ? How i can investigate more to solve that crash ?

right now this is all I have in my project:

enter image description here

In the end it should look and function pretty like this:

enter image description here enter image description hereenter image description here

1. How do I add items into the ScrollView (in a 2 x X View)

2. How do I make the ScrollView actually be able to scroll (and refresh like in the 3 pictures below) or is this maybe solvable with just a list?

I'm using Mapbox open source project, when I set the userTrackingMode as MGLUserTrackingModeFollowWith... the map correctly keep the gps in the center of the view. But what shall I do to keep the GPS icon at the 2/3 (or 3/4) of the screen (vertically) instead of the center? This is useful in Navigation App to have more map in front of the vehicle compared to having the vehicle in the center of the map.

I have not found any existing API or setting allowing it.

I'm gonna show a video in react native application, while the video is located on a cloud and is accessible via a url.

There are 3 scenarios for showing the video:

  1. Download the video and using the OS (Android or iOS) default video player to play it

  2. Download the video and using a react-native in-app video player

  3. Stream the video and show it in default os player or in-app video player

Which option is awesome in most cases? And how can I develop it?

Regards

How often can I call UIApplication. shared.registerForRemoteNotifications() in order to retrieve the device token from APNs. Are there any rules or limits stated in the documentation from Apple. Can I call it as often as I wish?

I have tried to look through the documentation my self, but could not find anything useful. If I have overlooked something please accept my apologies, but please paste a link to the documentation that relates to this question.

I will also be interested in other people's experiences.

Thank you very much in advance!

I am developing a Cocoa Touch Framework, I want to receive remote notifications from APNS and I found that (after all the process it needs to establish communication with APNS) I have to catch notifications inside application:didReceiveRemoteNotification:fetchCompletionHandler: method. However, I want to let the less work possible to the IOS developer, so I been wondering.

  • Is it possible to create a category of the AppDelegate inside my framework (Since it already has an extension when a new app is created) in order to implement this method ?.

I am using SwiftyStoreKit for InApp Purchase Consumable for a tip jar. Everything works for testing but according to this answer and the comments server side validation isn't necessary but it is suggested. The answer states "consumables, un-consumables and subscriptions are susceptible to fraudulent attacks. Often though iap crackers or network spoofing. Validating the receipt can mitigate this problem".

1- If userA sends me a tip how is it possible for an attacker to intercept that tip and take the money if everything goes through Apple?

2- Do I need to set up a Heroku instance or use something else for the server validation? I can't find anything on it. I would assume I would need to add the server side code in the success case below in if product.needsFinishTransaction { SwiftyStoreKit.finishTransaction(product.transaction) } but I don't know how to set up a server from that point on.

SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in

    switch result {
        case .success(let product):
            // fetch content from your server, then:
            if product.needsFinishTransaction {
                SwiftyStoreKit.finishTransaction(product.transaction)
            }
            print("Purchase Success: \(product.productId)")
        // failed cases ...
    }
}

Here is the code:

AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
        for purchase in purchases {
            switch purchase.transaction.transactionState {
            case .purchased, .restored:
                if purchase.needsFinishTransaction {
                    // Deliver content from server, then:
                    SwiftyStoreKit.finishTransaction(purchase.transaction)
                }
            // Unlock content
            case .failed, .purchasing, .deferred:
                break // do nothing
            @unknown default:
                break
            }
        }
    }
}

TipJarVC. The purchase is made in the collectionView's didSelect item:

var dataSource = [Tip]()
var sharedSecret = appStoreConnectSecretKey

let inAppProductIds = ["com.myCo.myAppName.firstTip", // 0.99
                       "com.myCo.myAppName.secondTip", // 9.99 ]

override func viewDidLoad() {
    super.viewDidLoad()

   getInAppPurchaseAmounts()
}

func getInAppPurchaseAmounts() {

    // show spinner

    let dispatchGroup = DispatchGroup()

    for productId in inAppProductIds {

        dispatchGroup.enter()

        SwiftyStoreKit.retrieveProductsInfo([productId]) { [weak self](result) in
            if let product = result.retrievedProducts.first {
                let priceString = product.localizedPrice!
                print("Product: \(product.localizedDescription), price: \(priceString)")

                let tip = Tip(displayName: product.description,
                              description: product.localizedDescription,
                              productId: productId
                              price: priceString)


                self?.addTipToDataSource(tip)

                if let sharedSecret = self?.sharedSecret {

                    self?.verifyPurchase(with: productId, sharedSecret: sharedSecret)
                }
                dispatchGroup.leave()

            } else if let invalidProductId = result.invalidProductIDs.first {
                print("Invalid product identifier: \(invalidProductId)")
                dispatchGroup.leave()

            } else {
                print("Error: \(String(describing: result.error))")
                dispatchGroup.leave()
            }
        }
    }

    dispatchGroup.notify(queue: .global(qos: .background)) { [weak self] in
        DispatchQueue.main.async { [weak self] in

            // removeSpinnerAndReloadData()
        }
    }
}

func verifyPurchase(with productId: String, sharedSecret: String) {

    let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: sharedSecret)
    SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
        switch result {
        case .success(let receipt):
            let productId = productId
            // Verify the purchase of Consumable or NonConsumable
            let purchaseResult = SwiftyStoreKit.verifyPurchase(
                productId: productId,
                inReceipt: receipt)

            switch purchaseResult {
            case .purchased(let receiptItem):
                print("\(productId) is purchased: \(receiptItem)")
            case .notPurchased:
                print("The user has never purchased \(productId)")
            }
        case .error(let error):
            print("Receipt verification failed: \(error)")
        }
    }
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) as? TipCell else { return }
    guard let indexPath = collectionView.indexPath(for: cell) else { return }

    let tip = dataSource[indexPath.item]

    purchaseProduct(with: tip.productId)
}

func purchaseProduct(with productId: String) {

    SwiftyStoreKit.retrieveProductsInfo([productId]) { result in
        if let product = result.retrievedProducts.first {
            SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in

                switch result {
                case .success(let product):
                    // fetch content from your server, then:
                    if product.needsFinishTransaction {
                        SwiftyStoreKit.finishTransaction(product.transaction)
                    }
                    print("Purchase Success: \(product.productId)")
                case .error(let error):
                    switch error.code {
                    case .unknown:
                        print("Unknown error. Please contact support")
                    case .clientInvalid:
                        print("Not allowed to make the payment")
                    case .paymentCancelled:
                        print("Payment cancelled")
                    case .paymentInvalid:
                        print("The purchase identifier was invalid")
                    case .paymentNotAllowed:
                        print("The device is not allowed to make the payment")
                    case .storeProductNotAvailable:
                        print("The product is not available in the current storefront")
                    case .cloudServicePermissionDenied:
                        print("Access to cloud service information is not allowed")
                    case .cloudServiceNetworkConnectionFailed:
                        print("Could not connect to the network")
                    case .cloudServiceRevoked:
                        print("User has revoked permission to use this cloud service")
                    default:
                        print((error as NSError).localizedDescription)
                    }
                }
            }
        }
    }
}

I have question about sender title. I have a button and I want to change title according to some condition. In this point I made sender.setTitle() code but title appear only like 0.5 second. Its not visible let me say more clear. How can I solve it? Here my code:

    @objc func handleExpandCloseForAlim(sender: UIButton) {


    if (sender.tag == 1) {
        if keys[0] == 0 {
            sender.setTitle("titlesample", for: .normal)
            keys[0] = 1
        }else{
            keys[0] = 0
            sender.setTitle("titlesampleclose", for: .normal)
        }
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }

    }

Here my header view code:

            let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 100))
        // code for adding centered title
        headerView.backgroundColor = .lightGray
        let headerLabel = UILabel(frame: CGRect(x: 0, y: 10, width: tableView.bounds.size.width, height: 28))
        headerLabel.textColor = UIColor.black
        headerLabel.text = "   Teslim Alınacağı Adres"
        headerLabel.font = UIFont.boldSystemFont(ofSize: 14)
        headerLabel.textAlignment = .left
        headerView.addSubview(headerLabel)

        // code for adding button to right corner of section header
        let button: UIButton = UIButton(frame: CGRect(x:headerView.frame.size.width - 100, y:8, width:100, height:28))
        button.setTitleColor(.black, for: .normal)
        button.titleLabel!.font = UIFont.boldSystemFont(ofSize: 14)
        button.layer.cornerRadius = 4
        button.tag = 1
        button.backgroundColor = UIColor.blue
        button.addTarget(self, action: #selector(handleExpandCloseForAlim(sender:)), for: .touchUpInside)

        headerView.addSubview(button)
        return headerView

I am creating a Chinese learning app, with a section for correctly pronouncing certain sounds. To organise these sounds into a table, I have created a UICollectionView, where each UICollectionViewCell contains a UITableView.

Each UICollectionViewCell acts as a column, and contains a UITableView.

Each UITableViewCell acts as a row in each column.

This results in the following appearance.

UICollectionView containing UITableViews

However, although I can easily detect taps on each UICollectionViewCell using collectionView:didSelectItemAtIndexPath: , I am unable to detect any touches at all on the UITableViewCells within.

Whether I convert the view in the Cell to be a button, or whether I as a UITapGestureRecognizer, or whether I use the function tableView:didSelectItemAtIndexPath, no touches at all are detected in the UITableView.

How can I allow my code to correctly detect these touches on the UITableViewCells within the UICollectionView?

Thanks!

Update 1:

Here is the @interfact of the Column .h file (UICollectionViewCell)

@interface PronounciationColumn : UICollectionViewCell <UITableViewDelegate, UITableViewDataSource>

In the .m of the UICollectionViewCell, the delegate and dataSource of the tableView is set to self.

Update 2:

Another user had a really neat idea of setting the delegate of the tableView to it's own collectionViewCell using the following code:

- (PronounciationColumn *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {

    PronounciationColumn *column;
    NSString *columnIdentifier = [self getIdentifier:collectionView];
    if (column == nil) {
        column = (PronounciationColumn *)[collectionView dequeueReusableCellWithReuseIdentifier:columnIdentifier forIndexPath:indexPath];
        column.arrayOfFinals = [[self getArrayOfPinyins:collectionView] objectAtIndex:indexPath.item];
    }
    column.tableOfFinals.delegate = column;
    column.tableOfFinals.dataSource = column;
    return column;
}

The tableView continues to be populated correctly, however, the touches are still not detected on the cells, as tableView:didSelectRowAtIndexPath: is still not called.