There is no trick, really. You just have to redraw the patterns during VBlank. That's a lot of data to update during VBlank though, so you'll have to code very efficient code to do this (unrolled loop), or keep the PPU disabled for a while after VBlank ends to get a little extra time. You'll probably have to do both actually!
I do exactly this in my current project. All the game graphics are stored in the ROM as blocks of 256 bytes (16 tiles). Every frame there is time to copy one of these blocks to a place in the pattern table. Those 256 bytes are scrambled a bit, to make reading them faster.
All blocks are inside 16KB ROM banks, meaning there are 64 of them in each a bank. The pattern-updating routine will switch in the bank with the desired block of tiles, load the X register with the index of the block inside that bank (0 to 63), set the destination address through $2006 and execute the following code:
Code:
lda Byte00, x
sta $2007
lda Byte01, x
sta $2007
lda Byte02, x
sta $2007
(...)
lda ByteFE, x
sta $2007
lda ByteFF, x
sta $2007
This code takes quite a bit of ROM space (1536 bytes), but makes it possible to update 256 bytes worth of patterns in 2048 CPU cycles (about 18 scanlines). That's almost the entire VBlank time, so I sure have to delay the start of the frame a bit in order to do other things while rendering is still off (update the palette, metatiles, sprites, etc). Those other routines should be pretty efficient too, since so much time was dedicated to copying patterns already.
Also, as I said before, the tiles are a bit scrambled. The first byte of each of the 64 blocks in the bank are stored together, and this list is pointed by the "Byte00" label. Then follow all the second bytes (pointed by "Byte01"), and so on.