This particular behaviour can be seen throughout most Dapps which interact with a user's token balance in some way or another. Uniswap, 1inch, Bancor and many more of the leading Dapps require the user to approve the token transfer in a separate transaction before the actual transfer of tokens can be executed.
The reason why this is necessary is due to the design of ERC-20 tokens. When a contract receives ETH, the sender of that transaction is available to the contract. This is not possible with ERC20 tokens however - the receiving contract has to transfer the tokens on behalf of the user to be able to know where it received the tokens from. Obviously, the user has to give authorization to the receiving contract to move his/her tokens first.
As you wrote in your question, there is the possibility to allow the receiving contract unlimited access to the user's funds. This way, only one authorization transaction is necessary per user and per token. At the moment, this is unfortunately the best general solution to the problem we have in regards to creating as few transactions for the end-user as possible and is supported by most Dapps. From a security point of view, this is not a good solution though.
In the original
CREATE opcode, the address for a contract is a function of the deployer's account and nonce and thus not accurately predictable. The
CREATE2 instruction was later added through EIP-1014 and changes the way contracts are deployed.
CREATE2 makes it possible to calculate the addresses of contracts deployed in the future. This allows to "interact" with contracts which are not deployed yet. However,
CREATE2 does not really help to solve the problem from your question.